prismarine-windows 2.6.1 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  strategy:
15
15
  matrix:
16
- node-version: [14.x]
16
+ node-version: [18.x]
17
17
 
18
18
  steps:
19
19
  - uses: actions/checkout@v2
@@ -0,0 +1,22 @@
1
+ name: Repo Commands
2
+
3
+ on:
4
+ issue_comment: # Handle comment commands
5
+ types: [created]
6
+ pull_request: # Handle renamed PRs
7
+ types: [edited]
8
+
9
+ jobs:
10
+ comment-trigger:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Check out repository
14
+ uses: actions/checkout@v3
15
+ - name: Run command handlers
16
+ uses: PrismarineJS/prismarine-repo-actions@master
17
+ with:
18
+ # NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it.
19
+ token: ${{ secrets.PAT_PASSWORD }}
20
+ # See `Options` section below for more info on these options
21
+ install-command: npm install
22
+ /fixlint.fix-command: npm run fix
@@ -13,7 +13,7 @@ jobs:
13
13
  - name: Set up Node.js
14
14
  uses: actions/setup-node@master
15
15
  with:
16
- node-version: 14.0.0
16
+ node-version: 18.0.0
17
17
  - id: publish
18
18
  uses: JS-DevTools/npm-publish@v1
19
19
  with:
package/HISTORY.md CHANGED
@@ -1,3 +1,19 @@
1
+ ### 2.8.0
2
+ * [Export changedSlots computation to mineflayer (#102)](https://github.com/PrismarineJS/prismarine-windows/commit/98c3d66ae6aca733bb234d487686e2e534d1924d) (thanks @kaduvert)
3
+ * [Ensure numberClick with same slots doesn't do anything (#103)](https://github.com/PrismarineJS/prismarine-windows/commit/50e6bfbdb9c3bc26f06ea45760527b2da53eb1f3) (thanks @kaduvert)
4
+ * [Fix broken behaviour with craftingResultSlots (#101)](https://github.com/PrismarineJS/prismarine-windows/commit/51cc06e0f7dd39ddbb617962dfc1bb659fc172ce) (thanks @kaduvert)
5
+
6
+ ### 2.7.0
7
+ * [More click modes (with tests!) (#74)](https://github.com/PrismarineJS/prismarine-windows/commit/9f19fb357b2a96e09060fa5c2393b9041cc869fe) (thanks @kaduvert)
8
+ * [Add command gh workflow allowing to use release command in comments (#98)](https://github.com/PrismarineJS/prismarine-windows/commit/6ddc90092d63f3838136d83e583d7c1d7ace3ae2) (thanks @rom1504)
9
+ * [fix](https://github.com/PrismarineJS/prismarine-windows/commit/8ab955e465ce9586bfba0286ef1340b86dc914e4) (thanks @rom1504)
10
+ * [Update to node 18.0.0 (#96)](https://github.com/PrismarineJS/prismarine-windows/commit/98912036737aa4c2e2f743b12bdb66eac5073889) (thanks @rom1504)
11
+ * [Bump @types/node from 18.16.13 to 20.2.1 (#95)](https://github.com/PrismarineJS/prismarine-windows/commit/cf7401e3caf1ff2a5f582338b3815eb1f80187b2) (thanks @dependabot[bot])
12
+ * [Fix slot layout for dispencer and droppers (#86)](https://github.com/PrismarineJS/prismarine-windows/commit/b42576a0e3eac7b42c843fa96c80191218efe97a) (thanks @IceTank)
13
+ * [Change slots from Array<Item> to Array<Item | null> (#85)](https://github.com/PrismarineJS/prismarine-windows/commit/652cd992b27254dcc7173e93ec8a112fd56cb459) (thanks @yurei-dll)
14
+ * [use registry.supportFeature (#82)](https://github.com/PrismarineJS/prismarine-windows/commit/3d4514ab56274cfe1fc5d578e3e101d6e41391f7) (thanks @Epirito)
15
+ * [Bump typed-emitter from 1.4.0 to 2.1.0 (#76)](https://github.com/PrismarineJS/prismarine-windows/commit/86e6aa0727c7e6a4ffa0602bf1aba7fd9d70f71f) (thanks @dependabot[bot])
16
+
1
17
  ## 2.6.1
2
18
 
3
19
  * fix typo
package/index.d.ts CHANGED
@@ -26,7 +26,7 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
26
26
  /**
27
27
  * Map of slot index to Item instance. null if the slot is empty
28
28
  */
29
- slots: Array<Item>;
29
+ slots: Array<Item | null>;
30
30
 
31
31
  /**
32
32
  * Slot from where the player inventory start in the window
@@ -49,7 +49,7 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
49
49
  craftingResultSlot: number;
50
50
 
51
51
  /**
52
- * boolean only false for chests in pre-1.14 versions.
52
+ * Boolean only false for chests in pre-1.14 versions.
53
53
  */
54
54
  requiresConfirmation: boolean;
55
55
 
@@ -58,15 +58,117 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
58
58
  */
59
59
  selectedItem: Item | null;
60
60
 
61
- acceptClick(click: Click): void;
61
+ /**
62
+ * accepts Clicks of with any mode, mouseButton and slot
63
+ * @param click click object to accept
64
+ * @param gamemode to know when certain clicks are allowed
65
+ */
66
+ acceptClick(click: Click, gamemode: number): void;
67
+
68
+ /** @deprecated use {@link acceptClick} instead */
62
69
  acceptOutsideWindowClick(click: Click): void;
70
+
71
+ /** @deprecated use {@link acceptClick} instead */
63
72
  acceptInventoryClick(click: Click): void;
73
+
74
+ /** @deprecated use {@link acceptClick} instead */
64
75
  acceptNonInventorySwapAreaClick(click: Click): void;
65
- acceptNonInventorySwapAreaClick(click: Click): void;
76
+
77
+ /** @deprecated use {@link acceptClick} instead */
66
78
  acceptSwapAreaLeftClick(click: Click): void;
79
+
80
+ /** @deprecated use {@link acceptClick} instead */
67
81
  acceptSwapAreaRightClick(click: Click): void;
82
+
83
+ /** @deprecated use {@link acceptClick} instead */
68
84
  acceptCraftingClick(click: Click): void;
69
85
 
86
+ /**
87
+ * See click types here https://wiki.vg/Protocol#Click_Window
88
+ */
89
+
90
+ /**
91
+ * Accepts click mode 0 with mouseButton 0 or 1
92
+ */
93
+ mouseClick(click: Click): void;
94
+
95
+ /**
96
+ * Accepts click mode 1 with mouseButton 0 or 1 (identical behaviour)
97
+ */
98
+ shiftClick(click: Click): void;
99
+
100
+ /**
101
+ * Accepts click mode 2 with mouseButton 0 (hotbarStart) to 8 (hotbarEnd) representing the hotbar slots
102
+ */
103
+ numberClick(click: Click): void;
104
+
105
+ /**
106
+ * Accepts click mode 3 with mouseButton 2 (gets a stack of the item at the slot into the selectedItem)
107
+ */
108
+ middleClick(click: Click, gamemode: number): void;
109
+
110
+ /**
111
+ * Accepts click mode 4 with mouseButton 0 (drops one of the item) or 1 (drops all of the item)
112
+ */
113
+ dropClick(click: Click): void;
114
+
115
+ /**
116
+ * Fills within specified range with given item and dumps remaining items if present and possible
117
+ * @param item item used to fill slots
118
+ * @param start start slot to begin the search from
119
+ * @param end end slot to end the search
120
+ * @param lastToFirst if true the matching Slots will be filled from the back
121
+ */
122
+ fillAndDump(item: Item, start: number, end: number, lastToFirst: boolean): void;
123
+
124
+ /**
125
+ * Fills slots with specified item
126
+ * @param slots slots to fill with the item
127
+ * @param lastToFirst if true the matching Slots will be filled from the back
128
+ */
129
+ fillSlotsWithItem(slots: Array<Item>, item: Item, lastToFirst: boolean): void;
130
+
131
+ /**
132
+ * Fills slot with specified item
133
+ * @param itemToFill item of which the count should be increased
134
+ * @param itemToTake item of which the count should be decreased
135
+ */
136
+ fillSlotWithItem(itemToFill: Item, itemToTake: Item): void;
137
+
138
+ /**
139
+ * Fills slot with selectedItem (the item held in mouse cursor)
140
+ * @param item item of which the count should be increased
141
+ * @param untilFull if true as many as possible will be transfered
142
+ */
143
+ fillSlotWithSelectedItem (item: Item, untilFull: boolean): void;
144
+
145
+ /**
146
+ * Searches for empty slot to dump the specified item
147
+ * @param item item which should be dumped
148
+ * @param start start slot to begin the search from
149
+ * @param end end slot to end the search
150
+ * @param lastToFirst if true item slot will be searched from the back
151
+ */
152
+ dumpItem(item: Item, start: number, end: number, lastToFirst: boolean): void;
153
+
154
+ /**
155
+ * Splits the slot in half and holds the split in mouse cursor
156
+ * @param item item to split
157
+ */
158
+ splitSlot(item: Item): void;
159
+
160
+ /**
161
+ * Swaps item with the item in mouse cursor
162
+ * @param item item to swap with
163
+ */
164
+ swapSelectedItem(item: Item): void;
165
+
166
+ /**
167
+ * Drops item held in mouse cursor
168
+ * @param untilEmpty if true whole item stack will be dropped (else just one)
169
+ */
170
+ dropSelectedItem(untilEmpty: boolean): void;
171
+
70
172
  /**
71
173
  * Change the slot to contain the newItem. Emit the updateSlot events.
72
174
  * @param slot {number}
@@ -75,6 +177,18 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
75
177
  updateSlot(slot: number, newItem: Item): void;
76
178
 
77
179
  /**
180
+ * Returns array of items in the given range matching the one specified
181
+ * @param start start slot to begin the search from
182
+ * @param end end slot to end the search
183
+ * @param itemType numerical id that you are looking for
184
+ * @param metadata metadata value that you are looking for. null means unspecified
185
+ * @param notFull (optional) - if true, means that the returned item should not be at its stackSize
186
+ * @param nbt nbt data for the item you are looking for. null means unspecified
187
+ */
188
+ findItemsRange(start: number, end: number, itemType: number, metadata: number | null, notFull: boolean, nbt: any): Array<Item> | null;
189
+
190
+ /**
191
+ * Returns item in the given range matching the one specified
78
192
  * @param start start slot to begin the search from
79
193
  * @param end end slot to end the search
80
194
  * @param itemType numerical id that you are looking for
@@ -99,7 +213,7 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
99
213
  * @param metadata metadata value that you are looking for. null means unspecified
100
214
  * @param notFull (optional) - if true, means that the returned item should not be at its stackSize
101
215
  */
102
- findInventoryItem(itemType: number | string, metadata: number | null, notFull: boolean): Item | null;
216
+ findInventoryItem(itemType: number, metadata: number | null, notFull: boolean): Item | null;
103
217
 
104
218
  /**
105
219
  * Search in the container of the window
@@ -107,7 +221,7 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
107
221
  * @param metadata metadata value that you are looking for. null means unspecified
108
222
  * @param notFull (optional) - if true, means that the returned item should not be at its stackSize
109
223
  */
110
- findContainerItem(itemType: number, metadata: number | null, notFull: boolean): Item | null
224
+ findContainerItem(itemType: number, metadata: number | null, notFull: boolean): Item | null;
111
225
 
112
226
  /**
113
227
  * Return the id of the first empty slot between start and end
@@ -132,6 +246,13 @@ export class Window<T = unknown> extends (EventEmitter as new <T>() => TypedEmit
132
246
  */
133
247
  firstEmptyInventorySlot(hotbarFirst?: boolean): number | null;
134
248
 
249
+ /**
250
+ * Returns how much items there are ignoring what the item is, between slots start and end
251
+ * @param start
252
+ * @param end
253
+ */
254
+ sumRange(start: number, end: number): number;
255
+
135
256
  /**
136
257
  * Returns how many item you have of the given type, between slots start and end
137
258
  * @param start
@@ -206,14 +327,13 @@ export interface WindowInfo {
206
327
  export interface WindowsExports {
207
328
  createWindow(id: number, type: number | string, title: string, slotCount?: number): Window;
208
329
  Window: typeof Window;
209
- windows: {[key in WindowName]: WindowInfo};
330
+ windows: {[key: string]: WindowInfo};
210
331
  }
211
-
212
332
  export declare function loader(mcVersion: string): WindowsExports;
213
333
 
214
334
  export default loader;
215
335
 
216
- export type WindowName =
336
+ export type WindowName =
217
337
  'minecraft:inventory' |
218
338
  'minecraft:generic_9x1' |
219
339
  'minecraft:generic_9x2' |
package/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  function loader (registryOrVersion) {
2
2
  const registry = typeof registryOrVersion === 'string' ? require('prismarine-registry')(registryOrVersion) : registryOrVersion
3
3
  const Item = require('prismarine-item')(registry)
4
- const Window = require('./lib/Window')(Item)
4
+ const Window = require('./lib/Window')(Item, registry)
5
5
 
6
6
  let windows
7
- // if (registry.supportFeature("village&pillageInventoryWindows")) {
8
- if (registry.isNewerOrEqualTo('1.14')) { // this line should be discarded for the one above when the corresponding feature is added to minecraft-data
7
+ if (registry.supportFeature('village&pillageInventoryWindows')) {
9
8
  // https://wiki.vg/Inventory
10
9
  windows = {}
11
10
  let protocolId = -1
@@ -30,8 +29,7 @@ function loader (registryOrVersion) {
30
29
  windows['minecraft:loom'] = { type: protocolId++, inventory: { start: 4, end: 39 }, slots: 40, craft: 3, requireConfirmation: true }
31
30
  windows['minecraft:merchant'] = { type: protocolId++, inventory: { start: 3, end: 38 }, slots: 39, craft: 2, requireConfirmation: true }
32
31
  windows['minecraft:shulker_box'] = { type: protocolId++, inventory: { start: 27, end: 62 }, slots: 63, craft: -1, requireConfirmation: true }
33
- // if (registry.supportFeature("netherUpdateInventoryWindows")) {
34
- if (registry.isNewerOrEqualTo('1.16')) { // this line should be discarded for the one above when the corresponding feature is added to minecraft-data
32
+ if (registry.supportFeature('netherUpdateInventoryWindows')) {
35
33
  windows['minecraft:smithing'] = { type: protocolId++, inventory: { start: 3, end: 38 }, slots: 39, craft: 2, requireConfirmation: true }
36
34
  }
37
35
  windows['minecraft:smoker'] = { type: protocolId++, inventory: { start: 3, end: 38 }, slots: 39, craft: 2, requireConfirmation: true }
@@ -39,14 +37,13 @@ function loader (registryOrVersion) {
39
37
  windows['minecraft:stonecutter'] = { type: protocolId++, inventory: { start: 2, end: 37 }, slots: 38, craft: 1, requireConfirmation: true }
40
38
  } else {
41
39
  // https://wiki.vg/index.php?title=Inventory&oldid=14093
42
- // const inventorySlots = registry.supportFeature("shieldSlot") ? 46 : 45
43
- const inventorySlots = registry.isNewerOrEqualTo('1.9') ? 46 : 45// this line should be discarded for the one above when the corresponding feature is added to minecraft-data
40
+ const inventorySlots = registry.supportFeature('shieldSlot') ? 46 : 45
44
41
  windows = {
45
42
  'minecraft:inventory': { type: 'minecraft:inventory', inventory: { start: 9, end: 44 }, slots: inventorySlots, craft: 0, requireConfirmation: true },
46
43
  'minecraft:chest': null,
47
44
  'minecraft:crafting_table': { type: 'minecraft:crafting_table', inventory: { start: 10, end: 45 }, slots: 46, craft: 0, requireConfirmation: true },
48
45
  'minecraft:furnace': { type: 'minecraft:furnace', inventory: { start: 3, end: 38 }, slots: 39, craft: 2, requireConfirmation: true },
49
- 'minecraft:dispenser': { type: 'minecraft:dispenser', inventory: { start: 7 * 9, end: 7 * 9 + 35 }, slots: 7 * 9 + 36, craft: -1, requireConfirmation: true },
46
+ 'minecraft:dispenser': { type: 'minecraft:dispenser', inventory: { start: 3 * 3, end: 3 * 3 + 35 }, slots: 3 * 3 + 36, craft: -1, requireConfirmation: true },
50
47
  'minecraft:enchanting_table': { type: 'minecraft:enchanting_table', inventory: { start: 2, end: 37 }, slots: 38, craft: -1, requireConfirmation: true },
51
48
  'minecraft:brewing_stand': { type: 'minecraft:brewing_stand', inventory: { start: 5, end: 40 }, slots: 41, craft: -1, requireConfirmation: true },
52
49
  'minecraft:container': null,
@@ -54,7 +51,7 @@ function loader (registryOrVersion) {
54
51
  'minecraft:beacon': { type: 'minecraft:beacon', inventory: { start: 1, end: 36 }, slots: 37, craft: -1, requireConfirmation: true },
55
52
  'minecraft:anvil': { type: 'minecraft:anvil', inventory: { start: 3, end: 38 }, slots: 39, craft: 2, requireConfirmation: true },
56
53
  'minecraft:hopper': { type: 'minecraft:hopper', inventory: { start: 5, end: 40 }, slots: 41, craft: -1, requireConfirmation: true },
57
- 'minecraft:dropper': { type: 'minecraft:dropper', inventory: { start: 7 * 9, end: 7 * 9 + 35 }, slots: 7 * 9 + 36, craft: -1, requireConfirmation: true },
54
+ 'minecraft:dropper': { type: 'minecraft:dropper', inventory: { start: 3 * 3, end: 3 * 3 + 35 }, slots: 3 * 3 + 36, craft: -1, requireConfirmation: true },
58
55
  'minecraft:shulker_box': { type: 'minecraft:shulker_box', inventory: { start: 27, end: 62 }, slots: 63, craft: -1, requireConfirmation: true },
59
56
  EntityHorse: null
60
57
  }
@@ -71,8 +68,7 @@ function loader (registryOrVersion) {
71
68
 
72
69
  return {
73
70
  createWindow: (id, type, title, slotCount = undefined) => {
74
- let winData = windowByType.get(type)
75
- if (!winData) winData = windows[type]
71
+ let winData = windowByType.get(type) ?? windows[type]
76
72
  if (!winData) {
77
73
  if (slotCount === undefined) return null
78
74
  winData = {
package/lib/Window.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const assert = require('assert')
2
2
  const EventEmitter = require('events').EventEmitter
3
3
 
4
- module.exports = (Item) => {
4
+ module.exports = (Item, registry) => {
5
5
  return class Window extends EventEmitter {
6
6
  constructor (id, type, title, slotCount,
7
7
  inventorySlotsRange = { start: 27, end: 62 },
@@ -22,106 +22,225 @@ module.exports = (Item) => {
22
22
  this.selectedItem = null
23
23
  }
24
24
 
25
- acceptClick (click) {
26
- assert.ok(click.mouseButton === 0 || click.mouseButton === 1)
27
- if (click.slot === -999) {
28
- return this.acceptOutsideWindowClick(click)
29
- } else if (click.slot >= this.inventoryStart && click.slot < this.inventoryEnd) {
30
- return this.acceptInventoryClick(click)
31
- } else if (click.slot === this.craftingResultSlot) {
32
- return this.acceptCraftingClick(click)
33
- } else {
34
- return this.acceptNonInventorySwapAreaClick(click)
25
+ acceptClick (click, gamemode = 0) {
26
+ const { mode, slot, mouseButton } = click
27
+ assert.ok(
28
+ (mode >= 0 && mode <= 6) &&
29
+ (mouseButton >= 0 && mouseButton <= 8) &&
30
+ ((slot >= 0 && slot < this.inventoryEnd) || slot === -999 ||
31
+ (this.type === 'minecraft:inventory' && slot === 45)),
32
+ 'invalid operation')
33
+
34
+ switch (click.mode) {
35
+ case 0:
36
+ assert.ok(mouseButton <= 1, 'invalid operation')
37
+ return this.mouseClick(click)
38
+
39
+ case 1:
40
+ assert.ok(mouseButton <= 1, 'invalid operation')
41
+ return this.shiftClick(click)
42
+
43
+ case 2:
44
+ assert.ok(mouseButton <= 8, 'invalid operation')
45
+ return this.numberClick(click)
46
+
47
+ case 3:
48
+ assert.ok(mouseButton === 2, 'invalid operation')
49
+ return this.middleClick(click, gamemode)
50
+
51
+ case 4:
52
+ assert.ok(mouseButton <= 1, 'invalid operation')
53
+ return this.dropClick(click)
54
+
55
+ case 5:
56
+ assert.ok([1, 5, 9, 2, 6, 10].includes(mouseButton), 'invalid operation')
57
+ return this.dragClick(click, gamemode)
58
+
59
+ case 6:
60
+ assert.ok(mouseButton === 0, 'invalid operation')
61
+ return this.doubleClick(click)
35
62
  }
36
63
  }
37
64
 
38
- acceptOutsideWindowClick (click) {
39
- assert.strictEqual(click.mode, 0, 'unimplemented')
40
- if (click.mouseButton === 0) {
41
- this.selectedItem = null
42
- } else if (click.mouseButton === 1) {
43
- this.selectedItem.count -= 1
44
- if (!this.selectedItem.count) this.selectedItem = null
65
+ mouseClick (click) {
66
+ if (click.slot === -999) {
67
+ this.dropSelectedItem(click.mouseButton === 0)
45
68
  } else {
46
- assert.ok(false, 'unimplemented')
69
+ let { item } = click
70
+ if (click.mouseButton === 0) { // left click
71
+ if (item && this.selectedItem) {
72
+ if (Item.equal(item, this.selectedItem, false)) {
73
+ if (click.slot === this.craftingResultSlot) {
74
+ const maxTransferrable = this.selectedItem.stackSize - this.selectedItem.count
75
+ if (item.count > maxTransferrable) {
76
+ this.selectedItem.count += maxTransferrable
77
+ item.count -= maxTransferrable
78
+ } else if (item.count <= maxTransferrable) {
79
+ this.selectedItem.count += item.count
80
+ this.updateSlot(item.slot, null)
81
+ }
82
+ } else {
83
+ this.fillSlotWithSelectedItem(item, true)
84
+ }
85
+ } else {
86
+ this.swapSelectedItem(click.slot, item)
87
+ }
88
+
89
+ return [click.slot]
90
+ } else if (this.selectedItem || item) {
91
+ this.swapSelectedItem(click.slot, item)
92
+
93
+ return [click.slot]
94
+ }
95
+ } else if (click.mouseButton === 1) { // right click
96
+ if (this.selectedItem) {
97
+ if (item) {
98
+ if (Item.equal(item, this.selectedItem, false)) {
99
+ this.fillSlotWithSelectedItem(item, false)
100
+ } else {
101
+ this.swapSelectedItem(click.slot, item)
102
+ }
103
+ } else {
104
+ item = new Item(this.selectedItem.type, 0, this.selectedItem.metadata, this.selectedItem.nbt)
105
+ this.updateSlot(click.slot, item)
106
+ this.fillSlotWithSelectedItem(item, false)
107
+ }
108
+
109
+ return [click.slot]
110
+ } else if (item && click.slot !== this.craftingResultSlot) {
111
+ this.splitSlot(item)
112
+
113
+ return [click.slot]
114
+ }
115
+ }
47
116
  }
117
+
48
118
  return []
49
119
  }
50
120
 
51
- acceptInventoryClick (click) {
52
- if (click.mouseButton === 0) {
53
- if (click.mode > 0) {
54
- assert.ok(false, 'unimplemented')
121
+ shiftClick (click) {
122
+ const { item } = click
123
+ if (!item) return
124
+ if (this.type === 'minecraft:inventory') {
125
+ if (click.slot < this.inventoryStart) {
126
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd, click.slot === this.craftingResultSlot)
55
127
  } else {
56
- return this.acceptSwapAreaLeftClick(click)
128
+ if (click.slot >= this.inventoryStart && click.slot < this.inventoryEnd - 10) {
129
+ this.fillAndDump(item, this.hotbarStart, this.inventoryEnd)
130
+ } else {
131
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd)
132
+ }
57
133
  }
58
- } else if (click.mouseButton === 1) {
59
- return this.acceptSwapAreaRightClick(click)
60
134
  } else {
61
- assert.ok(false, 'unimplemented')
62
- }
63
- }
64
-
65
- acceptNonInventorySwapAreaClick (click) {
66
- assert.strictEqual(click.mode, 0, 'unimplemented')
67
- if (click.mouseButton === 0) {
68
- return this.acceptSwapAreaLeftClick(click)
69
- } else if (click.mouseButton === 1) {
70
- return this.acceptSwapAreaRightClick(click)
71
- } else {
72
- assert.ok(false, 'unimplemented')
135
+ if (click.slot < this.inventoryStart) {
136
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd, this.craftingResultSlot === -1 || click.slot === this.craftingResultSlot)
137
+ } else {
138
+ this.fillAndDump(item, 0, this.inventoryStart - 1)
139
+ }
73
140
  }
74
141
  }
75
142
 
76
- acceptSwapAreaRightClick (click) {
77
- assert.strictEqual(click.mouseButton, 1)
78
- assert.strictEqual(click.mode, 0)
79
-
143
+ numberClick (click) {
144
+ if (this.selectedItem) return
80
145
  const { item } = click
81
- if (this.selectedItem) {
82
- if (item) {
83
- if (item.type === this.selectedItem.type &&
84
- item.metadata === this.selectedItem.metadata) {
85
- item.count += 1
86
- this.selectedItem.count -= 1
87
- if (this.selectedItem.count === 0) this.selectedItem = null
146
+ const hotbarSlot = this.hotbarStart + click.mouseButton
147
+ const itemAtHotbarSlot = this.slots[hotbarSlot]
148
+ if (Item.equal(item, itemAtHotbarSlot) && item?.slot === hotbarSlot) return
149
+ if (item) {
150
+ if (itemAtHotbarSlot) {
151
+ if ((this.type === 'minecraft:inventory' || registry.version['>=']('1.9')) && click.slot !== this.craftingResultSlot) {
152
+ this.updateSlot(click.slot, itemAtHotbarSlot)
153
+ this.updateSlot(hotbarSlot, item)
88
154
  } else {
89
- // swap selected item and window item
90
- this.updateSlot(click.slot, this.selectedItem)
91
- this.selectedItem = item
155
+ this.dumpItem(itemAtHotbarSlot, this.hotbarStart, this.inventoryEnd)
156
+ if (this.slots[hotbarSlot]) {
157
+ this.dumpItem(itemAtHotbarSlot, this.inventoryStart, this.hotbarStart - 1)
158
+ }
159
+ if (this.slots[hotbarSlot] === null) {
160
+ this.updateSlot(item.slot, null)
161
+ this.updateSlot(hotbarSlot, item)
162
+ let slots = this.findItemsRange(this.hotbarStart, this.inventoryEnd, itemAtHotbarSlot.type, itemAtHotbarSlot.metadata, true, itemAtHotbarSlot.nbt)
163
+ slots.push(...this.findItemsRange(this.inventoryStart, this.hotbarStart - 1, itemAtHotbarSlot.type, itemAtHotbarSlot.metadata, true, itemAtHotbarSlot.nbt))
164
+ slots = slots.filter(slot => slot.slot !== itemAtHotbarSlot.slot)
165
+ this.fillSlotsWithItem(slots, itemAtHotbarSlot)
166
+ }
92
167
  }
93
168
  } else {
94
- if (this.selectedItem.count === 1) {
95
- this.updateSlot(click.slot, this.selectedItem)
96
- this.selectedItem = null
97
- } else {
98
- this.updateSlot(click.slot, new Item(this.selectedItem.type, 1,
99
- this.selectedItem.metadata, this.selectedItem.nbt))
100
- this.selectedItem.count -= 1
101
- }
169
+ this.updateSlot(item.slot, null)
170
+ this.updateSlot(hotbarSlot, item)
102
171
  }
103
- } else if (item) {
104
- // grab 1/2 of item
105
- this.selectedItem = new Item(item.type, Math.ceil(item.count / 2),
106
- item.metadata, item.nbt)
107
- item.count -= this.selectedItem.count
108
- if (item.count === 0) this.updateSlot(item.slot, null)
172
+ } else if (itemAtHotbarSlot && click.slot !== this.craftingResultSlot) {
173
+ this.updateSlot(click.slot, itemAtHotbarSlot)
174
+ this.updateSlot(hotbarSlot, null)
109
175
  }
110
- return [{
111
- location: click.slot,
112
- item: Item.toNotch(this.slots[click.slot])
113
- }]
114
176
  }
115
177
 
116
- acceptSwapAreaLeftClick (click) {
117
- assert.strictEqual(click.mouseButton, 0)
118
- assert.strictEqual(click.mode, 0)
178
+ middleClick (click, gamemode) {
179
+ if (this.selectedItem) return []
180
+ const { item } = click
181
+ if (gamemode === 1 && item) {
182
+ this.selectedItem = new Item(item.type, item.stackSize, item.metadata, item.nbt)
183
+ }
184
+ return []
185
+ }
119
186
 
187
+ dropClick (click) {
120
188
  const { item } = click
121
- if (item && this.selectedItem &&
122
- item.type === this.selectedItem.type &&
123
- item.metadata === this.selectedItem.metadata) {
124
- // drop as many held item counts into the slot as we can
189
+ if (this.selectedItem || item === null) return []
190
+ if (click.mouseButton === 0) {
191
+ if (--click.item.count === 0) this.updateSlot(click.slot, null)
192
+ return [click.slot]
193
+ } else if (click.mouseButton === 1) {
194
+ this.updateSlot(click.slot, null)
195
+ return [click.slot]
196
+ }
197
+ }
198
+
199
+ dragClick (click, gamemode) {
200
+ // unimplemented
201
+ assert.ok(false, 'unimplemented')
202
+ }
203
+
204
+ doubleClick (click) {
205
+ // unimplemented
206
+ assert.ok(false, 'unimplemented')
207
+ }
208
+
209
+ acceptOutsideWindowClick = this.acceptClick
210
+ acceptInventoryClick = this.acceptClick
211
+ acceptNonInventorySwapAreaClick = this.acceptClick
212
+ acceptSwapAreaLeftClick = this.acceptClick
213
+ acceptSwapAreaRightClick = this.acceptClick
214
+ acceptCraftingClick = this.acceptClick
215
+
216
+ fillAndDump (item, start, end, lastToFirst = false) {
217
+ this.fillSlotsWithItem(this.findItemsRange(start, end, item.type, item.metadata, true, item.nbt, true), item, lastToFirst)
218
+ if (this.slots[item.slot]) {
219
+ this.dumpItem(item, start, end, lastToFirst)
220
+ }
221
+ }
222
+
223
+ fillSlotsWithItem (slots, item, lastToFirst = false) {
224
+ while (slots.length && item.count) {
225
+ this.fillSlotWithItem(lastToFirst ? slots.pop() : slots.shift(), item)
226
+ }
227
+ }
228
+
229
+ fillSlotWithItem (itemToFill, itemToTake) {
230
+ const newCount = itemToFill.count + itemToTake.count
231
+ const leftover = newCount - itemToFill.stackSize
232
+ if (leftover <= 0) {
233
+ itemToFill.count = newCount
234
+ itemToTake.count = 0
235
+ this.updateSlot(itemToTake.slot, null)
236
+ } else {
237
+ itemToFill.count = itemToFill.stackSize
238
+ itemToTake.count = leftover
239
+ }
240
+ }
241
+
242
+ fillSlotWithSelectedItem (item, untilFull) {
243
+ if (untilFull) {
125
244
  const newCount = item.count + this.selectedItem.count
126
245
  const leftover = newCount - item.stackSize
127
246
  if (leftover <= 0) {
@@ -132,15 +251,36 @@ module.exports = (Item) => {
132
251
  this.selectedItem.count = leftover
133
252
  }
134
253
  } else {
135
- // swap selected item and window item
136
- const tmp = this.selectedItem
137
- this.selectedItem = item
138
- this.updateSlot(click.slot, tmp)
254
+ if (item.count + 1 <= item.stackSize) {
255
+ item.count++
256
+ if (--this.selectedItem.count === 0) this.selectedItem = null
257
+ }
139
258
  }
140
- return [{
141
- location: click.slot,
142
- item: Item.toNotch(this.slots[click.slot])
143
- }]
259
+ }
260
+
261
+ dumpItem (item, start, end, lastToFirst = false) {
262
+ const emptySlot = lastToFirst ? this.lastEmptySlotRange(start, end) : this.firstEmptySlotRange(start, end)
263
+ if (emptySlot !== null && emptySlot !== this.craftingResultSlot) {
264
+ const slot = item.slot
265
+ this.updateSlot(emptySlot, item)
266
+ this.updateSlot(slot, null)
267
+ }
268
+ }
269
+
270
+ splitSlot (item) {
271
+ if (!item) return
272
+ this.selectedItem = new Item(item.type, Math.ceil(item.count / 2), item.metadata, item.nbt)
273
+ item.count -= this.selectedItem.count
274
+ if (item.count === 0) this.updateSlot(item.slot, null)
275
+ }
276
+
277
+ swapSelectedItem (slot, item) {
278
+ this.updateSlot(slot, this.selectedItem)
279
+ this.selectedItem = item
280
+ }
281
+
282
+ dropSelectedItem (untilEmpty) {
283
+ if (untilEmpty || --this.selectedItem.count === 0) this.selectedItem = null
144
284
  }
145
285
 
146
286
  updateSlot (slot, newItem) {
@@ -152,7 +292,18 @@ module.exports = (Item) => {
152
292
  this.emit(`updateSlot:${slot}`, oldItem, newItem)
153
293
  }
154
294
 
155
- findItemRange (start, end, itemType, metadata, notFull, nbt) {
295
+ findItemsRange (start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot = false) {
296
+ const items = []
297
+ while (start < end) {
298
+ const item = this.findItemRange(start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot)
299
+ if (!item) break
300
+ start = item.slot + 1
301
+ items.push(item)
302
+ }
303
+ return items
304
+ }
305
+
306
+ findItemRange (start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot = false) {
156
307
  assert.notStrictEqual(itemType, null)
157
308
  for (let i = start; i < end; ++i) {
158
309
  const item = this.slots[i]
@@ -160,7 +311,8 @@ module.exports = (Item) => {
160
311
  item && itemType === item.type &&
161
312
  (metadata == null || metadata === item.metadata) &&
162
313
  (!notFull || item.count < item.stackSize) &&
163
- (nbt == null || JSON.stringify(nbt) === JSON.stringify(item.nbt))) {
314
+ (nbt == null || JSON.stringify(nbt) === JSON.stringify(item.nbt)) &&
315
+ !(item.slot === this.craftingResultSlot && withoutCraftResultSlot)) {
164
316
  return item
165
317
  }
166
318
  }
@@ -196,7 +348,14 @@ module.exports = (Item) => {
196
348
 
197
349
  firstEmptySlotRange (start, end) {
198
350
  for (let i = start; i < end; ++i) {
199
- if (!this.slots[i]) return i
351
+ if (this.slots[i] === null) return i
352
+ }
353
+ return null
354
+ }
355
+
356
+ lastEmptySlotRange (start, end) {
357
+ for (let i = end; i >= start; i--) {
358
+ if (this.slots[i] === null) return i
200
359
  }
201
360
  return null
202
361
  }
@@ -217,6 +376,15 @@ module.exports = (Item) => {
217
376
  return this.firstEmptySlotRange(this.inventoryStart, this.inventoryEnd)
218
377
  }
219
378
 
379
+ sumRange (start, end) {
380
+ let sum = 0
381
+ for (let i = start; i < end; i++) {
382
+ const item = this.slots[i]
383
+ if (item) sum += item.count
384
+ }
385
+ return sum
386
+ }
387
+
220
388
  countRange (start, end, itemType, metadata) {
221
389
  let sum = 0
222
390
  for (let i = start; i < end; ++i) {
@@ -259,7 +427,7 @@ module.exports = (Item) => {
259
427
  emptySlotCount () {
260
428
  let count = 0
261
429
  for (let i = this.inventoryStart; i < this.inventoryEnd; ++i) {
262
- if (!this.slots[i]) count += 1
430
+ if (this.slots[i] === null) count += 1
263
431
  }
264
432
  return count
265
433
  }
@@ -268,13 +436,6 @@ module.exports = (Item) => {
268
436
  return this.requiresConfirmation
269
437
  }
270
438
 
271
- acceptCraftingClick (click) {
272
- assert.strictEqual(click.mouseButton, 0)
273
- assert.strictEqual(click.mode, 0)
274
- assert.strictEqual(this.selectedItem, null)
275
- return this.acceptNonInventorySwapAreaClick(click)
276
- }
277
-
278
439
  clear (blockId, count) {
279
440
  let clearedCount = 0
280
441
 
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "prismarine-windows",
3
- "version": "2.6.1",
3
+ "version": "2.8.0",
4
4
  "description": "Represent minecraft windows",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "npm run lint",
7
+ "mocha_test": "mocha --reporter spec --exit",
8
+ "test": "npm run mocha_test",
9
+ "pretest": "npm run lint",
8
10
  "lint": "standard",
9
11
  "fix": "standard --fix"
10
12
  },
@@ -13,13 +15,14 @@
13
15
  "url": "git+ssh://git@github.com/PrismarineJS/prismarine-windows.git"
14
16
  },
15
17
  "dependencies": {
16
- "prismarine-registry": "^1.5.0",
17
- "prismarine-item": "^1.5.0",
18
- "typed-emitter": "^1.0.0"
18
+ "prismarine-item": "^1.12.2",
19
+ "prismarine-registry": "^1.7.0",
20
+ "typed-emitter": "^2.1.0"
19
21
  },
20
22
  "devDependencies": {
21
- "@types/node": "^18.6.4",
22
- "standard": "^17.0.0"
23
+ "@types/node": "^20.2.1",
24
+ "standard": "^17.0.0",
25
+ "mocha": "^10.0.0"
23
26
  },
24
27
  "keywords": [
25
28
  "minecraft",
package/test/test.js ADDED
@@ -0,0 +1,367 @@
1
+ /* eslint-env mocha */
2
+
3
+ const { describe, it, afterEach } = require('mocha')
4
+ const assert = require('assert')
5
+
6
+ const mcVersion = '1.8'
7
+ const registry = require('prismarine-registry')(mcVersion)
8
+ const windows = require('..')(registry)
9
+ const Item = require('prismarine-item')(registry)
10
+
11
+ function getAssertFunctions (slot) {
12
+ return {
13
+ isEmpty: function () {
14
+ assert.equal(slot, null, 'slot is not empty')
15
+ return this
16
+ },
17
+ isNotEmpty: function () {
18
+ assert.notEqual(slot, null, 'slot is empty')
19
+ return this
20
+ },
21
+ hasCount: function (count) {
22
+ this.isNotEmpty()
23
+ assert.equal(slot.count, count, 'slot count doesn\'t match')
24
+ return this
25
+ },
26
+ hasMaxCount: function () {
27
+ this.isNotEmpty()
28
+ assert.equal(slot.count, slot.stackSize, 'slot doesn\'t have max count')
29
+ return this
30
+ },
31
+ hasType: function (type) {
32
+ this.isNotEmpty()
33
+ assert.equal(slot.type, type, `slot doesn't have type ${type}`)
34
+ return this
35
+ }
36
+ }
37
+ }
38
+
39
+ function getActualSlot (slotShorthand, inventoryEnd) {
40
+ // negative values start from the end of the inventory
41
+ return slotShorthand < 0 ? inventoryEnd + slotShorthand : slotShorthand
42
+ }
43
+
44
+ function getSlotShorthand (actualSlot, inventoryEnd) {
45
+ const negativeSlotShorthand = actualSlot - inventoryEnd
46
+ return Math.abs(negativeSlotShorthand) < actualSlot ? negativeSlotShorthand : actualSlot
47
+ }
48
+
49
+ function createTestWindow (type, slotCount = undefined) {
50
+ const testWindow = windows.createWindow(1, 'minecraft:' + type, type, slotCount ?? (type === 'chest' ? 27 : undefined))
51
+
52
+ testWindow.prepareSlot = function (slotShorthand, count, type) {
53
+ testWindow.updateSlot(getActualSlot(slotShorthand, testWindow.inventoryEnd), new Item(type, count))
54
+
55
+ return testWindow
56
+ }
57
+
58
+ testWindow.prepareSelectedItem = function (count, type) {
59
+ testWindow.selectedItem = new Item(type, count)
60
+
61
+ return testWindow
62
+ }
63
+
64
+ testWindow.executeClick = function (mode, mouseButton, slotShorthand, gamemode) {
65
+ const slot = getActualSlot(slotShorthand, testWindow.inventoryEnd)
66
+ const click = {
67
+ slot,
68
+ mouseButton,
69
+ mode,
70
+ windowId: testWindow.id,
71
+ item: slot === -999 ? null : testWindow.slots[slot]
72
+ }
73
+
74
+ testWindow.updatedSlots = []
75
+ testWindow.assertedSlots = []
76
+ const onSlotUpdate = (slot) => {
77
+ slot = +slot
78
+ if (!testWindow.updatedSlots.includes(slot)) {
79
+ testWindow.updatedSlots.push(slot)
80
+ }
81
+ }
82
+
83
+ testWindow.on('updateSlot', onSlotUpdate)
84
+
85
+ const changedSlots = testWindow.acceptClick(click, gamemode)
86
+
87
+ testWindow.removeListener('updateSlot', onSlotUpdate)
88
+
89
+ return changedSlots
90
+ }
91
+
92
+ testWindow.assertSlot = function (slotShorthand) {
93
+ let slot = getActualSlot(slotShorthand, testWindow.inventoryEnd)
94
+ if (!testWindow.assertedSlots.includes(slot)) {
95
+ testWindow.assertedSlots.push(slot)
96
+ }
97
+ slot = testWindow.slots[slot]
98
+ return getAssertFunctions(slot)
99
+ }
100
+
101
+ testWindow.assertSelectedItem = function () {
102
+ return getAssertFunctions(testWindow.selectedItem)
103
+ }
104
+
105
+ return testWindow
106
+ }
107
+
108
+ // item ids
109
+ const firstItem = 1
110
+ const secondItem = 2
111
+
112
+ let testWindow = null
113
+
114
+ afterEach(function () {
115
+ testWindow.updatedSlots.forEach((slot) => {
116
+ if (!testWindow.assertedSlots.includes(slot)) {
117
+ const slotShorthand = getSlotShorthand(slot, testWindow.inventoryEnd)
118
+ assert.fail(`slot ${slotShorthand}${slotShorthand !== slot ? ` (actual: ${slot})` : ''} updated, but has not been asserted`)
119
+ }
120
+ })
121
+ })
122
+
123
+ describe('mode 0 | normal click', () => {
124
+ describe('mouseButton 0', () => {
125
+ it('pickup item', () => {
126
+ testWindow = createTestWindow('chest')
127
+ .prepareSlot(0, 64, firstItem)
128
+
129
+ testWindow.executeClick(0, 0, 0)
130
+
131
+ testWindow.assertSlot(0).isEmpty()
132
+ testWindow.assertSelectedItem().hasCount(64).hasType(firstItem)
133
+ })
134
+ })
135
+
136
+ describe('mouseButton 1', () => {
137
+ it('drop one of selected Item into a slot (same item)', () => {
138
+ testWindow = createTestWindow('chest')
139
+ .prepareSelectedItem(64, firstItem)
140
+ .prepareSlot(0, 1, firstItem)
141
+
142
+ testWindow.executeClick(0, 1, 0)
143
+
144
+ testWindow.assertSelectedItem().hasCount(63)
145
+ testWindow.assertSlot(0).hasCount(2)
146
+ })
147
+
148
+ it('drop one of selected Item into a slot (empty slot)', () => {
149
+ testWindow = createTestWindow('chest')
150
+ .prepareSelectedItem(64, firstItem)
151
+
152
+ testWindow.executeClick(0, 1, 0)
153
+
154
+ testWindow.assertSelectedItem().hasCount(63)
155
+ testWindow.assertSlot(0).hasCount(1)
156
+ })
157
+
158
+ it('drop selected Item into a slot (empty slot)', () => {
159
+ testWindow = createTestWindow('chest')
160
+ .prepareSelectedItem(1, firstItem)
161
+
162
+ testWindow.executeClick(0, 1, 0)
163
+
164
+ testWindow.assertSelectedItem().isEmpty()
165
+ testWindow.assertSlot(0).hasCount(1)
166
+ })
167
+ })
168
+
169
+ describe('mouseButton 0', () => {
170
+ it('drop all of selected Item into a slot (almost full with same item)', () => {
171
+ testWindow = createTestWindow('chest')
172
+ .prepareSelectedItem(64, firstItem)
173
+ .prepareSlot(0, 60, firstItem)
174
+
175
+ testWindow.executeClick(0, 0, 0)
176
+
177
+ testWindow.assertSelectedItem().hasCount(60)
178
+ testWindow.assertSlot(0).hasCount(64)
179
+ })
180
+
181
+ it('drop selected Item into empty slot', () => {
182
+ testWindow = createTestWindow('chest')
183
+ .prepareSelectedItem(1, firstItem)
184
+
185
+ testWindow.executeClick(0, 0, 0)
186
+
187
+ testWindow.assertSelectedItem().isEmpty()
188
+ testWindow.assertSlot(0).isNotEmpty().hasCount(1).hasType(firstItem)
189
+ })
190
+ })
191
+ })
192
+
193
+ describe('mode 1 | shift click', () => {
194
+ describe('mouseButton 0', () => {
195
+ it('shift out of chest into inventory', () => {
196
+ testWindow = createTestWindow('chest')
197
+ .prepareSlot(0, 64, firstItem)
198
+
199
+ testWindow.executeClick(1, 0, 0)
200
+
201
+ testWindow.assertSlot(0).isEmpty()
202
+ testWindow.assertSlot(-1).isNotEmpty().hasCount(64).hasType(firstItem)
203
+ })
204
+
205
+ it('shift out of inventory into chest', () => {
206
+ testWindow = createTestWindow('chest')
207
+ .prepareSlot(-1, 64, firstItem)
208
+
209
+ testWindow.executeClick(1, 0, -1)
210
+
211
+ testWindow.assertSlot(0).isNotEmpty().hasCount(64).hasType(firstItem)
212
+ testWindow.assertSlot(-1).isEmpty()
213
+ })
214
+
215
+ it.skip('shift out of inventory into armor slot (unimplemented)', () => {
216
+ const someBoots = registry.itemsByName.leather_boots.id
217
+ testWindow = createTestWindow('inventory')
218
+ .prepareSlot(-1, 1, someBoots)
219
+
220
+ testWindow.executeClick(1, 0, -1)
221
+
222
+ testWindow.assertSlot(-1).isEmpty()
223
+ // 8 is the slot for boots
224
+ testWindow.assertSlot(8).isNotEmpty().hasType(someBoots).hasCount(1)
225
+ })
226
+ })
227
+ })
228
+
229
+ describe('mode 2 | number click', () => {
230
+ describe('mouseButton 0', () => {
231
+ it('from full slot into empty slot', () => {
232
+ testWindow = createTestWindow('chest')
233
+ .prepareSlot(0, 64, firstItem)
234
+
235
+ // mouseButton 0 = hotbarStart
236
+ // slot 0 = windowStart
237
+ testWindow.executeClick(2, 0, 0)
238
+
239
+ testWindow.assertSlot(-9).isNotEmpty().hasCount(64).hasType(firstItem)
240
+ testWindow.assertSlot(0).isEmpty()
241
+ })
242
+
243
+ it('from empty slot into full slot', () => {
244
+ testWindow = createTestWindow('chest')
245
+ // -9 = hotbarStart
246
+ .prepareSlot(-9, 64, firstItem)
247
+
248
+ // mouseButton 0 = hotbarStart
249
+ // slot 0 = windowStart
250
+ testWindow.executeClick(2, 0, 0)
251
+
252
+ testWindow.assertSlot(-9).isEmpty()
253
+ testWindow.assertSlot(0).isNotEmpty().hasCount(64).hasType(firstItem)
254
+ })
255
+
256
+ it('from slot with item to slot with same item', () => {
257
+ testWindow = createTestWindow('chest')
258
+ .prepareSlot(-9, 32, firstItem)
259
+ .prepareSlot(0, 32, firstItem)
260
+
261
+ testWindow.executeClick(2, 0, 0)
262
+
263
+ if (registry.version['>=']('1.9')) {
264
+ // expect nothing to happen
265
+ testWindow.assertSlot(0).hasCount(32)
266
+ testWindow.assertSlot(-9).hasCount(32)
267
+ } else {
268
+ testWindow.assertSlot(0).isEmpty()
269
+ testWindow.assertSlot(-9).isNotEmpty().hasCount(64).hasType(firstItem)
270
+ testWindow.assertSlot(-8).isEmpty()
271
+ }
272
+ })
273
+
274
+ it('from slot with item to slot with different item', () => {
275
+ testWindow = createTestWindow('chest')
276
+ .prepareSlot(0, 64, firstItem)
277
+ .prepareSlot(-1, 64, secondItem)
278
+
279
+ // mouseButton 8 = hotbarEnd
280
+ // slot 0 = windowStart
281
+ testWindow.executeClick(2, 8, 0)
282
+
283
+ if (registry.version['>=']('1.9')) {
284
+ testWindow.assertSlot(0).isNotEmpty().hasCount(64).hasType(secondItem)
285
+ testWindow.assertSlot(-1).isNotEmpty().hasCount(64).hasType(firstItem)
286
+ } else {
287
+ testWindow.assertSlot(0).isEmpty()
288
+ testWindow.assertSlot(-1).isNotEmpty().hasCount(64).hasType(firstItem)
289
+ testWindow.assertSlot(-9).isNotEmpty().hasCount(64).hasType(secondItem)
290
+ }
291
+ })
292
+
293
+ it('same slot click does nothing', () => {
294
+ testWindow = createTestWindow('chest')
295
+ .prepareSlot(62, 64, firstItem)
296
+
297
+ // slot 62 = hotbarEnd
298
+ // mouseButton 8 = hotbarEnd
299
+ // slot 0 = windowStart
300
+ testWindow.executeClick(2, 8, 62)
301
+
302
+ // no asserts, test would fail regardless
303
+ // if something did change
304
+ })
305
+ })
306
+ })
307
+
308
+ describe('mode 3 | middle click', () => {
309
+ describe('mouseButton 2', () => {
310
+ it('get stack into selectedItem (gamemode 0)', () => {
311
+ testWindow = createTestWindow('chest')
312
+ .prepareSlot(0, 1, firstItem)
313
+
314
+ testWindow.executeClick(3, 2, 0)
315
+
316
+ // expect no change
317
+ testWindow.assertSelectedItem().isEmpty()
318
+ })
319
+
320
+ it('get stack into selectedItem (gamemode 1)', () => {
321
+ testWindow = createTestWindow('chest')
322
+ .prepareSlot(0, 1, firstItem)
323
+
324
+ testWindow.executeClick(3, 2, 0, /* gamemode = */1)
325
+
326
+ testWindow.assertSelectedItem().isNotEmpty().hasCount(64).hasType(firstItem)
327
+ })
328
+ })
329
+ })
330
+
331
+ describe('mode 4 | drop click', () => {
332
+ describe('mouseButton 0', () => {
333
+ it('drop 1 of stack', () => {
334
+ testWindow = createTestWindow('chest')
335
+ .prepareSlot(0, 64, firstItem)
336
+
337
+ testWindow.executeClick(4, 0, 0)
338
+
339
+ testWindow.assertSlot(0).isNotEmpty().hasCount(64 - 1).hasType(firstItem)
340
+ })
341
+ })
342
+
343
+ describe('mouseButton 1', () => {
344
+ it('drop full stack', () => {
345
+ testWindow = createTestWindow('chest')
346
+ .prepareSlot(0, 64, firstItem)
347
+
348
+ testWindow.executeClick(4, 1, 0)
349
+
350
+ testWindow.assertSlot(0).isEmpty()
351
+ })
352
+ })
353
+ })
354
+
355
+ it('returning changed slots works', () => {
356
+ testWindow = createTestWindow('chest')
357
+ .prepareSlot(0, 64, firstItem)
358
+
359
+ const changedSlots = testWindow.executeClick(0, 0, 0)
360
+
361
+ testWindow.assertSlot(0).isEmpty()
362
+ testWindow.assertSelectedItem().isNotEmpty()
363
+
364
+ assert.equal(changedSlots.length, 1)
365
+ assert.equal(changedSlots[0], 0)
366
+ // selectedItem isn't included in changedSlots
367
+ })