prismarine-windows 2.6.0 → 2.7.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,18 @@
1
+ ### 2.7.0
2
+ * [More click modes (with tests!) (#74)](https://github.com/PrismarineJS/prismarine-windows/commit/9f19fb357b2a96e09060fa5c2393b9041cc869fe) (thanks @kaduvert)
3
+ * [Add command gh workflow allowing to use release command in comments (#98)](https://github.com/PrismarineJS/prismarine-windows/commit/6ddc90092d63f3838136d83e583d7c1d7ace3ae2) (thanks @rom1504)
4
+ * [fix](https://github.com/PrismarineJS/prismarine-windows/commit/8ab955e465ce9586bfba0286ef1340b86dc914e4) (thanks @rom1504)
5
+ * [Update to node 18.0.0 (#96)](https://github.com/PrismarineJS/prismarine-windows/commit/98912036737aa4c2e2f743b12bdb66eac5073889) (thanks @rom1504)
6
+ * [Bump @types/node from 18.16.13 to 20.2.1 (#95)](https://github.com/PrismarineJS/prismarine-windows/commit/cf7401e3caf1ff2a5f582338b3815eb1f80187b2) (thanks @dependabot[bot])
7
+ * [Fix slot layout for dispencer and droppers (#86)](https://github.com/PrismarineJS/prismarine-windows/commit/b42576a0e3eac7b42c843fa96c80191218efe97a) (thanks @IceTank)
8
+ * [Change slots from Array<Item> to Array<Item | null> (#85)](https://github.com/PrismarineJS/prismarine-windows/commit/652cd992b27254dcc7173e93ec8a112fd56cb459) (thanks @yurei-dll)
9
+ * [use registry.supportFeature (#82)](https://github.com/PrismarineJS/prismarine-windows/commit/3d4514ab56274cfe1fc5d578e3e101d6e41391f7) (thanks @Epirito)
10
+ * [Bump typed-emitter from 1.4.0 to 2.1.0 (#76)](https://github.com/PrismarineJS/prismarine-windows/commit/86e6aa0727c7e6a4ffa0602bf1aba7fd9d70f71f) (thanks @dependabot[bot])
11
+
12
+ ## 2.6.1
13
+
14
+ * fix typo
15
+
1
16
  ## 2.6.0
2
17
 
3
18
  * mcData to registry refactoring + anticipation of feature check refactoring
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
- 'minecraft:inventory': { type: 'minecraft:inventory', inventory: { start: 9, end: 44 }, slots: inventorySlots ? 46 : 45, craft: 0, requireConfirmation: true },
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,224 @@ module.exports = (Item) => {
22
22
  this.selectedItem = null
23
23
  }
24
24
 
25
- acceptClick (click) {
26
- assert.ok(click.mouseButton === 0 || click.mouseButton === 1)
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
+ // can not use structuredClone because of
35
+ // potentially incompatible node versions
36
+ const oldSlots = JSON.parse(JSON.stringify(this.slots))
37
+
38
+ switch (click.mode) {
39
+ case 0:
40
+ assert.ok(mouseButton <= 1, 'invalid operation')
41
+ this.mouseClick(click)
42
+ break
43
+
44
+ case 1:
45
+ assert.ok(mouseButton <= 1, 'invalid operation')
46
+ this.shiftClick(click)
47
+ break
48
+
49
+ case 2:
50
+ assert.ok(mouseButton <= 8, 'invalid operation')
51
+ this.numberClick(click)
52
+ break
53
+
54
+ case 3:
55
+ assert.ok(mouseButton === 2, 'invalid operation')
56
+ this.middleClick(click, gamemode)
57
+ break
58
+
59
+ case 4:
60
+ assert.ok(mouseButton <= 1, 'invalid operation')
61
+ this.dropClick(click)
62
+ break
63
+
64
+ case 5:
65
+ assert.ok([1, 5, 9, 2, 6, 10].includes(mouseButton), 'invalid operation')
66
+ this.dragClick(click, gamemode)
67
+ break
68
+
69
+ case 6:
70
+ assert.ok(mouseButton === 0, 'invalid operation')
71
+ this.doubleClick(click)
72
+ break
73
+ }
74
+
75
+ // this is required to satisfy mc versions >= 1.17
76
+ return this.getChangedSlotsAsNotch(oldSlots, this.slots)
77
+ }
78
+
79
+ mouseClick (click) {
27
80
  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)
81
+ this.dropSelectedItem(click.mouseButton === 0)
33
82
  } else {
34
- return this.acceptNonInventorySwapAreaClick(click)
83
+ let { item } = click
84
+ if (click.mouseButton === 0) { // left click
85
+ if (item && this.selectedItem) {
86
+ if (Item.equal(item, this.selectedItem, false)) {
87
+ if (click.slot === this.craftingResultSlot) {
88
+ if (item.count + this.selectedItem.count > item.stackSize) {
89
+ this.selectedItem.count += item.count
90
+ this.updateSlot(item.slot, null)
91
+ }
92
+ } else {
93
+ this.fillSlotWithSelectedItem(item, true)
94
+ }
95
+ } else {
96
+ this.swapSelectedItem(click.slot, item)
97
+ }
98
+ } else if (this.selectedItem || item) {
99
+ this.swapSelectedItem(click.slot, item)
100
+ }
101
+ } else if (click.mouseButton === 1) { // right click
102
+ if (this.selectedItem) {
103
+ if (item) {
104
+ if (Item.equal(item, this.selectedItem, false)) {
105
+ this.fillSlotWithSelectedItem(item, false)
106
+ } else {
107
+ this.swapSelectedItem(click.slot, item)
108
+ }
109
+ } else {
110
+ item = new Item(this.selectedItem.type, 0, this.selectedItem.metadata, this.selectedItem.nbt)
111
+ this.updateSlot(click.slot, item)
112
+ this.fillSlotWithSelectedItem(item, false)
113
+ }
114
+ } else if (item) {
115
+ if (click.slot !== this.craftingResultSlot) {
116
+ this.splitSlot(item)
117
+ } else {
118
+ this.swapSelectedItem(click.slot, item)
119
+ }
120
+ }
121
+ }
35
122
  }
36
123
  }
37
124
 
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
125
+ shiftClick (click) {
126
+ const { item } = click
127
+ if (!item) return
128
+ if (this.type === 'minecraft:inventory') {
129
+ if (click.slot < this.inventoryStart) {
130
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd, click.slot === this.craftingResultSlot)
131
+ } else {
132
+ if (click.slot >= this.inventoryStart && click.slot < this.inventoryEnd - 10) {
133
+ this.fillAndDump(item, this.hotbarStart, this.inventoryEnd)
134
+ } else {
135
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd)
136
+ }
137
+ }
45
138
  } else {
46
- assert.ok(false, 'unimplemented')
139
+ if (click.slot < this.inventoryStart) {
140
+ this.fillAndDump(item, this.inventoryStart, this.inventoryEnd, this.craftingResultSlot === -1 || click.slot === this.craftingResultSlot)
141
+ } else {
142
+ this.fillAndDump(item, 0, this.inventoryStart - 1)
143
+ }
47
144
  }
48
- return []
49
145
  }
50
146
 
51
- acceptInventoryClick (click) {
52
- if (click.mouseButton === 0) {
53
- if (click.mode > 0) {
54
- assert.ok(false, 'unimplemented')
147
+ numberClick (click) {
148
+ if (this.selectedItem) return
149
+ const { item } = click
150
+ const hotbarSlot = this.hotbarStart + click.mouseButton
151
+ const itemAtHotbarSlot = this.slots[hotbarSlot]
152
+ if (item) {
153
+ if (itemAtHotbarSlot) {
154
+ if (this.type === 'minecraft:inventory' || registry.version['>=']('1.9')) {
155
+ this.updateSlot(click.slot, itemAtHotbarSlot)
156
+ this.updateSlot(hotbarSlot, item)
157
+ } else {
158
+ this.dumpItem(itemAtHotbarSlot, this.hotbarStart, this.inventoryEnd)
159
+ if (this.slots[hotbarSlot]) {
160
+ this.dumpItem(itemAtHotbarSlot, this.inventoryStart, this.hotbarStart - 1)
161
+ }
162
+ if (this.slots[hotbarSlot] === null) {
163
+ this.updateSlot(item.slot, null)
164
+ this.updateSlot(hotbarSlot, item)
165
+ let slots = this.findItemsRange(this.hotbarStart, this.inventoryEnd, itemAtHotbarSlot.type, itemAtHotbarSlot.metadata, true, itemAtHotbarSlot.nbt)
166
+ slots.push(...this.findItemsRange(this.inventoryStart, this.hotbarStart - 1, itemAtHotbarSlot.type, itemAtHotbarSlot.metadata, true, itemAtHotbarSlot.nbt))
167
+ slots = slots.filter(slot => slot.slot !== itemAtHotbarSlot.slot)
168
+ this.fillSlotsWithItem(slots, itemAtHotbarSlot)
169
+ }
170
+ }
55
171
  } else {
56
- return this.acceptSwapAreaLeftClick(click)
172
+ this.updateSlot(item.slot, null)
173
+ this.updateSlot(hotbarSlot, item)
57
174
  }
58
- } else if (click.mouseButton === 1) {
59
- return this.acceptSwapAreaRightClick(click)
60
- } else {
61
- assert.ok(false, 'unimplemented')
175
+ } else if (itemAtHotbarSlot && click.slot !== this.craftingResultSlot) {
176
+ this.updateSlot(click.slot, itemAtHotbarSlot)
177
+ this.updateSlot(hotbarSlot, null)
62
178
  }
63
179
  }
64
180
 
65
- acceptNonInventorySwapAreaClick (click) {
66
- assert.strictEqual(click.mode, 0, 'unimplemented')
181
+ middleClick (click, gamemode) {
182
+ if (this.selectedItem) return
183
+ const { item } = click
184
+ if (gamemode === 1 && item) {
185
+ this.selectedItem = new Item(item.type, item.stackSize, item.metadata, item.nbt)
186
+ }
187
+ }
188
+
189
+ dropClick (click) {
190
+ if (this.selectedItem) return
67
191
  if (click.mouseButton === 0) {
68
- return this.acceptSwapAreaLeftClick(click)
192
+ if (--click.item.count === 0) this.updateSlot(click.slot, null)
69
193
  } else if (click.mouseButton === 1) {
70
- return this.acceptSwapAreaRightClick(click)
71
- } else {
72
- assert.ok(false, 'unimplemented')
194
+ this.updateSlot(click.slot, null)
73
195
  }
74
196
  }
75
197
 
76
- acceptSwapAreaRightClick (click) {
77
- assert.strictEqual(click.mouseButton, 1)
78
- assert.strictEqual(click.mode, 0)
198
+ dragClick (click, gamemode) {
199
+ // unimplemented
200
+ assert.ok(false, 'unimplemented')
201
+ }
79
202
 
80
- 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
88
- } else {
89
- // swap selected item and window item
90
- this.updateSlot(click.slot, this.selectedItem)
91
- this.selectedItem = item
92
- }
93
- } 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
- }
102
- }
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)
203
+ doubleClick (click) {
204
+ // unimplemented
205
+ assert.ok(false, 'unimplemented')
206
+ }
207
+
208
+ acceptOutsideWindowClick = this.acceptClick
209
+ acceptInventoryClick = this.acceptClick
210
+ acceptNonInventorySwapAreaClick = this.acceptClick
211
+ acceptSwapAreaLeftClick = this.acceptClick
212
+ acceptSwapAreaRightClick = this.acceptClick
213
+ acceptCraftingClick = this.acceptClick
214
+
215
+ fillAndDump (item, start, end, lastToFirst = false) {
216
+ this.fillSlotsWithItem(this.findItemsRange(start, end, item.type, item.metadata, true, item.nbt, true), item, lastToFirst)
217
+ if (this.slots[item.slot]) {
218
+ this.dumpItem(item, start, end, lastToFirst)
109
219
  }
110
- return [{
111
- location: click.slot,
112
- item: Item.toNotch(this.slots[click.slot])
113
- }]
114
220
  }
115
221
 
116
- acceptSwapAreaLeftClick (click) {
117
- assert.strictEqual(click.mouseButton, 0)
118
- assert.strictEqual(click.mode, 0)
222
+ fillSlotsWithItem (slots, item, lastToFirst = false) {
223
+ while (slots.length && item.count) {
224
+ this.fillSlotWithItem(lastToFirst ? slots.pop() : slots.shift(), item)
225
+ }
226
+ }
119
227
 
120
- 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
228
+ fillSlotWithItem (itemToFill, itemToTake) {
229
+ const newCount = itemToFill.count + itemToTake.count
230
+ const leftover = newCount - itemToFill.stackSize
231
+ if (leftover <= 0) {
232
+ itemToFill.count = newCount
233
+ itemToTake.count = 0
234
+ this.updateSlot(itemToTake.slot, null)
235
+ } else {
236
+ itemToFill.count = itemToFill.stackSize
237
+ itemToTake.count = leftover
238
+ }
239
+ }
240
+
241
+ fillSlotWithSelectedItem (item, untilFull) {
242
+ if (untilFull) {
125
243
  const newCount = item.count + this.selectedItem.count
126
244
  const leftover = newCount - item.stackSize
127
245
  if (leftover <= 0) {
@@ -132,15 +250,36 @@ module.exports = (Item) => {
132
250
  this.selectedItem.count = leftover
133
251
  }
134
252
  } else {
135
- // swap selected item and window item
136
- const tmp = this.selectedItem
137
- this.selectedItem = item
138
- this.updateSlot(click.slot, tmp)
253
+ if (item.count + 1 <= item.stackSize) {
254
+ item.count++
255
+ if (--this.selectedItem.count === 0) this.selectedItem = null
256
+ }
139
257
  }
140
- return [{
141
- location: click.slot,
142
- item: Item.toNotch(this.slots[click.slot])
143
- }]
258
+ }
259
+
260
+ dumpItem (item, start, end, lastToFirst = false) {
261
+ const emptySlot = lastToFirst ? this.lastEmptySlotRange(start, end) : this.firstEmptySlotRange(start, end)
262
+ if (emptySlot !== null && emptySlot !== this.craftingResultSlot) {
263
+ const slot = item.slot
264
+ this.updateSlot(emptySlot, item)
265
+ this.updateSlot(slot, null)
266
+ }
267
+ }
268
+
269
+ splitSlot (item) {
270
+ if (!item) return
271
+ this.selectedItem = new Item(item.type, Math.ceil(item.count / 2), item.metadata, item.nbt)
272
+ item.count -= this.selectedItem.count
273
+ if (item.count === 0) this.updateSlot(item.slot, null)
274
+ }
275
+
276
+ swapSelectedItem (slot, item) {
277
+ this.updateSlot(slot, this.selectedItem)
278
+ this.selectedItem = item
279
+ }
280
+
281
+ dropSelectedItem (untilEmpty) {
282
+ if (untilEmpty || --this.selectedItem.count === 0) this.selectedItem = null
144
283
  }
145
284
 
146
285
  updateSlot (slot, newItem) {
@@ -152,7 +291,18 @@ module.exports = (Item) => {
152
291
  this.emit(`updateSlot:${slot}`, oldItem, newItem)
153
292
  }
154
293
 
155
- findItemRange (start, end, itemType, metadata, notFull, nbt) {
294
+ findItemsRange (start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot = false) {
295
+ const items = []
296
+ while (start < end) {
297
+ const item = this.findItemRange(start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot)
298
+ if (!item) break
299
+ start = item.slot + 1
300
+ items.push(item)
301
+ }
302
+ return items
303
+ }
304
+
305
+ findItemRange (start, end, itemType, metadata, notFull, nbt, withoutCraftResultSlot = false) {
156
306
  assert.notStrictEqual(itemType, null)
157
307
  for (let i = start; i < end; ++i) {
158
308
  const item = this.slots[i]
@@ -160,7 +310,8 @@ module.exports = (Item) => {
160
310
  item && itemType === item.type &&
161
311
  (metadata == null || metadata === item.metadata) &&
162
312
  (!notFull || item.count < item.stackSize) &&
163
- (nbt == null || JSON.stringify(nbt) === JSON.stringify(item.nbt))) {
313
+ (nbt == null || JSON.stringify(nbt) === JSON.stringify(item.nbt)) &&
314
+ !(item.slot === this.craftingResultSlot && withoutCraftResultSlot)) {
164
315
  return item
165
316
  }
166
317
  }
@@ -196,7 +347,14 @@ module.exports = (Item) => {
196
347
 
197
348
  firstEmptySlotRange (start, end) {
198
349
  for (let i = start; i < end; ++i) {
199
- if (!this.slots[i]) return i
350
+ if (this.slots[i] === null) return i
351
+ }
352
+ return null
353
+ }
354
+
355
+ lastEmptySlotRange (start, end) {
356
+ for (let i = end; i >= start; i--) {
357
+ if (this.slots[i] === null) return i
200
358
  }
201
359
  return null
202
360
  }
@@ -217,6 +375,15 @@ module.exports = (Item) => {
217
375
  return this.firstEmptySlotRange(this.inventoryStart, this.inventoryEnd)
218
376
  }
219
377
 
378
+ sumRange (start, end) {
379
+ let sum = 0
380
+ for (let i = start; i < end; i++) {
381
+ const item = this.slots[i]
382
+ if (item) sum += item.count
383
+ }
384
+ return sum
385
+ }
386
+
220
387
  countRange (start, end, itemType, metadata) {
221
388
  let sum = 0
222
389
  for (let i = start; i < end; ++i) {
@@ -259,7 +426,7 @@ module.exports = (Item) => {
259
426
  emptySlotCount () {
260
427
  let count = 0
261
428
  for (let i = this.inventoryStart; i < this.inventoryEnd; ++i) {
262
- if (!this.slots[i]) count += 1
429
+ if (this.slots[i] === null) count += 1
263
430
  }
264
431
  return count
265
432
  }
@@ -268,11 +435,21 @@ module.exports = (Item) => {
268
435
  return this.requiresConfirmation
269
436
  }
270
437
 
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)
438
+ getChangedSlotsAsNotch (slots1, slots2) {
439
+ assert.equal(slots1.length, slots2.length)
440
+
441
+ const changedSlots = []
442
+
443
+ for (let i = 0; i < slots2.length; i++) {
444
+ if (!Item.equal(slots1[i], slots2[i])) {
445
+ changedSlots.push({
446
+ location: i,
447
+ item: Item.toNotch(slots2[i])
448
+ })
449
+ }
450
+ }
451
+
452
+ return changedSlots
276
453
  }
277
454
 
278
455
  clear (blockId, count) {
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "prismarine-windows",
3
- "version": "2.6.0",
3
+ "version": "2.7.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,356 @@
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
+ })
294
+
295
+ describe('mode 3 | middle click', () => {
296
+ describe('mouseButton 2', () => {
297
+ it('get stack into selectedItem (gamemode 0)', () => {
298
+ testWindow = createTestWindow('chest')
299
+ .prepareSlot(0, 1, firstItem)
300
+
301
+ testWindow.executeClick(3, 2, 0)
302
+
303
+ // expect no change
304
+ testWindow.assertSelectedItem().isEmpty()
305
+ })
306
+
307
+ it('get stack into selectedItem (gamemode 1)', () => {
308
+ testWindow = createTestWindow('chest')
309
+ .prepareSlot(0, 1, firstItem)
310
+
311
+ testWindow.executeClick(3, 2, 0, /* gamemode = */1)
312
+
313
+ testWindow.assertSelectedItem().isNotEmpty().hasCount(64).hasType(firstItem)
314
+ })
315
+ })
316
+ })
317
+
318
+ describe('mode 4 | drop click', () => {
319
+ describe('mouseButton 0', () => {
320
+ it('drop 1 of stack', () => {
321
+ testWindow = createTestWindow('chest')
322
+ .prepareSlot(0, 64, firstItem)
323
+
324
+ testWindow.executeClick(4, 0, 0)
325
+
326
+ testWindow.assertSlot(0).isNotEmpty().hasCount(64 - 1).hasType(firstItem)
327
+ })
328
+ })
329
+
330
+ describe('mouseButton 1', () => {
331
+ it('drop full stack', () => {
332
+ testWindow = createTestWindow('chest')
333
+ .prepareSlot(0, 64, firstItem)
334
+
335
+ testWindow.executeClick(4, 1, 0)
336
+
337
+ testWindow.assertSlot(0).isEmpty()
338
+ })
339
+ })
340
+ })
341
+
342
+ it('returning changed slots works', () => {
343
+ testWindow = createTestWindow('chest')
344
+ .prepareSlot(0, 64, firstItem)
345
+
346
+ const changedSlots = testWindow.executeClick(0, 0, 0)
347
+
348
+ testWindow.assertSlot(0).isEmpty()
349
+ testWindow.assertSelectedItem().isNotEmpty()
350
+
351
+ assert.ok(
352
+ changedSlots.find(changedSlot => changedSlot.location === 0) !== undefined &&
353
+ Item.fromNotch(changedSlots.find(changedSlot => changedSlot.location === 0)?.item) === null
354
+ // selectedItem isn't included in changedSlots
355
+ )
356
+ })