implement rare item pickup notifications

This commit is contained in:
Martin Michelsen
2024-03-28 21:44:05 -07:00
parent dce0f91678
commit dd9bc51457
9 changed files with 102 additions and 23 deletions
+1 -1
View File
@@ -1417,7 +1417,7 @@ struct S_LeaveLobby_66_69_Ep3_E9 {
// 7E: Invalid command
// 7F: Invalid command
// 80: Valid but ignored (all versions)
// 80: Valid but ignored (all versions except BB)
// Internal names: RcvGenerateID and SndGenerateID
// This command appears to be used to set the next item ID for the given player
// slot. PSO V3 and later accept this command, but ignore it entirely. Notably,
+1 -1
View File
@@ -585,7 +585,7 @@ JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared_ptr<co
{"LocationX", item->x},
{"LocationZ", item->z},
{"DropNumber", item->drop_number},
{"VisibilityFlags", item->visibility_flags},
{"Flags", item->flags},
{"Data", item->data.hex()},
{"ItemID", item->data.id.load()},
});
+12 -12
View File
@@ -12,7 +12,7 @@
using namespace std;
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
return this->visibility_flags & (1 << client_id);
return this->flags & (1 << client_id);
}
Lobby::FloorItemManager::FloorItemManager(uint32_t lobby_id, uint8_t floor)
@@ -27,18 +27,18 @@ shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) con
return this->items.at(item_id);
}
void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t visibility_flags) {
void Lobby::FloorItemManager::add(const ItemData& item, float x, float z, uint16_t flags) {
auto fi = make_shared<FloorItem>();
fi->data = item;
fi->x = x;
fi->z = z;
fi->drop_number = this->next_drop_number++;
fi->visibility_flags = visibility_flags & 0x0FFF;
fi->flags = flags;
this->add(fi);
}
void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
if (fi->visibility_flags == 0) {
if (fi->flags == 0) {
throw logic_error("floor item is not visible to any player");
}
@@ -51,8 +51,8 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
this->queue_for_client[z].emplace(fi->drop_number, fi);
}
}
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags);
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
}
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
@@ -70,8 +70,8 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
}
}
this->items.erase(item_it);
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " visible to %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->visibility_flags);
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
return fi;
}
@@ -89,7 +89,7 @@ std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::e
void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) {
unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->visibility_flags & remaining_clients_mask) == 0) {
if ((it.second->flags & remaining_clients_mask) == 0) {
item_ids_to_delete.emplace(it.first);
}
}
@@ -102,7 +102,7 @@ void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask
void Lobby::FloorItemManager::clear_private() {
unordered_set<uint32_t> item_ids_to_delete;
for (const auto& it : this->items) {
if ((it.second->visibility_flags & 0x00F) != 0x00F) {
if ((it.second->flags & 0x00F) != 0x00F) {
item_ids_to_delete.emplace(it.first);
}
}
@@ -843,9 +843,9 @@ shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) c
return this->floor_item_managers.at(floor).find(item_id);
}
void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t visibility_flags) {
void Lobby::add_item(uint8_t floor, const ItemData& data, float x, float z, uint16_t flags) {
auto& m = this->floor_item_managers.at(floor);
m.add(data, x, z, visibility_flags);
m.add(data, x, z, flags);
this->evict_items_from_floor(floor);
}
+9 -3
View File
@@ -29,7 +29,13 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
float x;
float z;
uint64_t drop_number;
uint16_t visibility_flags;
// The low 12 bits of flags are visibility flags, specifying which clients
// can see the item. (In practice, only the lowest 4 of these bits are used,
// but the game has fields for 12 players so we do too.)
// The 13th bit (0x1000) specifies whether a rare item notification should
// be sent to all players when the item is picked up. This has no effect for
// non-rare items.
uint16_t flags;
bool visible_to_client(uint8_t client_id) const;
};
@@ -46,7 +52,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
bool exists(uint32_t item_id) const;
std::shared_ptr<FloorItem> find(uint32_t item_id) const;
void add(const ItemData& item, float x, float z, uint16_t visibility_flags);
void add(const ItemData& item, float x, float z, uint16_t flags);
void add(std::shared_ptr<FloorItem> fi);
std::shared_ptr<FloorItem> remove(uint32_t item_id, uint8_t client_id);
std::unordered_set<std::shared_ptr<FloorItem>> evict();
@@ -290,7 +296,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
bool item_exists(uint8_t floor, uint32_t item_id) const;
std::shared_ptr<FloorItem> find_item(uint8_t floor, uint32_t item_id) const;
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t visibility_flags);
void add_item(uint8_t floor, const ItemData& item, float x, float z, uint16_t flags);
void add_item(uint8_t floor, std::shared_ptr<FloorItem>);
void evict_items_from_floor(uint8_t floor);
std::shared_ptr<FloorItem> remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id);
+47 -6
View File
@@ -1896,7 +1896,7 @@ static void on_box_or_enemy_item_drop_t(shared_ptr<Client> c, uint8_t command, u
ItemData item = cmd.item.item;
item.decode_for_version(c->version());
l->on_item_id_generated_externally(item.id);
l->add_item(cmd.item.floor, item, cmd.item.x, cmd.item.z, 0x00F);
l->add_item(cmd.item.floor, item, cmd.item.x, cmd.item.z, 0x100F);
auto name = s->describe_item(c->version(), item, false);
l->log.info("Player %hhu (leader) created floor item %08" PRIX32 " (%s) at %hhu:(%g, %g)",
@@ -1993,6 +1993,47 @@ static void on_pick_up_item_generic(
send_create_inventory_item_to_client(lc, client_id, fi->data);
}
}
if ((fi->flags & 0x1000) && (fi->data.data1[0] < 0x04)) {
auto pmt = s->item_parameter_table(c->version());
bool should_send_game_notif = false;
bool should_send_global_notif = false;
switch (fi->data.data1[0]) {
case 0x00:
case 0x01: {
uint8_t stars = pmt->get_item_adjusted_stars(fi->data);
should_send_game_notif = (stars >= s->game_rare_notif_min_stars);
should_send_global_notif = (stars >= s->global_rare_notif_min_stars);
break;
}
case 0x02: {
should_send_game_notif = s->game_rare_mag_notifs_enabled && pmt->is_item_rare(fi->data);
break;
}
case 0x03: {
should_send_game_notif = s->game_rare_tool_notifs_enabled && pmt->is_item_rare(fi->data);
break;
}
}
if (should_send_game_notif || should_send_global_notif) {
string p_name = p->disp.name.decode();
string desc = s->describe_item(c->version(), fi->data, true);
string message = string_printf("$C6%s$C7 has found\n%s", p_name.c_str(), desc.c_str());
if (should_send_global_notif) {
for (auto& it : s->channel_to_client) {
if (it.second->license &&
!is_patch(it.second->version()) &&
!is_ep3(it.second->version()) &&
it.second->lobby.lock()) {
send_text_message(it.second, message);
}
}
} else {
send_text_message(l, message);
}
}
}
}
}
@@ -2605,7 +2646,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
res.item.id = l->generate_item_id(0xFF);
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x1000 | (1 << lc->lobby_client_id));
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
}
@@ -2615,7 +2656,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
res.item.id = l->generate_item_id(0xFF);
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for all clients",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x00F);
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x100F);
send_drop_item_to_lobby(l, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
for (auto lc : l->clients) {
if (lc) {
@@ -2638,7 +2679,7 @@ static void on_entity_drop_item_request(shared_ptr<Client> c, uint8_t command, u
res.item.id = l->generate_item_id(0xFF);
l->log.info("Creating item %08" PRIX32 " at %02hhX:%g,%g for %s",
res.item.id.load(), cmd.floor, cmd.x.load(), cmd.z.load(), lc->channel.name.c_str());
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, (1 << lc->lobby_client_id));
l->add_item(cmd.floor, res.item, cmd.x, cmd.z, 0x1000 | (1 << lc->lobby_client_id));
send_drop_item_to_channel(s, lc->channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
send_item_notification_if_needed(s, lc->channel, lc->config, res.item, res.is_from_rare_table);
}
@@ -3703,7 +3744,7 @@ static void on_request_challenge_grave_recovery_item_bb(shared_ptr<Client> c, ui
};
ItemData item = items.at(cmd.item_type);
item.id = l->generate_item_id(cmd.header.client_id);
l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F);
l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x100F);
send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z);
}
}
@@ -4072,7 +4113,7 @@ static void on_quest_F95E_result_bb(shared_ptr<Client> c, uint8_t, uint8_t, void
}
item.id = l->generate_item_id(0xFF);
l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x00F);
l->add_item(cmd.floor, item, cmd.x, cmd.z, 0x100F);
send_drop_stacked_item_to_lobby(l, item, cmd.floor, cmd.x, cmd.z);
}
+4
View File
@@ -733,6 +733,10 @@ void ServerState::load_config_early() {
this->default_rare_notifs_enabled_v3_v4 = this->default_rare_notifs_enabled_v1_v2;
this->default_rare_notifs_enabled_v1_v2 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV1V2", this->default_rare_notifs_enabled_v1_v2);
this->default_rare_notifs_enabled_v3_v4 = this->config_json->get_bool("RareNotificationsEnabledByDefaultV3V4", this->default_rare_notifs_enabled_v3_v4);
this->game_rare_notif_min_stars = this->config_json->get_int("GameRareNotifMinStars", this->game_rare_notif_min_stars);
this->global_rare_notif_min_stars = this->config_json->get_int("GlobalRareNotifMinStars", this->global_rare_notif_min_stars);
this->game_rare_mag_notifs_enabled = this->config_json->get_bool("GameRareMagNotifsEnabled", this->game_rare_mag_notifs_enabled);
this->game_rare_tool_notifs_enabled = this->config_json->get_bool("GameRareToolNotifsEnabled", this->game_rare_tool_notifs_enabled);
this->ep3_send_function_call_enabled = this->config_json->get_bool("EnableEpisode3SendFunctionCall", false);
this->enable_v3_v4_protected_subcommands = this->config_json->get_bool("EnableV3V4ProtectedSubcommands", false);
this->catch_handler_exceptions = this->config_json->get_bool("CatchHandlerExceptions", true);
+4
View File
@@ -128,6 +128,10 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool use_game_creator_section_id = false;
bool default_rare_notifs_enabled_v1_v2 = false;
bool default_rare_notifs_enabled_v3_v4 = false;
uint8_t game_rare_notif_min_stars = 0xFF;
uint8_t global_rare_notif_min_stars = 0xFF;
bool game_rare_mag_notifs_enabled = false;
bool game_rare_tool_notifs_enabled = false;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
std::shared_ptr<const FunctionCodeIndex> function_code_index;
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
+20
View File
@@ -969,6 +969,26 @@
"RareNotificationsEnabledByDefaultV1V2": false,
"RareNotificationsEnabledByDefaultV3V4": false,
// Conditions for when to broadcast rare notifications. These only apply to
// items dropped by boxes and enemies; items dropped by players or created
// with the $item command do not cause notifications when picked up.
// If a weapon, armor, shield, or unit with at least this many stars is
// picked up, everyone in the game is notified. To disable these
// notifications, set this to 255.
"GameRareNotifMinStars": 10,
// If a weapon, armor, shield, or unit with at least this many stars is
// picked up, everyone on the server (except Episode 3 players) is notified.
// To disable these notifications, set this to 255.
"GlobalRareNotifMinStars": 12,
// If this option is on and a rare mag is picked up, everyone in the game is
// notified. Note that there are no rare mags in any of the default drop
// tables, so this has no effect unless you add rare mags to the drop tables.
"GameRareMagNotifsEnabled": true,
// If this option is on and a rare tool item (for example, a Photon Drop) is
// picked up, everyone in the game is notified.
"GameRareToolNotifsEnabled": true,
// Whether to enable patches on Episode 3 USA. This functionality depends on
// exploiting a bug in Episode 3, and while it seems to work reliably on
// Dolphin, it hasn't been tested on a real GameCube. So, newserv doesn't
+4
View File
@@ -32,6 +32,10 @@
"CheatModeBehavior": "OnByDefault",
"UnlockAllAreas": false,
"RareNotificationsEnabledByDefault": false,
"GameRareNotifMinStars": 10,
"GlobalRareNotifMinStars": 12,
"GameRareMagNotifsEnabled": true,
"GameRareToolNotifsEnabled": true,
"LocalAddress": "en0",
"ExternalAddress": "en0",