Q to drop
This commit is contained in:
@@ -138,6 +138,9 @@ export class Client {
|
|||||||
this.pop_screen();
|
this.pop_screen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (InputManager.is_key_pressed(options.key_drop)) {
|
||||||
|
this.#handle_drop();
|
||||||
|
}
|
||||||
if (InputManager.is_key_pressed(options.key_chat) && !this.screen) {
|
if (InputManager.is_key_pressed(options.key_chat) && !this.screen) {
|
||||||
this.push_screen(new GuiChat(this));
|
this.push_screen(new GuiChat(this));
|
||||||
}
|
}
|
||||||
@@ -165,6 +168,24 @@ export class Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the held item while playing, the slot under the mouse in an inventory (only with nothing on the cursor)
|
||||||
|
#handle_drop() {
|
||||||
|
const all = InputManager.is_key_down("ControlLeft") || InputManager.is_key_down("ControlRight");
|
||||||
|
const inventories = this.player.inventories;
|
||||||
|
const screen = this.screen;
|
||||||
|
if (!screen) {
|
||||||
|
this.game_mode.drop_item(inventories.inventory, "inventory", inventories.hotbar_selected, all);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const slot = screen instanceof GuiInventoryScreen ? screen.hovering : undefined;
|
||||||
|
const container = slot && screen instanceof GuiInventoryScreen
|
||||||
|
? screen.get_container(slot.container)
|
||||||
|
: undefined;
|
||||||
|
if (slot && container && !slot.output && !inventories.cursor.item) {
|
||||||
|
this.game_mode.drop_item(container, slot.container, slot.index, all);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// clicks happen right away, holding to break advances in tick()
|
// clicks happen right away, holding to break advances in tick()
|
||||||
#handle_block_interaction() {
|
#handle_block_interaction() {
|
||||||
const hit = this.hit_result;
|
const hit = this.hit_result;
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { BlockRegistry, EverythingRegistry, ItemRegistry } from "$/common/everyt
|
|||||||
import { AIR, FACE_OFFSETS, TICK_DELTA } from "$/common/constants.ts";
|
import { AIR, FACE_OFFSETS, TICK_DELTA } from "$/common/constants.ts";
|
||||||
import type { Client } from "./client.ts";
|
import type { Client } from "./client.ts";
|
||||||
import type { BlockHitResult } from "./level/client_level.ts";
|
import type { BlockHitResult } from "./level/client_level.ts";
|
||||||
|
import type { Container } from "$/common/inventory.ts";
|
||||||
|
import type { ContainerKey } from "$/common/protocol.ts";
|
||||||
|
|
||||||
// breaking and using blocks against a server, like minecraft's MultiPlayerGameMode. the client times breaking
|
// breaking and using blocks against a server, like minecraft's MultiPlayerGameMode. the client times breaking
|
||||||
// and guesses the results so they feel instant, the server decides what really happens
|
// and guesses the results so they feel instant, the server decides what really happens
|
||||||
@@ -68,6 +70,17 @@ export class MultiPlayerGameMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// q on a slot: throws one, or the whole stack. the server spawns the item, this only takes it out right away
|
||||||
|
drop_item(container: Container, key: ContainerKey, index: number, all: boolean) {
|
||||||
|
const item = container.get_item(index);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.client.connection.send({ type: "drop_item", container: key, index, all });
|
||||||
|
item.amount = all ? 0 : item.amount - 1;
|
||||||
|
container.set_item(index, item.amount > 0 ? item : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
// right click on nothing
|
// right click on nothing
|
||||||
use_item() {
|
use_item() {
|
||||||
this.client.connection.send({ type: "use_item" });
|
this.client.connection.send({ type: "use_item" });
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export class Options {
|
|||||||
key_jump: KeyCode = "Space";
|
key_jump: KeyCode = "Space";
|
||||||
key_sprint: KeyCode = "ShiftLeft";
|
key_sprint: KeyCode = "ShiftLeft";
|
||||||
key_inventory: KeyCode = "KeyE";
|
key_inventory: KeyCode = "KeyE";
|
||||||
|
// with ctrl it drops the whole stack
|
||||||
|
key_drop: KeyCode = "KeyQ";
|
||||||
key_chat: KeyCode = "KeyT";
|
key_chat: KeyCode = "KeyT";
|
||||||
key_debug: KeyCode = "F3";
|
key_debug: KeyCode = "F3";
|
||||||
key_fullscreen: KeyCode = "F11";
|
key_fullscreen: KeyCode = "F11";
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ export type ClientMessage =
|
|||||||
| { type: "use_block"; x: number; y: number; z: number; face: Faces }
|
| { type: "use_block"; x: number; y: number; z: number; face: Faces }
|
||||||
| { type: "select_slot"; slot: number }
|
| { type: "select_slot"; slot: number }
|
||||||
| { type: "click"; container: ContainerKey; index: number; button: number }
|
| { type: "click"; container: ContainerKey; index: number; button: number }
|
||||||
|
// q on a slot (the held one when no screen is open) throws one item, or the whole stack with ctrl
|
||||||
|
| { type: "drop_item"; container: ContainerKey; index: number; all: boolean }
|
||||||
// closes the open screen, including the player's own inventory screen
|
// closes the open screen, including the player's own inventory screen
|
||||||
| { type: "close_screen" }
|
| { type: "close_screen" }
|
||||||
| { type: "chat"; text: string };
|
| { type: "chat"; text: string };
|
||||||
|
|||||||
@@ -310,6 +310,26 @@ export class GameServer {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// out of the player's face the way they're looking, like minecraft's Player.drop
|
||||||
|
throw_item(player: ServerPlayer, stack: ItemStack) {
|
||||||
|
const entity = this.spawn_item(
|
||||||
|
player.x,
|
||||||
|
player.y + PLAYER_EYE_HEIGHT - 0.3,
|
||||||
|
player.z,
|
||||||
|
stack,
|
||||||
|
PLAYER_DROP_PICKUP_DELAY,
|
||||||
|
);
|
||||||
|
// minecraft's numbers are per tick
|
||||||
|
const speed = 0.3 * TICKS_PER_SECOND;
|
||||||
|
const cos_pitch = Math.cos(player.pitch);
|
||||||
|
const spread_angle = Math.random() * Math.PI * 2;
|
||||||
|
const spread = Math.random() * 0.02 * TICKS_PER_SECOND;
|
||||||
|
entity.vx = -Math.sin(player.yaw) * cos_pitch * speed + Math.cos(spread_angle) * spread;
|
||||||
|
entity.vy = Math.sin(player.pitch) * speed + (0.1 + (Math.random() - Math.random()) * 0.1) * TICKS_PER_SECOND;
|
||||||
|
entity.vz = -Math.cos(player.yaw) * cos_pitch * speed + Math.sin(spread_angle) * spread;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
item_entities(): ItemEntity[] {
|
item_entities(): ItemEntity[] {
|
||||||
return [...this.#entities.values()];
|
return [...this.#entities.values()];
|
||||||
}
|
}
|
||||||
@@ -504,6 +524,9 @@ export class GameServer {
|
|||||||
case "click":
|
case "click":
|
||||||
this.#click(player, message.container, message.index, message.button);
|
this.#click(player, message.container, message.index, message.button);
|
||||||
break;
|
break;
|
||||||
|
case "drop_item":
|
||||||
|
this.#drop_item(player, message.container, message.index, message.all === true);
|
||||||
|
break;
|
||||||
case "close_screen":
|
case "close_screen":
|
||||||
this.#close_screen(player);
|
this.#close_screen(player);
|
||||||
break;
|
break;
|
||||||
@@ -645,6 +668,32 @@ export class GameServer {
|
|||||||
this.mods.after.block_place.emit(place);
|
this.mods.after.block_place.emit(place);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#drop_item(player: ServerPlayer, key: unknown, index: unknown, all: boolean) {
|
||||||
|
if (!is_int(index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const container = this.#get_container(player, key as ContainerKey);
|
||||||
|
// the crafting result isn't an item yet, minecraft crafts it first but that's not supported
|
||||||
|
if (
|
||||||
|
!container || index < 0 || index >= container.size || (key === "crafting" && index === CRAFTING_RESULT_SLOT)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = container.get_item(index);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const thrown = item.clone();
|
||||||
|
thrown.amount = all ? item.amount : 1;
|
||||||
|
item.amount -= thrown.amount;
|
||||||
|
container.set_item(index, item.amount > 0 ? item : undefined);
|
||||||
|
if (key === "crafting") {
|
||||||
|
update_crafting_result(container, this.recipes.shaped);
|
||||||
|
}
|
||||||
|
this.throw_item(player, thrown);
|
||||||
|
}
|
||||||
|
|
||||||
#click(player: ServerPlayer, key: unknown, index: unknown, button: unknown) {
|
#click(player: ServerPlayer, key: unknown, index: unknown, button: unknown) {
|
||||||
if (!is_int(index) || !is_int(button)) {
|
if (!is_int(index) || !is_int(button)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -85,3 +85,32 @@ Deno.test("stacks next to each other merge, and items on the ground are saved wi
|
|||||||
const joined = reloaded.join(2, "bob");
|
const joined = reloaded.join(2, "bob");
|
||||||
assertEquals(joined.entities.map((entity: { item: unknown }) => entity.item), [{ id: "bworld:dirt", count: 6 }]);
|
assertEquals(joined.entities.map((entity: { item: unknown }) => entity.item), [{ id: "bworld:dirt", count: 6 }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("q throws one of an item the way the player looks, ctrl throws the whole stack", async () => {
|
||||||
|
const { game, take, send, join } = await test_game("mods");
|
||||||
|
join(1, "alice");
|
||||||
|
build_floor(game);
|
||||||
|
// looking straight along -z (yaw 0)
|
||||||
|
send(1, { type: "move", x: 0.5, y: 100, z: 0.5, yaw: 0, pitch: 0 });
|
||||||
|
send(1, { type: "chat", text: "/give bworld:stone 5" });
|
||||||
|
take(1);
|
||||||
|
|
||||||
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: false });
|
||||||
|
const [thrown] = game.item_entities();
|
||||||
|
assertEquals(thrown.item.amount, 1);
|
||||||
|
assert(thrown.vz < -5 && Math.abs(thrown.vx) < 1, "thrown forwards");
|
||||||
|
assertEquals(inventory_count(take(1), "bworld:stone"), 4);
|
||||||
|
|
||||||
|
for (let i = 0; i < 20; i++) game.tick();
|
||||||
|
assert(thrown.z < -1, "it flew away from the player");
|
||||||
|
assertEquals(game.item_entities().length, 1, "and isn't picked straight back up");
|
||||||
|
|
||||||
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: true });
|
||||||
|
assertEquals(game.item_entities().map((entity) => entity.item.amount).sort(), [1, 4]);
|
||||||
|
assertEquals(inventory_count(take(1), "bworld:stone"), 0);
|
||||||
|
|
||||||
|
// empty slots and the crafting result don't throw anything
|
||||||
|
send(1, { type: "drop_item", container: "inventory", index: 0, all: true });
|
||||||
|
send(1, { type: "drop_item", container: "crafting", index: 9, all: true });
|
||||||
|
assertEquals(game.item_entities().length, 2);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user