From 9b136d9444c21e2142d5ac499fbc889886b133e2 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 19 Jan 2023 19:12:12 -0800 Subject: [PATCH] make $item more powerful --- LICENSE | 2 +- README.md | 2 +- src/ChatCommands.cc | 35 ++++++++++++++++++----------- src/Lobby.cc | 1 - src/Lobby.hh | 1 - src/ProxyCommands.cc | 46 ++++++++++++++------------------------- src/ProxyServer.cc | 3 +++ src/ProxyServer.hh | 3 ++- src/ReceiveSubcommands.cc | 11 +++------- src/SendCommands.cc | 11 ++++++---- src/SendCommands.hh | 2 ++ src/ServerShell.cc | 17 ++++++++++----- 12 files changed, 68 insertions(+), 66 deletions(-) diff --git a/LICENSE b/LICENSE index c79da966..2c5edb50 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Martin Michelsen +Copyright (c) 2023 Martin Michelsen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index d3ee5a28..30944405 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$warp `: Warps yourself to the given area. * `$next`: Warps yourself to the next area. * `$swa`: Enables or disables switch assist. When enabled, the server will attempt to automatically unlock two-player doors in solo games if you step on both switches sequentially. - * `$item `: Sets the next item to be dropped from an enemy or box. Item codes are 16 hex bytes; at least 2 bytes must be specified, and all unspecified bytes are zeroes. If you are on the proxy server, you must be the game leader and not using Blue Burst for this command to work. On the game server, this command works for all versions, and you do not have to be the game leader. + * `$item ` (or `$i `): Create an item. Item codes are 16 hex bytes; at least 2 bytes must be specified, and all unspecified bytes are zeroes. If you are on the proxy server, you must not be using Blue Burst for this command to work. On the game server, this command works for all versions. * Configuration commands * `$event `: Sets the current holiday event in the current lobby. Holiday events are documented in the "Using $event" item in the information menu. If you're on the proxy server, this applies to all lobbies and games you join, but only you will see the new event - other players will not. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 8951ca3a..46000938 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -336,7 +337,6 @@ static void server_command_cheat(shared_ptr, shared_ptr l, c->options.infinite_tp = false; c->options.switch_assist = false; } - l->next_drop_item = PlayerInventoryItem(); } } @@ -1018,16 +1018,20 @@ static void server_command_item(shared_ptr, shared_ptr l, return; } - l->next_drop_item.clear(); + PlayerInventoryItem item; + item.data.id = l->generate_item_id(c->lobby_client_id); if (data.size() <= 12) { - memcpy(&l->next_drop_item.data.data1, data.data(), data.size()); + memcpy(&item.data.data1, data.data(), data.size()); } else { - memcpy(&l->next_drop_item.data.data1, data.data(), 12); - memcpy(&l->next_drop_item.data.data2, data.data() + 12, data.size() - 12); + memcpy(&item.data.data1, data.data(), 12); + memcpy(&item.data.data2, data.data() + 12, data.size() - 12); } - string name = name_for_item(l->next_drop_item.data, true); - send_text_message(c, u"$C7Next drop:\n" + decode_sjis(name)); + l->add_item(item, c->area, c->x, c->z); + send_drop_stacked_item(l, item.data, c->area, c->x, c->z); + + string name = name_for_item(item.data, true); + send_text_message(c, u"$C7Item created:\n" + decode_sjis(name)); } static void proxy_command_item(shared_ptr, @@ -1058,16 +1062,20 @@ static void proxy_command_item(shared_ptr, return; } - session.next_drop_item.clear(); + PlayerInventoryItem item; + item.data.id = random_object(); if (data.size() <= 12) { - memcpy(&session.next_drop_item.data.data1, data.data(), data.size()); + memcpy(&item.data.data1, data.data(), data.size()); } else { - memcpy(&session.next_drop_item.data.data1, data.data(), 12); - memcpy(&session.next_drop_item.data.data2, data.data() + 12, data.size() - 12); + memcpy(&item.data.data1, data.data(), 12); + memcpy(&item.data.data2, data.data() + 12, data.size() - 12); } - string name = name_for_item(session.next_drop_item.data, true); - send_text_message(session.client_channel, u"$C7Next drop:\n" + decode_sjis(name)); + send_drop_stacked_item(session.client_channel, item.data, session.area, session.x, session.z); + send_drop_stacked_item(session.server_channel, item.data, session.area, session.x, session.z); + + string name = name_for_item(item.data, true); + send_text_message(session.client_channel, u"$C7Item created:\n" + decode_sjis(name)); } @@ -1103,6 +1111,7 @@ static const unordered_map chat_commands({ {u"$infhp", {server_command_infinite_hp, proxy_command_infinite_hp, u"Usage:\ninfhp"}}, {u"$inftp", {server_command_infinite_tp, proxy_command_infinite_tp, u"Usage:\ninftp"}}, {u"$item", {server_command_item, proxy_command_item, u"Usage:\nitem "}}, + {u"$i", {server_command_item, proxy_command_item, u"Usage:\ni "}}, {u"$kick", {server_command_kick, nullptr, u"Usage:\nkick "}}, {u"$li", {server_command_lobby_info, proxy_command_lobby_info, u"Usage:\nli"}}, {u"$maxlevel", {server_command_max_level, nullptr, u"Usage:\nmax_level "}}, diff --git a/src/Lobby.cc b/src/Lobby.cc index b7c73115..f5fb62e8 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -33,7 +33,6 @@ Lobby::Lobby(uint32_t id) for (size_t x = 0; x < 12; x++) { this->next_item_id[x] = 0x00010000 + 0x00200000 * x; } - this->next_drop_item = PlayerInventoryItem(); } void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) { diff --git a/src/Lobby.hh b/src/Lobby.hh index f82858bb..e77af95c 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -62,7 +62,6 @@ struct Lobby : public std::enable_shared_from_this { std::vector enemies; std::array next_item_id; uint32_t next_game_item_id; - PlayerInventoryItem next_drop_item; std::unordered_map item_id_to_floor_item; parray variations; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index ef57db2f..89d02445 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -939,36 +939,6 @@ static HandlerResult S_6x(shared_ptr, session.log.warning("Blocking subcommand 6x49 with invalid count"); return HandlerResult::Type::SUPPRESS; } - } else if ((data[0] == 0x60) && - session.next_drop_item.data.data1d[0] && - (session.version != GameVersion::BB)) { - const auto& cmd = check_size_t(data, - sizeof(G_EnemyDropItemRequest_DC_6x60), - sizeof(G_EnemyDropItemRequest_PC_V3_BB_6x60)); - session.next_drop_item.data.id = session.next_item_id++; - send_drop_item(session.server_channel, session.next_drop_item.data, - true, cmd.area, cmd.x, cmd.z, cmd.request_id); - send_drop_item(session.client_channel, session.next_drop_item.data, - true, cmd.area, cmd.x, cmd.z, cmd.request_id); - session.next_drop_item.clear(); - return HandlerResult::Type::SUPPRESS; - - // Note: This static_cast is required to make compilers not complain that - // the comparison is always false (which even happens in some environments - // if we use -0x5E... apparently char is unsigned on some systems, or - // std::string's char_type isn't char??) - } else if ((static_cast(data[0]) == 0xA2) && - session.next_drop_item.data.data1d[0] && - (session.version != GameVersion::BB)) { - const auto& cmd = check_size_t(data); - session.next_drop_item.data.id = session.next_item_id++; - send_drop_item(session.server_channel, session.next_drop_item.data, - false, cmd.area, cmd.x, cmd.z, cmd.request_id); - send_drop_item(session.client_channel, session.next_drop_item.data, - false, cmd.area, cmd.x, cmd.z, cmd.request_id); - session.next_drop_item.clear(); - return HandlerResult::Type::SUPPRESS; - } else if ((static_cast(data[0]) == 0xB5) && (session.version == GameVersion::GC) && (data.size() > 4)) { @@ -1440,6 +1410,14 @@ constexpr on_command_t C_DGX_81 = &C_81; constexpr on_command_t C_P_81 = &C_81; constexpr on_command_t C_B_81 = &C_81; + +template +void C_6x_movement(ProxyServer::LinkedSession& session, const string& data) { + const auto& cmd = check_size_t(data); + session.x = cmd.x; + session.z = cmd.z; +} + template static HandlerResult C_6x(shared_ptr s, ProxyServer::LinkedSession& session, uint16_t command, uint32_t flag, string& data) { @@ -1464,6 +1442,14 @@ static HandlerResult C_6x(shared_ptr s, send_player_stats_change(session.client_channel, session.lobby_client_id, PlayerStatsChange::ADD_HP, 2550); } + } else if (data[0] == 0x3E) { + C_6x_movement(session, data); + } else if (data[0] == 0x3F) { + C_6x_movement(session, data); + } else if (data[0] == 0x40) { + C_6x_movement(session, data); + } else if (data[0] == 0x42) { + C_6x_movement(session, data); } else if (data[0] == 0x48) { if (session.options.infinite_tp) { send_player_stats_change(session.client_channel, diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 35c5061b..58fd8c62 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -486,6 +486,9 @@ ProxyServer::LinkedSession::LinkedSession( lobby_players(12), lobby_client_id(0), leader_client_id(0), + area(0), + x(0.0), + z(0.0), is_in_game(false) { this->last_switch_enabled_command.header.subcommand = 0; memset(this->prev_server_command_bytes, 0, sizeof(this->prev_server_command_bytes)); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index cc63aeaf..7d143032 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -65,7 +65,6 @@ public: ClientConfigBB newserv_client_config; std::deque should_forward_function_call_return_queue; G_SwitchStateChanged_6x05 last_switch_enabled_command; - PlayerInventoryItem next_drop_item; uint32_t next_item_id; struct LobbyPlayer { @@ -79,6 +78,8 @@ public: size_t lobby_client_id; size_t leader_client_id; uint16_t area; + float x; + float z; bool is_in_game; std::shared_ptr detector_crypt; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5f8fbe7a..3656754e 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -881,13 +881,8 @@ static bool drop_item( PlayerInventoryItem item; - // If there's an override item set (via the $item command), use that item code - if (l->next_drop_item.data.data1d[0]) { - item = l->next_drop_item; - l->next_drop_item.clear(); - // If the game is BB, run the rare + common drop logic - } else if (l->version == GameVersion::BB) { + if (l->version == GameVersion::BB) { if (!l->common_item_creator.get()) { throw runtime_error("received box drop subcommand without item creator present"); } @@ -932,8 +927,8 @@ static bool drop_item( } } - // If the game is not BB and there's no override item, forward the request to - // the leader instead of generating the item drop command + // If the game is not BB, forward the request to the leader instead of + // generating the item drop command } else { return false; } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 829731e8..f9a3d54e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1764,12 +1764,15 @@ void send_drop_item(shared_ptr l, const ItemData& item, send_command_t(l, 0x60, 0x00, cmd); } -// Notifies other players that a stack was split and part of it dropped (a new -// item was created) +void send_drop_stacked_item(Channel& ch, const ItemData& item, + uint8_t area, float x, float z) { + G_DropStackedItem_PC_V3_BB_6x5D cmd = { + {{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0}; + ch.send(0x60, 0x00, &cmd, sizeof(cmd)); +} + void send_drop_stacked_item(shared_ptr l, const ItemData& item, uint8_t area, float x, float z) { - // TODO: Is this order correct? The original code sent {item, 0}, but it seems - // GC sends {0, item} (the last two fields in the struct are switched). G_DropStackedItem_PC_V3_BB_6x5D cmd = { {{0x5D, 0x0A, 0x0000}, area, 0, x, z, item}, 0}; send_command_t(l, 0x60, 0x00, cmd); diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 8f8c0fcf..ba556116 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -298,6 +298,8 @@ void send_drop_item(Channel& ch, const ItemData& item, bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); void send_drop_item(std::shared_ptr l, const ItemData& item, bool from_enemy, uint8_t area, float x, float z, uint16_t request_id); +void send_drop_stacked_item(Channel& ch, const ItemData& item, + uint8_t area, float x, float z); void send_drop_stacked_item(std::shared_ptr l, const ItemData& item, uint8_t area, float x, float z); void send_pick_up_item(std::shared_ptr l, std::shared_ptr c, uint32_t id, diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 704b2725..2d743e19 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -5,6 +5,7 @@ #include #include +#include #include "ReceiveCommands.hh" #include "ServerState.hh" @@ -655,16 +656,20 @@ session with ID 17205AE4, run the command `on 17205AE4 sc 1D 00 04 00`.\n\ throw runtime_error("data too long"); } - session->next_drop_item.clear(); + PlayerInventoryItem item; + item.data.id = random_object(); if (data.size() <= 12) { - memcpy(&session->next_drop_item.data.data1, data.data(), data.size()); + memcpy(&item.data.data1, data.data(), data.size()); } else { - memcpy(&session->next_drop_item.data.data1, data.data(), 12); - memcpy(&session->next_drop_item.data.data2, data.data() + 12, data.size() - 12); + memcpy(&item.data.data1, data.data(), 12); + memcpy(&item.data.data2, data.data() + 12, data.size() - 12); } - string name = name_for_item(session->next_drop_item.data, true); - send_text_message(session->client_channel, u"$C7Next drop:\n" + decode_sjis(name)); + send_drop_stacked_item(session->client_channel, item.data, session->area, session->x, session->z); + send_drop_stacked_item(session->server_channel, item.data, session->area, session->x, session->z); + + string name = name_for_item(item.data, true); + send_text_message(session->client_channel, u"$C7Item created:\n" + decode_sjis(name)); } else if (command_name == "close-idle-sessions") { size_t count = this->state->proxy_server->delete_disconnected_sessions();