Skip to content

Commit 5e6d643

Browse files
authored
add functionality for using item of a backpack (#274)
1 parent 0be6657 commit 5e6d643

4 files changed

Lines changed: 167 additions & 47 deletions

File tree

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,8 +727,8 @@ <h1>BACKPACK</h1>
727727
</div>
728728

729729
<div class="footer">
730-
<button id="item-use-btn">Use (enter)</button>
731-
<button id="item-unequip-btn">Unequip (delete)</button>
730+
<button id="item-use-btn">Use (R)</button>
731+
<button id="item-drop-btn">Drop (X)</button>
732732
<button id="inventory-close-btn">Close (I)</button>
733733
</div>
734734
</div>

src/backpack.js

Lines changed: 110 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
1-
import { getGameState, setGameState } from './utils/gameState';
1+
import { getGameState, saveGameState, setGameState } from './utils/gameState';
22
import { k } from './kplayCtx';
3-
3+
import { updateEnergyUI } from './utils/energyUpdate';
44
const backpackBtn = document.getElementById('backpack-btn');
55

66
let isOpened = false;
7+
let selectedItem = null;
8+
9+
function consumeInteraction(energyQty) {
10+
const gameState = getGameState();
11+
gameState.player.energy += energyQty;
12+
setGameState(gameState);
13+
updateEnergyUI(gameState.player.energy);
14+
}
15+
16+
const itemsMap = {
17+
banana: () => consumeInteraction(10),
18+
};
719

820
export class Backpack {
9-
constructor(state, maxSlots = 20) {
21+
constructor(maxSlots = 20) {
1022
this.maxSlots = maxSlots;
11-
this.inventory = state;
23+
}
24+
25+
get inventory() {
26+
const gameState = getGameState();
27+
return gameState.player.backpack.items;
1228
}
1329

1430
getInventorySlotHtml(item) {
15-
return `<div class="inventory-slot">
31+
return `<div class="inventory-slot ${selectedItem === item.itemName ? 'selected' : ''}" data-id="${item.itemName}">
1632
<img src="${item.assetUrl}" with="100%" alt="" />
1733
<span class="quantity-badge">${item.qty || 1}</span>
1834
<div class="tooltip">${item.itemName} ×${item.qty || 1}</div>
@@ -54,11 +70,57 @@ export class Backpack {
5470
);
5571
}
5672
}
73+
this.saveInventoryState(this.inventory);
74+
}
75+
76+
saveInventoryState() {
5777
const newState = getGameState();
5878
newState.player.backpack.items = this.inventory;
5979
setGameState(newState);
6080
}
6181

82+
selectItem(slotEl) {
83+
const itemId = slotEl.getAttribute('data-id');
84+
selectedItem = itemId;
85+
// deselect others
86+
const slots = document.querySelectorAll(
87+
'#backpack-content .inventory-slot:not(.empty)'
88+
);
89+
slots.forEach((item) => {
90+
item.classList.remove('selected');
91+
});
92+
//higlight selected item
93+
slotEl.classList.add('selected');
94+
}
95+
96+
decreaseItemQty(itemId) {
97+
const ind = this.inventory.findIndex(
98+
(item) => item.itemName === itemId
99+
);
100+
101+
if (ind != -1) {
102+
if (this.inventory[ind].qty >= 2) {
103+
--this.inventory[ind].qty;
104+
} else {
105+
//remove item
106+
this.inventory.splice(ind, 1);
107+
selectedItem = null;
108+
}
109+
this.saveInventoryState();
110+
this.render();
111+
}
112+
}
113+
114+
useItem() {
115+
if (!selectedItem) return;
116+
const action = itemsMap[selectedItem];
117+
action?.();
118+
// Decrease the qty
119+
this.decreaseItemQty(selectedItem);
120+
}
121+
122+
dropItem() {}
123+
62124
render() {
63125
const backpack = document.getElementById('backpack-content');
64126
backpack.style.display = 'flex';
@@ -79,10 +141,7 @@ export class Backpack {
79141
static getInstance() {
80142
const gameState = getGameState();
81143
if (gameState.player.backpack) {
82-
return new Backpack(
83-
gameState.player.backpack.items,
84-
gameState.player.backpack.maxSlots
85-
);
144+
return new Backpack(gameState.player.backpack.maxSlots);
86145
}
87146
return false;
88147
}
@@ -99,46 +158,72 @@ export class Backpack {
99158
items: [],
100159
maxSlots: 20,
101160
};
161+
saveGameState(gameState);
102162
}
103163

104-
handleCloseBackpack() {
105-
if (!k.isFocused()) {
106-
k.pressButton('backpack');
107-
k.canvas.focus();
164+
handleDocumentKeypress(e) {
165+
if (e.key == 'i') {
166+
//workaround for press backpack key if canvas lose the focus
167+
if (!k.isFocused()) {
168+
k.canvas.focus();
169+
k.pressButton('backpack');
170+
}
171+
} else if (e.key == 'r') {
172+
this.useItem();
108173
}
109174
}
110175

111176
openBackpack() {
112177
this.render();
113-
document.addEventListener('keypress', this.handleCloseBackpack);
114-
const inventoryCloseBtn = document.getElementById(
115-
'inventory-close-btn'
178+
document.addEventListener(
179+
'keypress',
180+
this.handleDocumentKeypress.bind(this)
116181
);
117-
inventoryCloseBtn.addEventListener('click', this.handleCloseBackpack);
118182
isOpened = true;
119183
}
120184

121185
hideBackpack() {
122186
this.hide();
123-
document.removeEventListener('keypress', this.handleCloseBackpack);
124-
const inventoryCloseBtn = document.getElementById(
125-
'inventory-close-btn'
126-
);
127-
inventoryCloseBtn.removeEventListener(
128-
'click',
129-
this.handleCloseBackpack
187+
document.removeEventListener(
188+
'keypress',
189+
this.handleDocumentKeypress.bind(this)
130190
);
131191
isOpened = false;
132192
}
133193

134194
static init() {
135195
const instance = Backpack.getInstance();
136196
if (instance) {
137-
backpackBtn.style.display = 'block';
197+
Backpack.showButton();
138198

139199
backpackBtn.addEventListener('click', () => {
140200
instance.openBackpack();
141201
});
202+
203+
const inventoryCloseBtn = document.getElementById(
204+
'inventory-close-btn'
205+
);
206+
const useItem = document.getElementById('item-use-btn');
207+
const dropItem = document.getElementById('item-drop-btn');
208+
inventoryCloseBtn.addEventListener(
209+
'click',
210+
instance.hideBackpack.bind(instance)
211+
);
212+
useItem.addEventListener('click', instance.useItem.bind(instance));
213+
dropItem.addEventListener(
214+
'click',
215+
instance.dropItem.bind(instance)
216+
);
217+
218+
document
219+
.getElementById('backpack-content')
220+
.addEventListener('click', (e) => {
221+
const slot = e.target.closest(
222+
'.inventory-slot:not(.empty)'
223+
);
224+
if (!slot) return;
225+
instance.selectItem(slot);
226+
});
142227
}
143228
}
144229

@@ -157,11 +242,6 @@ export class Backpack {
157242

158243
static showButton() {
159244
backpackBtn.style.display = 'block';
160-
161-
const instance = Backpack.getInstance();
162-
backpackBtn.addEventListener('click', () => {
163-
instance.openBackpack();
164-
});
165245
}
166246

167247
static removeButton() {

src/interactions/map_campus_house_1/backpack.interaction.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ export const backpackInteractions = (player, k, map) => {
1414
const dialog = [];
1515

1616
if (wantBackpack) {
17-
Backpack.initState();
1817
k.destroy(backpack);
1918
// show backpack on side right panel
20-
Backpack.showButton();
19+
Backpack.initState();
20+
Backpack.init();
2121

2222
dialog.push('Aye, a trusty pack for a worthy traveler.');
2323
} else {

src/styles/backpack.css

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@
4747

4848
.backpack-frame .backpack-container {
4949
padding: 0 16px 16px 16px;
50-
max-height: 300px;
51-
overflow: auto;
5250
}
5351

5452
.backpack-frame .backpack-graphic {
@@ -92,6 +90,11 @@
9290
position: relative;
9391
}
9492

93+
.backpack-frame .inventory-slot.selected,
94+
.backpack-frame .inventory-slot.selected:hover {
95+
background: #48d387;
96+
}
97+
9598
.backpack-frame .inventory-slot:hover {
9699
background: #2d7a4f;
97100
transform: translateY(-2px);
@@ -202,26 +205,63 @@
202205
cursor: pointer;
203206
}
204207

205-
@media (max-width: 600px) {
208+
/* Adding responsive CSS media queries for mobile devices */
209+
@media (max-width: 768px) {
210+
#backpack-content .backpack-frame {
211+
width: 90vw;
212+
max-width: 400px;
213+
}
214+
206215
.backpack-frame .inventory-grid {
207-
grid-template-columns: repeat(4, 1fr);
208-
gap: 8px;
216+
grid-template-columns: repeat(5, 1fr);
217+
gap: 6px;
218+
}
219+
}
220+
221+
@media (max-width: 480px) {
222+
#backpack-content .backpack-frame {
223+
width: 70vw;
224+
border: 6px solid #2a1a42;
209225
}
210226

211-
.backpack-frame .title-bar h1 {
212-
font-size: 16px;
213-
letter-spacing: 2px;
227+
.backpack-frame .title-bar {
228+
font-size: 14px;
229+
padding: 8px 10px;
230+
letter-spacing: 0.5px;
214231
}
215232

216233
.backpack-frame .backpack-container {
217-
max-height: 180px;
234+
padding: 12px;
235+
}
236+
237+
.backpack-frame .inventory-grid {
238+
grid-template-columns: repeat(4, 1fr);
239+
gap: 5px;
240+
margin-bottom: 12px;
218241
}
219242

220-
.backpack-frame .inventory-slot {
221-
font-size: 24px;
243+
.backpack-frame .backpack-flap {
244+
height: 10px;
245+
margin-bottom: 8px;
222246
}
247+
}
223248

224-
.backpack-frame .footer button {
225-
font-size: 10px;
249+
/* Adding landscape mobile optimization */
250+
@media (max-height: 800px) {
251+
#backpack-content .backpack-frame {
252+
width: 85vw;
253+
max-width: 600px;
254+
margin: 10px auto;
255+
}
256+
257+
.backpack-frame .backpack-flap {
258+
height: 12px;
259+
margin-bottom: 6px;
260+
}
261+
262+
.backpack-frame .inventory-grid {
263+
grid-template-columns: repeat(10, 1fr);
264+
gap: 6px;
265+
margin-bottom: 8px;
226266
}
227267
}

0 commit comments

Comments
 (0)