feat: shop
This commit is contained in:
parent
512c5a470a
commit
f673833a1d
|
@ -97,21 +97,25 @@ class ItemProxy {
|
|||
get projection() {
|
||||
return this.json.projection;
|
||||
}
|
||||
get price() {
|
||||
return this.json.price;
|
||||
}
|
||||
get qty() {
|
||||
return this.instance.slots[this.slot].qty;
|
||||
}
|
||||
set qty(qty) {
|
||||
const {instance} = this;
|
||||
if (qty <= 0) {
|
||||
this.Component.markChange(instance.entity, 'cleared', {[this.slot]: true});
|
||||
delete instance.slots[this.slot];
|
||||
delete instance.$$items[this.slot];
|
||||
instance.clear(this.slot);
|
||||
}
|
||||
else {
|
||||
instance.slots[this.slot].qty = qty;
|
||||
this.Component.markChange(instance.entity, 'qtyUpdated', {[this.slot]: qty});
|
||||
}
|
||||
}
|
||||
get source() {
|
||||
return this.instance.slots[this.slot].source;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Inventory extends Component {
|
||||
|
@ -169,6 +173,11 @@ export default class Inventory extends Component {
|
|||
const Component = this;
|
||||
return class InventoryInstance extends Instance {
|
||||
$$items = {};
|
||||
clear(slot) {
|
||||
Component.markChange(this.entity, 'cleared', {[slot]: true});
|
||||
delete this.slots[slot];
|
||||
delete this.$$items[slot];
|
||||
}
|
||||
item(slot) {
|
||||
return this.$$items[slot];
|
||||
}
|
||||
|
@ -185,7 +194,7 @@ export default class Inventory extends Component {
|
|||
if (!slots[slot]) {
|
||||
slots[slot] = stack;
|
||||
this.$$items[slot] = new ItemProxy(Component, this, slot);
|
||||
await this.$$items[slot].load();
|
||||
await this.$$items[slot].load(stack.source);
|
||||
Component.markChange(this.entity, 'given', {[slot]: slots[slot]});
|
||||
return;
|
||||
}
|
||||
|
|
3
app/ecs/components/shop.js
Normal file
3
app/ecs/components/shop.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Shop extends Component {}
|
7
app/ecs/components/wallet.js
Normal file
7
app/ecs/components/wallet.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Component from '@/ecs/component.js';
|
||||
|
||||
export default class Wallet extends Component {
|
||||
static properties = {
|
||||
gold: {type: 'uint32'},
|
||||
};
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import {memo} from 'react';
|
||||
|
||||
import styles from './bag.module.css';
|
||||
import gridStyles from './grid.module.css';
|
||||
|
||||
import Grid from './grid.jsx';
|
||||
|
||||
|
@ -8,8 +9,9 @@ import Grid from './grid.jsx';
|
|||
* Inventory bag. 10-40 slots of inventory.
|
||||
*/
|
||||
function Bag({
|
||||
highlighted,
|
||||
isInventoryOpen,
|
||||
onActivate,
|
||||
onSlotMouseDown,
|
||||
slots,
|
||||
}) {
|
||||
return (
|
||||
|
@ -17,11 +19,20 @@ function Bag({
|
|||
className={styles.bag}
|
||||
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}}
|
||||
>
|
||||
<style>{`
|
||||
.${styles.bag} .${gridStyles.highlighted} {
|
||||
background-color: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
.${styles.bag} .${gridStyles.highlighted} button {
|
||||
border: 2.5px dashed rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
`}</style>
|
||||
<Grid
|
||||
color="rgba(02, 02, 28, 0.6)"
|
||||
columns={10}
|
||||
highlighted={highlighted}
|
||||
label="Bag"
|
||||
onActivate={onActivate}
|
||||
onSlotMouseDown={onSlotMouseDown}
|
||||
slots={slots}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {memo} from 'react';
|
||||
|
||||
import styles from './external.module.css';
|
||||
import gridStyles from './grid.module.css';
|
||||
|
||||
import Grid from './grid.jsx';
|
||||
|
||||
|
@ -8,8 +9,9 @@ import Grid from './grid.jsx';
|
|||
* External inventory.
|
||||
*/
|
||||
function External({
|
||||
highlighted,
|
||||
isInventoryOpen,
|
||||
onActivate,
|
||||
onSlotMouseDown,
|
||||
slots,
|
||||
}) {
|
||||
return (
|
||||
|
@ -17,11 +19,20 @@ function External({
|
|||
className={styles.external}
|
||||
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, top: '450px'}}
|
||||
>
|
||||
<style>{`
|
||||
.${styles.external} .${gridStyles.highlighted} {
|
||||
background-color: rgba(0, 255, 0, 0.4);
|
||||
}
|
||||
.${styles.external} .${gridStyles.highlighted} button {
|
||||
border: 2.5px dashed rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
`}</style>
|
||||
<Grid
|
||||
color="rgba(57, 02, 02, 0.6)"
|
||||
columns={10}
|
||||
highlighted={highlighted}
|
||||
label="Chest"
|
||||
onActivate={onActivate}
|
||||
onSlotMouseDown={onSlotMouseDown}
|
||||
slots={slots}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
.external {
|
||||
left: 20px;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 274px;
|
||||
transition: top 150ms, opacity 200ms;
|
||||
--nothing: 0;
|
||||
}
|
||||
|
|
|
@ -8,14 +8,19 @@ export default function Grid({
|
|||
active = -1,
|
||||
color,
|
||||
columns,
|
||||
highlighted,
|
||||
label,
|
||||
onActivate,
|
||||
onSlotMouseDown,
|
||||
slots,
|
||||
}) {
|
||||
const Slots = slots.map((slot, i) => (
|
||||
<div
|
||||
className={
|
||||
[styles.slot, active === i && styles.active]
|
||||
[
|
||||
styles.slot,
|
||||
active === i && styles.active,
|
||||
highlighted && highlighted.includes(i) && styles.highlighted,
|
||||
]
|
||||
.filter(Boolean).join(' ')
|
||||
}
|
||||
key={i}
|
||||
|
@ -23,25 +28,14 @@ export default function Grid({
|
|||
<Slot
|
||||
icon={slot?.icon}
|
||||
onMouseDown={(event) => {
|
||||
onActivate(i)
|
||||
onSlotMouseDown(i)
|
||||
event.stopPropagation();
|
||||
}}
|
||||
onMouseUp={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
event.preventDefault();
|
||||
}}
|
||||
onDragStart={(event) => {
|
||||
if (!slot) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.dataTransfer.setData('silphius/item', i);
|
||||
onActivate(i);
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
event.preventDefault();
|
||||
onActivate(i);
|
||||
}}
|
||||
qty={slot?.qty}
|
||||
/>
|
||||
|
|
|
@ -43,3 +43,7 @@
|
|||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
--nothing: 0;
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@ import Grid from './grid.jsx';
|
|||
*/
|
||||
function Hotbar({
|
||||
active,
|
||||
highlighted,
|
||||
hotbarIsHidden,
|
||||
onActivate,
|
||||
onSlotMouseDown,
|
||||
slots,
|
||||
}) {
|
||||
return (
|
||||
|
@ -23,13 +24,20 @@ function Hotbar({
|
|||
.${styles.hotbar} .${gridStyles.label} {
|
||||
text-align: center;
|
||||
}
|
||||
.${styles.hotbar} .${gridStyles.highlighted} {
|
||||
background-color: rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
.${styles.hotbar} .${gridStyles.highlighted} button {
|
||||
border: 2.5px dashed rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
`}</style>
|
||||
<Grid
|
||||
active={active}
|
||||
color="rgba(02, 02, 57, 0.6)"
|
||||
columns={10}
|
||||
highlighted={highlighted}
|
||||
label={slots[active] && slots[active].label}
|
||||
onActivate={onActivate}
|
||||
onSlotMouseDown={onSlotMouseDown}
|
||||
slots={slots}
|
||||
/>
|
||||
</div>
|
||||
|
|
86
app/react/components/dom/trade.jsx
Normal file
86
app/react/components/dom/trade.jsx
Normal file
|
@ -0,0 +1,86 @@
|
|||
import styles from './trade.module.css';
|
||||
|
||||
function reducePrice(r, item) {
|
||||
return r + item.price * item.qty;
|
||||
}
|
||||
|
||||
function Trade({
|
||||
isInventoryOpen,
|
||||
losing: losingUnfiltered,
|
||||
onTradeAccepted,
|
||||
gaining: gainingUnfiltered,
|
||||
wallet,
|
||||
}) {
|
||||
const losing = losingUnfiltered.filter(Boolean);
|
||||
const gaining = gainingUnfiltered.filter(Boolean);
|
||||
const nop = 0 === losing.length && 0 === gaining.length;
|
||||
let earn = losing.reduce(reducePrice, 0) - gaining.reduce(reducePrice, 0);
|
||||
const canAfford = -earn <= wallet;
|
||||
return (
|
||||
<div
|
||||
className={styles.trade}
|
||||
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, top: '450px'}}
|
||||
>
|
||||
<div className={styles.tradeInner}>
|
||||
{
|
||||
nop
|
||||
? (
|
||||
<div className={styles.nop}>Let's trade!</div>
|
||||
)
|
||||
: (
|
||||
<ul className={styles.summary}>
|
||||
{losing.length > 0 && (
|
||||
<li className={styles.lose}>
|
||||
Shop receives:
|
||||
{' '}
|
||||
<ul className={styles.items}>
|
||||
{losing.map(({label, qty}, i) => (
|
||||
<li key={i}>{label} x<span className={styles.qty}>{qty}</span></li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
{gaining.length > 0 && (
|
||||
<li className={styles.gain}>
|
||||
You receive:
|
||||
{' '}
|
||||
<ul className={styles.items}>
|
||||
{gaining.map(({label, qty}, i) => (
|
||||
<li key={i}>{label} x<span className={styles.qty}>{qty}</span></li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
)}
|
||||
<li className={styles.subtotal}>
|
||||
{
|
||||
earn > 0
|
||||
? (
|
||||
<span className={styles.cost}>You <span className={styles.make}>earn <span className={styles.amount}>{earn}</span></span><span className={styles.gold}>🄶</span></span>
|
||||
)
|
||||
: (
|
||||
earn < 0
|
||||
? <span className={styles.cost}>You <span className={styles.pay}>pay <span className={styles.amount}>{-earn}</span></span><span className={styles.gold}>🄶</span></span>
|
||||
: false
|
||||
)
|
||||
}
|
||||
<button
|
||||
className={styles.agree}
|
||||
disabled={!canAfford}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onTradeAccepted(event);
|
||||
}}
|
||||
title={canAfford ? "Finish the transaction" : `Transaction requires ${-earn}g but you only have ${wallet}g`}
|
||||
>
|
||||
{canAfford ? "Agree" : "Can't afford"}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Trade;
|
129
app/react/components/dom/trade.module.css
Normal file
129
app/react/components/dom/trade.module.css
Normal file
|
@ -0,0 +1,129 @@
|
|||
.trade {
|
||||
border: 2.5px solid #444444;
|
||||
flex-grow: 1;
|
||||
font-size: 0.8em;
|
||||
margin: 16px 40px 0 8px;
|
||||
text-shadow:
|
||||
1px 0 0 black,
|
||||
0 1px 0 black,
|
||||
-1px 0 0 black,
|
||||
0 -1px 0 black
|
||||
;
|
||||
}
|
||||
|
||||
.tradeInner {
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border: 2.5px solid #999999;
|
||||
display: flex;
|
||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
height: 100%;
|
||||
padding: 2.5px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.agree {
|
||||
background-color: rgba(255, 90, 0, 1);
|
||||
border: 2.5px solid #cccccc;
|
||||
box-shadow: inset 0 0 10px #666666;
|
||||
color: white;
|
||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
font-size: 1em;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
bottom: 2.5px;
|
||||
right: 2.5px;
|
||||
text-shadow:
|
||||
1px 0 0 black,
|
||||
0 1px 0 black,
|
||||
-1px 0 0 black,
|
||||
0 -1px 0 black
|
||||
;
|
||||
transition: box-shadow 200ms;
|
||||
&:disabled {
|
||||
background-color: #333333;
|
||||
border-color: #777777;
|
||||
color: #777777;
|
||||
}
|
||||
&:not(:disabled):hover {
|
||||
box-shadow: inset 0 0 10px #222222;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2.5px;
|
||||
width: 100%;
|
||||
> li {
|
||||
padding: 2.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.gain {
|
||||
background-color: rgba(0, 255, 0, 0.5);
|
||||
border: 2.5px solid rgba(0, 255, 0, 0.5);
|
||||
max-height: 40.5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.lose {
|
||||
background-color: rgba(255, 0, 0, 0.5);
|
||||
border: 2.5px solid rgba(255, 0, 0, 0.5);
|
||||
max-height: 40.5px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: inline;
|
||||
li {
|
||||
display: inline;
|
||||
&:not(:last-child):after {
|
||||
content: ', ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.make {
|
||||
color: rgb(0, 255, 0);
|
||||
}
|
||||
|
||||
.pay {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.gold {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.qty {
|
||||
font-family: Joystix;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.cost {
|
||||
position: absolute;
|
||||
bottom: 2.5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 1.5em;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.nop {
|
||||
align-self: center;
|
||||
font-size: 2em;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
17
app/react/components/dom/wallet.jsx
Normal file
17
app/react/components/dom/wallet.jsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import styles from './wallet.module.css';
|
||||
|
||||
function Wallet({gold}) {
|
||||
const goldString = gold.toString();
|
||||
const pad = ''.padStart(7 - goldString.length, '0');
|
||||
return (
|
||||
<span className={styles.wallet}>
|
||||
<span className={styles.number}>
|
||||
<span className={styles.pad}>{pad}</span>
|
||||
<span className={styles.amount}>{goldString}</span>
|
||||
</span>
|
||||
<span className={styles.gold}>🄶</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default Wallet;
|
19
app/react/components/dom/wallet.module.css
Normal file
19
app/react/components/dom/wallet.module.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.wallet {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
text-shadow: 1px 0 0 black, 0 1px 0 black, -1px 0 0 black, 0 -1px 0 black;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.gold {
|
||||
color: gold;
|
||||
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-family: Joystix;
|
||||
}
|
||||
|
||||
.pad {
|
||||
color: #555555;
|
||||
}
|
|
@ -15,6 +15,8 @@ import DateTime from './dom/datetime.jsx';
|
|||
import Dom from './dom/dom.jsx';
|
||||
import Entities from './dom/entities.jsx';
|
||||
import External from './dom/external.jsx';
|
||||
import Trade from './dom/trade.jsx';
|
||||
import Wallet from './dom/wallet.jsx';
|
||||
import HotBar from './dom/hotbar.jsx';
|
||||
import Pixi from './pixi/pixi.jsx';
|
||||
import Devtools from './devtools.jsx';
|
||||
|
@ -62,7 +64,11 @@ function Ui({disconnected}) {
|
|||
const [isInventoryOpen, setIsInventoryOpen] = useState(false);
|
||||
const [externalInventory, setExternalInventory] = useState();
|
||||
const [externalInventorySlots, setExternalInventorySlots] = useState();
|
||||
const [gaining, setGaining] = useState([]);
|
||||
const [losing, setLosing] = useState([]);
|
||||
const [wallet, setWallet] = useState(0);
|
||||
const [particleWorker, setParticleWorker] = useState();
|
||||
const [trading, setTrading] = useState(false);
|
||||
useEffect(() => {
|
||||
let handle;
|
||||
if (disconnected) {
|
||||
|
@ -245,6 +251,9 @@ function Ui({disconnected}) {
|
|||
if (update.MainEntity) {
|
||||
mainEntityRef.current = id;
|
||||
}
|
||||
if (update.Wallet && mainEntityRef.current === id) {
|
||||
setWallet(update.Wallet.gold);
|
||||
}
|
||||
if (update.Inventory) {
|
||||
if (mainEntityRef.current === id) {
|
||||
setBufferSlot(entity.Inventory.item(0));
|
||||
|
@ -267,6 +276,7 @@ function Ui({disconnected}) {
|
|||
newInventorySlots[i] = entity.Inventory.item(i);
|
||||
}
|
||||
setExternalInventory(entity.id)
|
||||
setTrading(!!entity.Shop);
|
||||
setExternalInventorySlots(newInventorySlots);
|
||||
setIsInventoryOpen(true);
|
||||
setHotbarIsHidden(false);
|
||||
|
@ -277,6 +287,9 @@ function Ui({disconnected}) {
|
|||
else if (update.Inventory.closed) {
|
||||
setExternalInventory();
|
||||
setExternalInventorySlots();
|
||||
setGaining([]);
|
||||
setLosing([]);
|
||||
setTrading(false);
|
||||
}
|
||||
}
|
||||
if (mainEntityRef.current === id) {
|
||||
|
@ -371,25 +384,67 @@ function Ui({disconnected}) {
|
|||
mainEntityRef,
|
||||
scale,
|
||||
]);
|
||||
const hotbarOnActivate = useCallback((i) => {
|
||||
const hotbarOnSlotMouseDown = useCallback((i) => {
|
||||
keepHotbarOpen();
|
||||
if (trading) {
|
||||
const index = losing.indexOf(i + 1);
|
||||
if (-1 === index) {
|
||||
losing.push(i + 1);
|
||||
}
|
||||
else {
|
||||
losing.splice(index, 1);
|
||||
}
|
||||
setLosing([...losing]);
|
||||
}
|
||||
else {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 1]},
|
||||
});
|
||||
}
|
||||
}, [client, keepHotbarOpen, losing, mainEntityRef, trading]);
|
||||
const bagOnSlotMouseDown = useCallback((i) => {
|
||||
if (trading) {
|
||||
const index = losing.indexOf(i + 11);
|
||||
if (-1 === index) {
|
||||
losing.push(i + 11);
|
||||
}
|
||||
else {
|
||||
losing.splice(index, 1);
|
||||
}
|
||||
setLosing([...losing]);
|
||||
}
|
||||
else {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 11]},
|
||||
});
|
||||
}
|
||||
}, [client, losing, mainEntityRef, trading]);
|
||||
const externalInventoryOnSlotMouseDown = useCallback((i) => {
|
||||
if (trading) {
|
||||
const index = gaining.indexOf(i);
|
||||
if (-1 === index) {
|
||||
gaining.push(i);
|
||||
}
|
||||
else {
|
||||
gaining.splice(index, 1);
|
||||
}
|
||||
setGaining([...gaining]);
|
||||
}
|
||||
else {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, externalInventory, i]},
|
||||
});
|
||||
}
|
||||
}, [client, externalInventory, gaining, trading]);
|
||||
const onTradeAccepted = useCallback(() => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 1]},
|
||||
payload: {type: 'acceptTrade', value: {gaining, losing}},
|
||||
});
|
||||
}, [client, keepHotbarOpen, mainEntityRef]);
|
||||
const bagOnActivate = useCallback((i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, mainEntityRef.current, i + 11]},
|
||||
});
|
||||
}, [client, mainEntityRef]);
|
||||
const externalInventoryOnActivate = useCallback((i) => {
|
||||
client.send({
|
||||
type: 'Action',
|
||||
payload: {type: 'swapSlots', value: [0, externalInventory, i]},
|
||||
});
|
||||
}, [client, externalInventory]);
|
||||
}, [client, gaining, losing]);
|
||||
useEffect(() => {
|
||||
if (!pixiRef.current) {
|
||||
return;
|
||||
|
@ -510,21 +565,37 @@ function Ui({disconnected}) {
|
|||
<Dom>
|
||||
<HotBar
|
||||
active={activeSlot}
|
||||
highlighted={trading && losing.filter((i) => i < 11).map((i) => i - 1)}
|
||||
hotbarIsHidden={hotbarIsHidden}
|
||||
onActivate={hotbarOnActivate}
|
||||
onSlotMouseDown={hotbarOnSlotMouseDown}
|
||||
slots={hotbarSlots}
|
||||
/>
|
||||
<Bag
|
||||
highlighted={trading && losing.filter((i) => i >= 11).map((i) => i - 11)}
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
onActivate={bagOnActivate}
|
||||
onSlotMouseDown={bagOnSlotMouseDown}
|
||||
slots={inventorySlots}
|
||||
/>
|
||||
{externalInventory && (
|
||||
<External
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
onActivate={externalInventoryOnActivate}
|
||||
slots={externalInventorySlots}
|
||||
/>
|
||||
<div className={styles.external}>
|
||||
<External
|
||||
highlighted={trading && gaining}
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
onSlotMouseDown={externalInventoryOnSlotMouseDown}
|
||||
slots={externalInventorySlots}
|
||||
/>
|
||||
{trading && (
|
||||
<Trade
|
||||
isInventoryOpen={isInventoryOpen}
|
||||
onTradeAccepted={onTradeAccepted}
|
||||
gaining={gaining.map((slot) => externalInventorySlots[slot])}
|
||||
losing={losing.map((slot) => {
|
||||
return slot < 11 ? hotbarSlots[slot - 1] : inventorySlots[slot - 11];
|
||||
})}
|
||||
wallet={wallet}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Entities
|
||||
camera={camera}
|
||||
|
@ -550,6 +621,7 @@ function Ui({disconnected}) {
|
|||
<Disconnected />
|
||||
)}
|
||||
<DateTime />
|
||||
<Wallet gold={wallet} />
|
||||
</Dom>
|
||||
</div>
|
||||
{devtoolsIsOpen && (
|
||||
|
|
|
@ -22,3 +22,13 @@
|
|||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.external {
|
||||
display: flex;
|
||||
left: 20px;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
top: 274px;
|
||||
transition: top 150ms, opacity 200ms;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -152,6 +152,7 @@ export default async function createHomestead(id) {
|
|||
},
|
||||
},
|
||||
Position: {x: 200, y: 200},
|
||||
Shop: {},
|
||||
Sprite: {
|
||||
anchorX: 0.5,
|
||||
anchorY: 0.7,
|
||||
|
|
|
@ -49,6 +49,9 @@ export default async function createPlayer(id) {
|
|||
},
|
||||
Ticking: {},
|
||||
VisibleAabb: {},
|
||||
Wallet: {
|
||||
gold: 1000,
|
||||
},
|
||||
Wielder: {
|
||||
activeSlot: 1,
|
||||
},
|
||||
|
|
|
@ -166,6 +166,8 @@ export default class Engine {
|
|||
Interacts,
|
||||
Interlocutor,
|
||||
Inventory,
|
||||
Player,
|
||||
Wallet,
|
||||
Wielder,
|
||||
} = entity;
|
||||
const ecs = this.ecses[Ecs.path];
|
||||
|
@ -231,6 +233,33 @@ export default class Engine {
|
|||
Controlled[payload.type] = payload.value;
|
||||
break;
|
||||
}
|
||||
case 'acceptTrade': {
|
||||
const {losing, gaining} = payload.value;
|
||||
const gainingSlots = gaining.filter((slot) => Player.openInventory.item(slot));
|
||||
const losingSlots = losing.filter((slot) => Inventory.item(slot));
|
||||
const nop = 0 === gainingSlots.length && 0 === losingSlots.length;
|
||||
const gainingItems = gainingSlots.map((slot) => Player.openInventory.item(slot));
|
||||
const losingItems = losingSlots.map((slot) => Inventory.item(slot));
|
||||
if (nop) {
|
||||
break;
|
||||
}
|
||||
const reducePrice = (r, item) => {
|
||||
return r + item.price * item.qty;
|
||||
};
|
||||
const earn = losingItems.reduce(reducePrice, 0) - gainingItems.reduce(reducePrice, 0);
|
||||
const canAfford = -earn <= Wallet.gold;
|
||||
if (!canAfford) {
|
||||
break;
|
||||
}
|
||||
for (const slot of losingSlots) {
|
||||
Inventory.clear(slot);
|
||||
}
|
||||
for (const item of gainingItems) {
|
||||
Inventory.give({qty: item.qty, source: item.source});
|
||||
}
|
||||
Wallet.gold += earn;
|
||||
break;
|
||||
}
|
||||
case 'swapSlots': {
|
||||
if (!Controlled.locked) {
|
||||
const [l, other, r] = payload.value;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/resources/brush/brush.png",
|
||||
"label": "Brush",
|
||||
"price": 100,
|
||||
"start": "/resources/brush/start.js"
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"icon": "/resources/furball/furball.png",
|
||||
"label": "Fur Ball"
|
||||
"label": "Fur Ball",
|
||||
"price": 5
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"icon": "/resources/hoe/icon.png",
|
||||
"label": "Hoe",
|
||||
"price": 100,
|
||||
"projectionCheck": "/resources/hoe/projection-check.js",
|
||||
"projection": {
|
||||
"distance": [3, -1],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/resources/magic-swords/icon.png",
|
||||
"label": "Magic swords",
|
||||
"price": 2000,
|
||||
"start": "/resources/magic-swords/start.js"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"icon": "/resources/potion/icon.png",
|
||||
"label": "Potion",
|
||||
"price": 50,
|
||||
"start": "/resources/potion/start.js"
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"icon": "/resources/tomato-seeds/icon.png",
|
||||
"label": "Tomato Seeds",
|
||||
"price": 20,
|
||||
"projection": {
|
||||
"distance": [1, -1],
|
||||
"grid": [
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{
|
||||
"icon": "/resources/tomato/tomato.png"
|
||||
"icon": "/resources/tomato/tomato.png",
|
||||
"label": "Tomato",
|
||||
"price": 20
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"icon": "/resources/watering-can/icon.png",
|
||||
"label": "Watering Can",
|
||||
"price": 100,
|
||||
"projectionCheck": "/resources/watering-can/projection-check.js",
|
||||
"projection": {
|
||||
"distance": [3, -1],
|
||||
|
|
|
@ -14,11 +14,11 @@ export default {
|
|||
decorators: [
|
||||
(Hotbar, ctx) => {
|
||||
const [, updateArgs] = useArgs();
|
||||
const {onActivate} = ctx.args;
|
||||
ctx.args.onActivate = (i) => {
|
||||
const {onSlotMouseDown} = ctx.args;
|
||||
ctx.args.onSlotMouseDown = (i) => {
|
||||
updateArgs({active: i});
|
||||
if (onActivate) {
|
||||
onActivate(i);
|
||||
if (onSlotMouseDown) {
|
||||
onSlotMouseDown(i);
|
||||
}
|
||||
};
|
||||
return Hotbar();
|
||||
|
@ -33,7 +33,7 @@ export default {
|
|||
tags: ['autodocs'],
|
||||
args: {
|
||||
active: 0,
|
||||
onActivate: fn(),
|
||||
onSlotMouseDown: fn(),
|
||||
slots,
|
||||
},
|
||||
argTypes: {
|
||||
|
|
Loading…
Reference in New Issue
Block a user