rewrite map data model
This commit is contained in:
@@ -91,7 +91,7 @@ If you want to use parts of newserv in your project, there are two easy ways to
|
||||
|
||||
# Compatibility
|
||||
|
||||
newserv supports all known versions of PSO, including development prototypes. This table lists all versions that newserv supports. (NTE stands for Network Trial Edition; the GameCube beta versions were called Trial Edition instead, but we use the NTE abbreviation anyway for consistency.)
|
||||
newserv supports all known versions of PSO, including various development prototypes. This table lists all versions that newserv supports. (NTE stands for Network Trial Edition; the GameCube beta versions were called Trial Edition instead, but we use the NTE abbreviation anyway for consistency.)
|
||||
|
||||
| Version | Lobbies | Games | Proxy |
|
||||
|-----------------|----------|----------|----------|
|
||||
@@ -339,13 +339,13 @@ Quest contents are cached in memory, but if you've changed the contents of the q
|
||||
newserv supports server-side item generation on all game versions, except for the earliest DC prototypes (NTE and 11/2000). By default, the game behaves as it did on the original servers - on all versions except BB, item drops are controlled by the leader client in each game, and on BB, item drops are controlled by the server.
|
||||
|
||||
There are five different available behaviors for item drops:
|
||||
* `DISABLED` (or `NONE`): No items will drop from boxes or enemies.
|
||||
* `CLIENT`: The game leader generates items, all items are visible to all players, and any player may pick up any item. This is the default mode for all game versions, except this mode cannot be used on BB.
|
||||
* `SERVER_SHARED`: The server generates items, all items are visible to all players, and any player may pick up any item. This is the default mode for BB.
|
||||
* `SERVER_PRIVATE`: The server generates items, but each player may get a different item from any box or enemy. If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are visible to everyone.
|
||||
* `SERVER_DUPLICATE`: The server generates items, and each player will get the same item from any box or enemy, but there is one copy of each item for each player (and each player only sees their own copy of the item). If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are not duplicated and are visible to everyone.
|
||||
* `disabled` (or `none`): No items will drop from boxes or enemies.
|
||||
* `client`: The game leader generates items, all items are visible to all players, and any player may pick up any item. This is the default mode for all game versions, except this mode cannot be used if the game leader is on BB.
|
||||
* `shared`: The server generates items, all items are visible to all players, and any player may pick up any item. This is the default mode if the game leader is on BB.
|
||||
* `private`: The server generates items, but each player may get a different item from any box or enemy. If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are visible to everyone.
|
||||
* `duplicate`: The server generates items, and each player will get the same item from any box or enemy, but there is one copy of each item for each player (and each player only sees their own copy of the item). If a player isn't in the same area as an enemy at the time it's defeated, they won't get any item from it. Items dropped by players are not duplicated and are visible to everyone.
|
||||
|
||||
In the `SERVER_PRIVATE` and `SERVER_DUPLICATE` modes, there is no incentive to pick up items before another player, since other players cannot pick up the items you see dropped from boxes and enemies. However, if you pick up an item and drop it later, it can then be seen and picked up by any player.
|
||||
In the `private` and `duplicate` modes, there is no incentive to pick up items before another player, since other players cannot pick up the items you see dropped from boxes and enemies. However, if you pick up an item and drop it later, it can then be seen and picked up by any player.
|
||||
|
||||
The drop mode can be changed at any time during a game with the `$dropmode` chat command. If the mode is changed after some items have already been dropped, the existing items retain their visibility (that is, items dropped in private mode still can't be picked up by other players since they were dropped before the mode was changed). You can configure which drop modes are used by default, and which modes players are allowed to choose, in config.json. See the comments above the AllowedDropModes and DefaultDropMode keys.
|
||||
|
||||
@@ -353,13 +353,18 @@ In the server drop modes, the item tables used to generate common items are in t
|
||||
|
||||
## Cross-version play
|
||||
|
||||
All versions of PSO can see and interact with each other in the lobby. newserv also allows some versions to play in-game with each other:
|
||||
* DC V1 players can join DC V2 games if the difficulty level isn't set to Ultimate and the creator chose to allow V1 players.
|
||||
* DC V2 players can join DC V1 games.
|
||||
* If AllowDCPCGames is enabled in config.json, PC and DC players can join each other's games. DC V1 players cannot join PC games with the Ultimate difficulty level.
|
||||
* If AllowGCXBGames is enabled in config.json, GC and Xbox players can join each other's games.
|
||||
All versions of PSO can see and interact with each other in the lobby. By default, newserv allows V1 and V2 players to play together, and allows GC and Xbox players to play together. You can change these rules with the CompatibilityGroups setting in config.json.
|
||||
|
||||
In V1/V2 cross-version play, when any of the server drop modes are used, the server uses the drop table corresponding to the version the game was created with. (For example, if a DC V1 player created the game, rare-table-v1.json will be used, even after V2 players join.)
|
||||
There are several cross-version restrictions that always apply regardless of the compatibility groups setting:
|
||||
* DC V1 players cannot join DC V2 games if the game creator didn't choose to allow them.
|
||||
* DC V1 players cannot join games if the difficulty level is set to Ultimate or the game mode is Battle or Challenge.
|
||||
* Only GC, Xbox, and BB players can join games in Episode 2.
|
||||
* Only BB players can join games in Episode 4.
|
||||
* Episode 3 players cannot join non-Episode 3 games, and vice versa.
|
||||
|
||||
V1/V2 compatibility and GC/Xbox compatibility are well-tested, but other situations are not. Not much attention has been given to how items should be handled across major versions; if you enable v2/GC compatibility, for example, there will likely be bugs. Please report such bugs as GitHub issues.
|
||||
|
||||
In cross-version play, when any of the server drop modes are used, the server uses the drop tables corresponding to the leader's version and section ID. (For example, if a DC V1 player is the game leader, rare-table-v1.json will be used, even after V2 players join.) If a BB player is the leader and the `client` drop mode is used, the server generates items as if it were in `shared` mode.
|
||||
|
||||
## Server-side saves
|
||||
|
||||
@@ -371,7 +376,7 @@ There is a third command, `$bbchar <username> <password> <slot>`, which behaves
|
||||
|
||||
Exactly which data is saved and loaded depends on the game version:
|
||||
|
||||
| Game | Inventory | Character | Options/chats | Quest flags | Bank | Battle/challenge |
|
||||
| Game | Inventory | Character | Options/chats | Quest flags | Bank | Battle/Challenge |
|
||||
|----------------------|-----------|-----------|---------------|-------------|------|------------------|
|
||||
| PSO DC v1 prototypes | Yes | Yes | No | No | No | N/A |
|
||||
| PSO DC v1 | Yes | Yes | No | No | No | N/A |
|
||||
@@ -547,7 +552,7 @@ Some commands only work on the game server and not on the proxy server. The chat
|
||||
* You'll see in-game messages from the server when you take certain actions, like killing an enemy in BB.
|
||||
* You'll see the rare seed value and floor variations when you join a game.
|
||||
* You'll be placed into the last available slot in lobbies and games instead of the first, unless you're joining a BB solo-mode game.
|
||||
* You'll be able to join games with any PSO version, not only those for which crossplay is normally supported. Be prepared for client crashes and other client-side brokenness if you do this. Do not submit any issues for broken behaviors in crossplay, unless the situation is explicitly supported (see the "Cross-version play" section above).
|
||||
* You'll be able to join games with any PSO version, not only those for which crossplay is normally enabled. See the "Cross-version play" section above for details on this.
|
||||
* The rest of the commands in this section are enabled on the game server. (They are always enabled on the proxy server.)
|
||||
* `$readmem <address>` (game server only): Reads 4 bytes from the given address and shows you the values.
|
||||
* `$writemem <address> <data>` (game server only): Writes data to the given address. Data is not required to be any specific size.
|
||||
@@ -729,7 +734,6 @@ There are several actions that don't fit well into the table above, which let yo
|
||||
* Run a brute-force search for a decryption seed (`find-decryption-seed`)
|
||||
* Format Episode 3 game data in a human-readable manner (`show-ep3-maps`, `show-ep3-cards`, `generate-ep3-cards-html`)
|
||||
* Format Blue Burst battle parameter files in a human-readable manner (`show-battle-params`)
|
||||
* Search for rare enemy seeds that result in rare enemies on console versions (`find-rare-enemy-seeds`)
|
||||
* Convert item data to a human-readable description, or vice versa (`describe-item`)
|
||||
* Connect to another PSO server and pretend to be a client (`cat-client`)
|
||||
* Generate or describe DC serial numbers (`generate-dc-serial-number`, `inspect-dc-serial-number`)
|
||||
|
||||
@@ -92,7 +92,7 @@ public:
|
||||
private:
|
||||
struct File {
|
||||
std::shared_ptr<const std::string> data;
|
||||
const Table* table;
|
||||
const Table* table = nullptr;
|
||||
};
|
||||
|
||||
// Indexed as [online/offline][episode]
|
||||
|
||||
+27
-20
@@ -465,7 +465,7 @@ static void server_command_swsetall(shared_ptr<Client> c, const std::string&) {
|
||||
auto& cmd = cmds[z];
|
||||
cmd.header.subcommand = 0x05;
|
||||
cmd.header.size = 0x03;
|
||||
cmd.header.object_id = 0xFFFF;
|
||||
cmd.header.entity_id = 0xFFFF;
|
||||
cmd.switch_flag_floor = c->floor;
|
||||
cmd.switch_flag_num = z;
|
||||
cmd.flags = 0x01;
|
||||
@@ -485,7 +485,7 @@ static void proxy_command_swsetall(shared_ptr<ProxyServer::LinkedSession> ses, c
|
||||
auto& cmd = cmds[z];
|
||||
cmd.header.subcommand = 0x05;
|
||||
cmd.header.size = 0x03;
|
||||
cmd.header.object_id = 0xFFFF;
|
||||
cmd.header.entity_id = 0xFFFF;
|
||||
cmd.switch_flag_floor = ses->floor;
|
||||
cmd.switch_flag_num = z;
|
||||
cmd.flags = 0x01;
|
||||
@@ -1117,7 +1117,7 @@ static void server_command_cheat(shared_ptr<Client> c, const std::string&) {
|
||||
if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) &&
|
||||
!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE) &&
|
||||
s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
size_t default_min_level = s->default_min_level_for_game(c->version(), l->episode, l->difficulty);
|
||||
if (l->min_level < default_min_level) {
|
||||
l->min_level = default_min_level;
|
||||
send_text_message_printf(l, "$C6Minimum level set\nto %" PRIu32, l->min_level + 1);
|
||||
@@ -1312,7 +1312,7 @@ static void server_command_secid(shared_ptr<Client> c, const std::string& args)
|
||||
c->config.override_section_id = new_override_section_id;
|
||||
if (l->is_game() && (l->leader_id == c->lobby_client_id)) {
|
||||
l->override_section_id = new_override_section_id;
|
||||
l->change_section_id();
|
||||
l->create_item_creator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1343,11 +1343,17 @@ static void server_command_variations(shared_ptr<Client> c, const std::string& a
|
||||
check_is_game(l, false);
|
||||
check_cheats_allowed(s, c, s->cheat_flags.override_variations);
|
||||
|
||||
c->override_variations = make_unique<parray<le_uint32_t, 0x20>>();
|
||||
c->override_variations->clear(0);
|
||||
for (size_t z = 0; z < min<size_t>(c->override_variations->size(), args.size()); z++) {
|
||||
c->override_variations->at(z) = args[z] - '0';
|
||||
c->override_variations = make_unique<Variations>();
|
||||
for (size_t z = 0; z < min<size_t>(c->override_variations->entries.size() * 2, args.size()); z++) {
|
||||
auto& entry = c->override_variations->entries.at(z / 2);
|
||||
if (z & 1) {
|
||||
entry.entities = args[z] - '0';
|
||||
} else {
|
||||
entry.layout = args[z] - '0';
|
||||
}
|
||||
}
|
||||
auto vars_str = c->override_variations->str();
|
||||
c->log.info("Override variations set to %s", vars_str.c_str());
|
||||
}
|
||||
|
||||
static void server_command_rand(shared_ptr<Client> c, const std::string& args) {
|
||||
@@ -1441,7 +1447,7 @@ static void server_command_min_level(shared_ptr<Client> c, const std::string& ar
|
||||
bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) ||
|
||||
c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
|
||||
if (!cheats_allowed && s->cheat_flags.insufficient_minimum_level) {
|
||||
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty);
|
||||
size_t default_min_level = s->default_min_level_for_game(c->version(), l->episode, l->difficulty);
|
||||
if (new_min_level < default_min_level) {
|
||||
send_text_message_printf(c, "$C6Cannot set minimum\nlevel below %zu", default_min_level + 1);
|
||||
return;
|
||||
@@ -2084,7 +2090,10 @@ static void proxy_command_next(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
static void server_command_where(shared_ptr<Client> c, const std::string&) {
|
||||
auto l = c->require_lobby();
|
||||
send_text_message_printf(c, "$C7%01" PRIX32 ":%s X:%" PRId32 " Z:%" PRId32,
|
||||
c->floor, short_name_for_floor(l->episode, c->floor), static_cast<int32_t>(c->x), static_cast<int32_t>(c->z));
|
||||
c->floor,
|
||||
short_name_for_floor(l->episode, c->floor),
|
||||
static_cast<int32_t>(c->pos.x.load()),
|
||||
static_cast<int32_t>(c->pos.z.load()));
|
||||
for (auto lc : l->clients) {
|
||||
if (lc && (lc != c)) {
|
||||
string name = lc->character()->disp.name.decode(lc->language());
|
||||
@@ -2108,9 +2117,7 @@ static void server_command_what(shared_ptr<Client> c, const std::string&) {
|
||||
if (!it.second->visible_to_client(c->lobby_client_id)) {
|
||||
continue;
|
||||
}
|
||||
float dx = it.second->x - c->x;
|
||||
float dz = it.second->z - c->z;
|
||||
float dist2 = (dx * dx) + (dz * dz);
|
||||
float dist2 = (it.second->pos - c->pos).norm2();
|
||||
if (!nearest_fi || (dist2 < min_dist2)) {
|
||||
nearest_fi = it.second;
|
||||
min_dist2 = dist2;
|
||||
@@ -2281,7 +2288,7 @@ static void server_command_dropmode(shared_ptr<Client> c, const std::string& arg
|
||||
return;
|
||||
}
|
||||
|
||||
l->set_drop_mode(new_mode);
|
||||
l->drop_mode = new_mode;
|
||||
switch (l->drop_mode) {
|
||||
case Lobby::DropMode::DISABLED:
|
||||
send_text_message(l, "Item drops disabled");
|
||||
@@ -2358,11 +2365,11 @@ static void server_command_item(shared_ptr<Client> c, const std::string& args) {
|
||||
item.id = l->generate_item_id(c->lobby_client_id);
|
||||
|
||||
if ((l->drop_mode == Lobby::DropMode::SERVER_PRIVATE) || (l->drop_mode == Lobby::DropMode::SERVER_DUPLICATE)) {
|
||||
l->add_item(c->floor, item, c->x, c->z, (1 << c->lobby_client_id));
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, c->floor, c->x, c->z);
|
||||
l->add_item(c->floor, item, c->pos, nullptr, nullptr, (1 << c->lobby_client_id));
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, c->floor, c->pos);
|
||||
} else {
|
||||
l->add_item(c->floor, item, c->x, c->z, 0x00F);
|
||||
send_drop_stacked_item_to_lobby(l, item, c->floor, c->x, c->z);
|
||||
l->add_item(c->floor, item, c->pos, nullptr, nullptr, 0x00F);
|
||||
send_drop_stacked_item_to_lobby(l, item, c->floor, c->pos);
|
||||
}
|
||||
|
||||
string name = s->describe_item(c->version(), item, true);
|
||||
@@ -2397,8 +2404,8 @@ static void proxy_command_item(shared_ptr<ProxyServer::LinkedSession> ses, const
|
||||
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->pos);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->pos);
|
||||
|
||||
string name = s->describe_item(ses->version(), item, true);
|
||||
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
|
||||
|
||||
+6
-2
@@ -195,8 +195,6 @@ Client::Client(
|
||||
bb_connection_phase(0xFF),
|
||||
ping_start_time(0),
|
||||
sub_version(-1),
|
||||
x(0.0f),
|
||||
z(0.0f),
|
||||
floor(0),
|
||||
lobby_client_id(0),
|
||||
lobby_arrow_color(0),
|
||||
@@ -407,6 +405,9 @@ bool Client::can_see_quest(
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
if (!q->has_version_any_language(this->version())) {
|
||||
return false;
|
||||
}
|
||||
return this->evaluate_quest_availability_expression(q->available_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
@@ -417,6 +418,9 @@ bool Client::can_play_quest(
|
||||
uint8_t difficulty,
|
||||
size_t num_players,
|
||||
bool v1_present) const {
|
||||
if (!q->has_version_any_language(this->version())) {
|
||||
return false;
|
||||
}
|
||||
return this->evaluate_quest_availability_expression(q->enabled_expression, game, event, difficulty, num_players, v1_present);
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -206,10 +206,9 @@ public:
|
||||
// Lobby/positioning
|
||||
Config config;
|
||||
Config synced_config;
|
||||
std::unique_ptr<parray<le_uint32_t, 0x20>> override_variations;
|
||||
std::unique_ptr<Variations> override_variations;
|
||||
int32_t sub_version;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
uint32_t floor;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
|
||||
+131
-215
@@ -9,6 +9,7 @@
|
||||
#include "Episode3/DeckState.hh"
|
||||
#include "Episode3/MapState.hh"
|
||||
#include "Episode3/PlayerStateSubordinates.hh"
|
||||
#include "Map.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "SaveFileFormats.hh"
|
||||
@@ -1233,7 +1234,7 @@ struct S_JoinGameT_DC_PC {
|
||||
// field when sending this command to start an Episode 3 tournament game. This
|
||||
// can be misleading when reading old logs from those days, but the Episode 3
|
||||
// client really does ignore it.
|
||||
/* 0004 */ parray<le_uint32_t, 0x20> variations;
|
||||
/* 0004 */ Variations variations;
|
||||
// Unlike lobby join commands, these are filled in in their slot positions.
|
||||
// That is, if there's only one player in a game with ID 2, then the first two
|
||||
// of these are blank and the player's data is in the third entry here.
|
||||
@@ -1246,7 +1247,7 @@ struct S_JoinGameT_DC_PC {
|
||||
/* 0109 */ uint8_t event = 0;
|
||||
/* 010A */ uint8_t section_id = 0;
|
||||
/* 010B */ uint8_t challenge_mode = 0;
|
||||
/* 010C */ le_uint32_t rare_seed = 0;
|
||||
/* 010C */ le_uint32_t random_seed = 0;
|
||||
/* 0110 */
|
||||
} __packed__;
|
||||
|
||||
@@ -1255,7 +1256,7 @@ struct S_JoinGame_DCNTE_64 {
|
||||
uint8_t leader_id = 0;
|
||||
uint8_t disable_udp = 1;
|
||||
uint8_t unused = 0;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
Variations variations;
|
||||
parray<PlayerLobbyDataDCGC, 4> lobby_data;
|
||||
} __packed_ws__(S_JoinGame_DCNTE_64, 0x104);
|
||||
using S_JoinGame_DC_64 = S_JoinGameT_DC_PC<PlayerLobbyDataDCGC>;
|
||||
@@ -2238,50 +2239,19 @@ struct S_ServerTime_B1 {
|
||||
|
||||
struct S_ExecuteCode_B2 {
|
||||
// If code_size == 0, no code is executed, but checksumming may still occur.
|
||||
// In that case, this structure is the entire body of the command (no footer
|
||||
// is sent).
|
||||
// In that case, this structure is the entire body of the command (no code
|
||||
// or footer is sent).
|
||||
le_uint32_t code_size = 0; // Size of code (following this struct) and footer
|
||||
le_uint32_t checksum_start = 0; // May be null if size is zero
|
||||
le_uint32_t checksum_size = 0; // If zero, no checksum is computed
|
||||
// The code immediately follows, ending with an S_ExecuteCode_Footer_B2
|
||||
// The code immediately follows, ending with a RELFileFooter. The REL's root
|
||||
// offset is relative to the start of the code object here, so an offset of 0
|
||||
// refers to the byte after checksum_size. The root offset points to the
|
||||
// offset ot the entrypoint (that is, the entrypoint offset is doubly
|
||||
// indirect). This is presumably done so the entrypoint can be optionally
|
||||
// relocated.
|
||||
} __packed_ws__(S_ExecuteCode_B2, 0x0C);
|
||||
|
||||
template <bool BE>
|
||||
struct S_ExecuteCode_FooterT_B2 {
|
||||
static constexpr bool IsBE = BE; // Needed by generate_client_command_t
|
||||
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) containing the number of doublewords (uint32_t) to skip for each
|
||||
// relocation. The relocation pointer starts immediately after the
|
||||
// checksum_size field in the header, and advances by the value of one
|
||||
// relocation word (times 4) before each relocation. At each relocated
|
||||
// doubleword, the address of the first byte of the code (after checksum_size)
|
||||
// is added to the existing value.
|
||||
// For example, if the code segment contains the following data (where R
|
||||
// specifies doublewords to relocate):
|
||||
// RR RR RR RR ?? ?? ?? ?? ?? ?? ?? ?? RR RR RR RR
|
||||
// RR RR RR RR ?? ?? ?? ?? RR RR RR RR
|
||||
// then the relocation words should be 0000, 0003, 0001, and 0002.
|
||||
// If there is a small number of relocations, they may be placed in the unused
|
||||
// fields of this structure to save space and/or confuse reverse engineers.
|
||||
// The game never accesses the last 12 bytes of this structure unless
|
||||
// relocations_offset points there, so those 12 bytes may also be omitted from
|
||||
// the command entirely (without changing code_size - so code_size would
|
||||
// technically extend beyond the end of the B2 command).
|
||||
U32T<BE> relocations_offset = 0; // Relative to code base (after checksum_size)
|
||||
U32T<BE> num_relocations = 0;
|
||||
parray<U32T<BE>, 2> unused1;
|
||||
// entrypoint_offset is doubly indirect - it points to a pointer to a 32-bit
|
||||
// value that itself is the actual entrypoint. This is presumably done so the
|
||||
// entrypoint can be optionally relocated.
|
||||
U32T<BE> entrypoint_addr_offset = 0; // Relative to code base (after checksum_size).
|
||||
parray<U32T<BE>, 3> unused2;
|
||||
} __packed__;
|
||||
using S_ExecuteCode_Footer_GC_B2 = S_ExecuteCode_FooterT_B2<true>;
|
||||
using S_ExecuteCode_Footer_DC_PC_XB_BB_B2 = S_ExecuteCode_FooterT_B2<false>;
|
||||
check_struct_size(S_ExecuteCode_Footer_GC_B2, 0x20);
|
||||
check_struct_size(S_ExecuteCode_Footer_DC_PC_XB_BB_B2, 0x20);
|
||||
|
||||
// B3 (C->S): Execute code and/or checksum memory result
|
||||
// Not used on versions that don't support the B2 command (see above).
|
||||
|
||||
@@ -3244,7 +3214,7 @@ struct SC_SyncSaveFiles_BB_E7 {
|
||||
// primary game's players should be the leader (this is what newserv does).
|
||||
|
||||
struct S_JoinSpectatorTeam_Ep3_E8 {
|
||||
/* 0000 */ parray<le_uint32_t, 0x20> variations; // unused
|
||||
/* 0000 */ Variations variations; // unused
|
||||
struct PlayerEntry {
|
||||
/* 0000 */ PlayerLobbyDataDCGC lobby_data;
|
||||
/* 0020 */ PlayerInventory inventory;
|
||||
@@ -3260,7 +3230,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 {
|
||||
/* 1175 */ uint8_t event = 0;
|
||||
/* 1176 */ uint8_t section_id = 0;
|
||||
/* 1177 */ uint8_t challenge_mode = 0;
|
||||
/* 1178 */ le_uint32_t rare_seed = 0;
|
||||
/* 1178 */ le_uint32_t random_seed = 0;
|
||||
/* 117C */ uint8_t episode = 0;
|
||||
/* 117D */ parray<uint8_t, 3> unused;
|
||||
struct SpectatorEntry {
|
||||
@@ -3867,16 +3837,11 @@ struct G_ClientIDHeader {
|
||||
uint8_t size = 0;
|
||||
le_uint16_t client_id = 0; // <= 12
|
||||
} __packed_ws__(G_ClientIDHeader, 4);
|
||||
struct G_EnemyIDHeader {
|
||||
struct G_EntityIDHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
le_uint16_t enemy_id = 0; // In [0x1000, 0x4000); not the same as enemy_index!
|
||||
} __packed_ws__(G_EnemyIDHeader, 4);
|
||||
struct G_ObjectIDHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
le_uint16_t object_id = 0; // >= 0x4000, != 0xFFFF
|
||||
} __packed_ws__(G_ObjectIDHeader, 4);
|
||||
le_uint16_t entity_id = 0; // 0-B=client, 1000-3FFF=enemy, 4000-FFFF=object
|
||||
} __packed_ws__(G_EntityIDHeader, 4);
|
||||
struct G_ParameterHeader {
|
||||
uint8_t subcommand = 0;
|
||||
uint8_t size = 0;
|
||||
@@ -3916,9 +3881,9 @@ struct G_Unknown_6x02_6x03 {
|
||||
// TODO: This does something with TObjDoorKey objects
|
||||
|
||||
struct G_Unknown_6x04 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t door_key_token = 0;
|
||||
le_uint16_t unused = 0;
|
||||
G_ParameterHeader header; // param = door token (NOT entity ID or index)
|
||||
le_uint16_t unused1 = 0;
|
||||
le_uint16_t unused2 = 0;
|
||||
} __packed_ws__(G_Unknown_6x04, 8);
|
||||
|
||||
// 6x05: Switch state changed
|
||||
@@ -3930,7 +3895,7 @@ struct G_Unknown_6x04 {
|
||||
|
||||
struct G_SwitchStateChanged_6x05 {
|
||||
// Note: header.object_id is 0xFFFF for room clear when all enemies defeated
|
||||
G_ObjectIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
// TODO: Some of these might be big-endian on GC; it only byteswaps
|
||||
// switch_flag_num. Are the others actually uint16, or are they uint8[2]?
|
||||
le_uint16_t client_id = 0;
|
||||
@@ -3996,14 +3961,14 @@ struct G_SymbolChat_6x07 {
|
||||
// 6x09: Unknown
|
||||
|
||||
struct G_Unknown_6x09 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
} __packed_ws__(G_Unknown_6x09, 4);
|
||||
|
||||
// 6x0A: Update enemy state
|
||||
|
||||
template <bool BE>
|
||||
struct G_UpdateEnemyStateT_6x0A {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t enemy_index = 0; // [0, 0xB50)
|
||||
le_uint16_t total_damage = 0;
|
||||
// Flags:
|
||||
@@ -4019,7 +3984,7 @@ check_struct_size(G_UpdateEnemyState_DC_PC_XB_BB_6x0A, 0x0C);
|
||||
// 6x0B: Update object state
|
||||
|
||||
struct G_UpdateObjectState_6x0B {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t flags = 0;
|
||||
le_uint32_t object_index = 0;
|
||||
} __packed_ws__(G_UpdateObjectState_6x0B, 0x0C);
|
||||
@@ -4069,12 +4034,12 @@ struct G_Unknown_6x0E {
|
||||
|
||||
// 6x10: Unknown (not valid on Episode 3)
|
||||
|
||||
struct G_Unknown_6x10_6x11_6x12_6x14 {
|
||||
G_EnemyIDHeader header;
|
||||
struct G_Unknown_6x10_6x11_6x14 {
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
} __packed_ws__(G_Unknown_6x10_6x11_6x12_6x14, 0x0C);
|
||||
} __packed_ws__(G_Unknown_6x10_6x11_6x14, 0x0C);
|
||||
|
||||
// 6x11: Unknown (not valid on Episode 3)
|
||||
// Same format as 6x10
|
||||
@@ -4083,7 +4048,7 @@ struct G_Unknown_6x10_6x11_6x12_6x14 {
|
||||
|
||||
template <bool BE>
|
||||
struct G_DragonBossActionsT_6x12 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -4098,7 +4063,7 @@ check_struct_size(G_DragonBossActions_GC_6x12, 0x14);
|
||||
// 6x13: De Rol Le boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_DeRolLeBossActions_6x13 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_DeRolLeBossActions_6x13, 8);
|
||||
@@ -4109,7 +4074,7 @@ struct G_DeRolLeBossActions_6x13 {
|
||||
// 6x15: Vol Opt boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOptBossActions_6x15 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
@@ -4119,7 +4084,7 @@ struct G_VolOptBossActions_6x15 {
|
||||
// 6x16: Vol Opt boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOptBossActions_6x16 {
|
||||
G_UnusedHeader header;
|
||||
G_EntityIDHeader header;
|
||||
parray<uint8_t, 6> unknown_a2;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_VolOptBossActions_6x16, 0x0C);
|
||||
@@ -4127,7 +4092,7 @@ struct G_VolOptBossActions_6x16 {
|
||||
// 6x17: Vol Opt phase 2 boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOpt2BossActions_6x17 {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_float unknown_a2 = 0.0f;
|
||||
le_float unknown_a3 = 0.0f;
|
||||
le_float unknown_a4 = 0.0f;
|
||||
@@ -4137,14 +4102,14 @@ struct G_VolOpt2BossActions_6x17 {
|
||||
// 6x18: Vol Opt phase 2 boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_VolOpt2BossActions_6x18 {
|
||||
G_ClientIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
parray<le_uint16_t, 4> unknown_a2;
|
||||
} __packed_ws__(G_VolOpt2BossActions_6x18, 0x0C);
|
||||
|
||||
// 6x19: Dark Falz boss actions (not valid on Episode 3)
|
||||
|
||||
struct G_DarkFalzActions_6x19 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -4186,9 +4151,7 @@ struct G_SetPlayerFloor_6x1F {
|
||||
struct G_SetPosition_6x20 {
|
||||
G_ClientIDHeader header;
|
||||
le_int32_t floor = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_SetPosition_6x20, 0x18);
|
||||
|
||||
@@ -4212,9 +4175,7 @@ struct G_SetPlayerVisibility_6x22_6x23 {
|
||||
struct G_TeleportPlayer_6x24 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_TeleportPlayer_6x24, 0x14);
|
||||
|
||||
// 6x25: Equip item (protected on V3/V4)
|
||||
@@ -4266,9 +4227,7 @@ struct G_DropItem_6x2A {
|
||||
le_uint16_t amount = 0;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint32_t item_id = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_DropItem_6x2A, 0x18);
|
||||
|
||||
// 6x2B: Create item in inventory (e.g. via tekker or bank withdraw) (protected on V3/V4)
|
||||
@@ -4377,11 +4336,11 @@ struct G_PhotonBlast_6x37 {
|
||||
|
||||
// 6x38: Donate to photon blast (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x38 {
|
||||
struct G_DonateToPhotonBlast_6x38 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_Unknown_6x38, 8);
|
||||
} __packed_ws__(G_DonateToPhotonBlast_6x38, 8);
|
||||
|
||||
// 6x39: Photon blast ready (protected on V3/V4)
|
||||
|
||||
@@ -4414,9 +4373,7 @@ struct G_StopAtPosition_6x3E {
|
||||
le_uint16_t angle = 0;
|
||||
le_int16_t floor = 0;
|
||||
le_int16_t room = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_StopAtPosition_6x3E, 0x18);
|
||||
|
||||
// 6x3F: Set position (protected on V3/V4)
|
||||
@@ -4427,9 +4384,7 @@ struct G_SetPosition_6x3F {
|
||||
le_uint16_t angle = 0;
|
||||
le_int16_t floor = 0;
|
||||
le_int16_t room = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_SetPosition_6x3F, 0x18);
|
||||
|
||||
// 6x40: Walk (protected on V3/V4)
|
||||
@@ -4437,28 +4392,19 @@ struct G_SetPosition_6x3F {
|
||||
|
||||
struct G_WalkToPosition_6x40 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
le_uint32_t action = 0;
|
||||
} __packed_ws__(G_WalkToPosition_6x40, 0x10);
|
||||
|
||||
// 6x41: Unknown
|
||||
// This subcommand is completely ignored by v2 and later.
|
||||
|
||||
struct G_Unknown_6x41 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_Unknown_6x41, 0x0C);
|
||||
|
||||
// 6x41: Move to position (v1)
|
||||
// 6x42: Run (protected on V3/V4)
|
||||
// This subcommand is completely ignored by v2 and later.
|
||||
// If UDP mode is enabled, this command is sent via UDP.
|
||||
|
||||
struct G_RunToPosition_6x42 {
|
||||
struct G_MoveToPosition_6x41_6x42 {
|
||||
G_ClientIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_RunToPosition_6x42, 0x0C);
|
||||
VectorXZF pos;
|
||||
} __packed_ws__(G_MoveToPosition_6x41_6x42, 0x0C);
|
||||
|
||||
// 6x43: First attack (protected on V3/V4)
|
||||
// 6x44: Second attack (protected on V3/V4)
|
||||
@@ -4484,7 +4430,7 @@ struct TargetEntry {
|
||||
|
||||
struct G_AttackFinished_6x46 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t count = 0;
|
||||
le_uint32_t target_count = 0;
|
||||
// The client may send a shorter command if not all of these are used.
|
||||
parray<TargetEntry, 10> targets;
|
||||
} __packed_ws__(G_AttackFinished_6x46, 0x30);
|
||||
@@ -4544,8 +4490,7 @@ struct G_HitByEnemy_6x4B_6x4C {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t angle = 0;
|
||||
le_uint16_t current_hp = 0;
|
||||
le_float x_velocity = 0.0f;
|
||||
le_float z_velocity = 0.0f;
|
||||
VectorXZF velocity;
|
||||
} __packed_ws__(G_HitByEnemy_6x4B_6x4C, 0x10);
|
||||
|
||||
// 6x4D: Player died (protected on V3/V4)
|
||||
@@ -4603,7 +4548,9 @@ struct G_Unknown_6x53 {
|
||||
} __packed_ws__(G_Unknown_6x53, 4);
|
||||
|
||||
// 6x54: Unknown
|
||||
// This subcommand is completely ignored (at least, by PSO GC).
|
||||
// This subcommand is completely ignored by DCv2 and later. On DC NTE, 11/2000,
|
||||
// and DCv1, the handler has some logic in it and it calls a virtual function
|
||||
// on TObjPlayer, but that codepath ends up doing nothing.
|
||||
|
||||
struct G_Unknown_6x54 {
|
||||
G_ClientIDHeader header;
|
||||
@@ -4614,23 +4561,17 @@ struct G_Unknown_6x54 {
|
||||
struct G_IntraMapWarp_6x55 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x1 = 0.0f;
|
||||
le_float y1 = 0.0f;
|
||||
le_float z1 = 0.0f;
|
||||
le_float x2 = 0.0f;
|
||||
le_float y2 = 0.0f;
|
||||
le_float z2 = 0.0f;
|
||||
VectorXYZF pos1;
|
||||
VectorXYZF pos2;
|
||||
} __packed_ws__(G_IntraMapWarp_6x55, 0x20);
|
||||
|
||||
// 6x56: Unknown (supported; game) (protected on V3/V4)
|
||||
// 6x56: Set player position and angle (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x56 {
|
||||
struct G_SetPlayerPositionAndAngle_6x56 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
} __packed_ws__(G_Unknown_6x56, 0x14);
|
||||
le_uint32_t angle_y = 0;
|
||||
VectorXYZF pos;
|
||||
} __packed_ws__(G_SetPlayerPositionAndAngle_6x56, 0x14);
|
||||
|
||||
// 6x57: Unknown (supported; lobby & game) (protected on V3/V4)
|
||||
|
||||
@@ -4680,8 +4621,7 @@ struct G_DropStackedItem_DC_6x5D {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unknown_a2 = 0; // Corresponds to FloorItem::unknown_a2
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
ItemData item_data;
|
||||
} __packed_ws__(G_DropStackedItem_DC_6x5D, 0x24);
|
||||
|
||||
@@ -4700,10 +4640,9 @@ struct G_BuyShopItem_6x5E {
|
||||
|
||||
struct FloorItem {
|
||||
/* 00 */ uint8_t floor = 0;
|
||||
/* 01 */ uint8_t from_enemy = 0;
|
||||
/* 02 */ le_uint16_t entity_id = 0; // < 0x0B50 if from_enemy != 0; otherwise < 0x0BA0
|
||||
/* 04 */ le_float x = 0.0f;
|
||||
/* 08 */ le_float z = 0.0f;
|
||||
/* 01 */ uint8_t source_type = 0; // 1 = enemy, 2 = box
|
||||
/* 02 */ le_uint16_t entity_index = 0; // < 0x0B50 if source_type == 1; otherwise < 0x0BA0
|
||||
/* 04 */ VectorXZF pos;
|
||||
/* 0C */ le_uint16_t unknown_a2 = 0;
|
||||
// The drop number is scoped to the floor and increments by 1 each time an
|
||||
// item is dropped. The last item dropped in each floor has drop_number equal
|
||||
@@ -4728,9 +4667,8 @@ struct G_StandardDropItemRequest_DC_6x60 {
|
||||
/* 00 */ G_UnusedHeader header;
|
||||
/* 04 */ uint8_t floor = 0;
|
||||
/* 05 */ uint8_t rt_index = 0;
|
||||
/* 06 */ le_uint16_t entity_id = 0;
|
||||
/* 08 */ le_float x = 0.0f;
|
||||
/* 0C */ le_float z = 0.0f;
|
||||
/* 06 */ le_uint16_t entity_index = 0;
|
||||
/* 08 */ VectorXZF pos;
|
||||
/* 10 */ le_uint16_t section = 0;
|
||||
/* 12 */ le_uint16_t ignore_def = 0;
|
||||
/* 14 */
|
||||
@@ -4794,9 +4732,7 @@ struct G_SetTelepipeState_6x68 {
|
||||
le_uint16_t unknown_b1 = 0;
|
||||
uint8_t unknown_b2 = 0;
|
||||
uint8_t unknown_b3 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
} __packed_ws__(G_SetTelepipeState_6x68, 0x1C);
|
||||
|
||||
@@ -4829,16 +4765,21 @@ struct G_NPCControl_6x69 {
|
||||
le_uint16_t param3; // Commands 0/3: npc_template_index; commands 1/2: unused
|
||||
} __packed_ws__(G_NPCControl_6x69, 0x0C);
|
||||
|
||||
// 6x6A: Use boss warp (not valid on Episode 3)
|
||||
// 6x6A: Set boss warp flags (not valid on Episode 3)
|
||||
|
||||
struct G_UseBossWarp_6x6A {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_UseBossWarp_6x6A, 8);
|
||||
struct G_SetBossWarpFlags_6x6A {
|
||||
G_EntityIDHeader header; // entity_id = boss warp object ID
|
||||
le_uint16_t flags;
|
||||
le_uint16_t unused;
|
||||
} __packed_ws__(G_SetBossWarpFlags_6x6A, 8);
|
||||
|
||||
// 6x6B: Sync enemy state (used while loading into game)
|
||||
|
||||
// Note that DC NTE doesn't send the decompressed size in the header here. This
|
||||
// is a bug that can cause the client to write more entries than it should when
|
||||
// it receives one of these commands. All later versions, including 11/2000,
|
||||
// use the second header structure here, which prevents this issue.
|
||||
|
||||
struct G_SyncGameStateHeader_DCNTE_6x6B_6x6C_6x6D_6x6E {
|
||||
G_ExtendedHeaderT<G_UnusedHeader> header;
|
||||
le_uint32_t decompressed_size = 0;
|
||||
@@ -4852,25 +4793,11 @@ struct G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E {
|
||||
// BC0-compressed data follows here (see bc0_decompress)
|
||||
} __packed_ws__(G_SyncGameStateHeader_6x6B_6x6C_6x6D_6x6E, 0x10);
|
||||
|
||||
// Decompressed format is a list of these
|
||||
struct G_SyncEnemyState_6x6B_Entry_Decompressed {
|
||||
le_uint32_t flags = 0; // Same as flags in 6x0A
|
||||
le_uint16_t item_drop_id = 0;
|
||||
le_uint16_t total_damage = 0; // Same as in 6x0A
|
||||
uint8_t red_buff_type = 0;
|
||||
uint8_t red_buff_level = 0;
|
||||
uint8_t blue_buff_type = 0;
|
||||
uint8_t blue_buff_level = 0;
|
||||
} __packed_ws__(G_SyncEnemyState_6x6B_Entry_Decompressed, 0x0C);
|
||||
// Decompressed format is a list of SyncEnemyStateEntry (from Map.hh).
|
||||
|
||||
// 6x6C: Sync object state (used while loading into game)
|
||||
// Compressed format is the same as 6x6B.
|
||||
|
||||
// Decompressed format is a list of these
|
||||
struct G_SyncObjectState_6x6C_Entry_Decompressed {
|
||||
le_uint16_t flags = 0;
|
||||
le_uint16_t item_drop_id = 0;
|
||||
} __packed_ws__(G_SyncObjectState_6x6C_Entry_Decompressed, 4);
|
||||
// Decompressed format is a list of SyncObjectStateEntry (from Map.hh).
|
||||
|
||||
// 6x6D: Sync item state (used while loading into game)
|
||||
// Internal name: RcvItemCondition
|
||||
@@ -4967,9 +4894,7 @@ struct G_6x70_Sub_Telepipe {
|
||||
/* 00 */ le_uint16_t owner_client_id = 0xFFFF;
|
||||
/* 02 */ le_uint16_t floor = 0;
|
||||
/* 04 */ le_uint32_t unknown_a1 = 0;
|
||||
/* 08 */ le_float x = 0.0f;
|
||||
/* 0C */ le_float y = 0.0f;
|
||||
/* 10 */ le_float z = 0.0f;
|
||||
/* 08 */ VectorXYZF pos;
|
||||
/* 14 */ le_uint32_t unknown_a3 = 0;
|
||||
/* 18 */ le_uint32_t unknown_a4 = 0x0000FFFF;
|
||||
/* 1C */
|
||||
@@ -4997,9 +4922,7 @@ struct G_6x70_Base_DCNTE {
|
||||
/* 0000 */ le_uint16_t client_id = 0;
|
||||
/* 0002 */ le_uint16_t room_id = 0;
|
||||
/* 0004 */ le_uint32_t flags1 = 0;
|
||||
/* 0008 */ le_float x = 0.0f;
|
||||
/* 000C */ le_float y = 0.0f;
|
||||
/* 0010 */ le_float z = 0.0f;
|
||||
/* 0008 */ VectorXYZF pos;
|
||||
/* 0014 */ le_uint32_t angle_x = 0;
|
||||
/* 0018 */ le_uint32_t angle_y = 0;
|
||||
/* 001C */ le_uint32_t angle_z = 0;
|
||||
@@ -5174,7 +5097,7 @@ struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 {
|
||||
// bitwise OR operation instead of a simple assignment.
|
||||
|
||||
struct G_SetEntitySetFlags_6x76 {
|
||||
G_EnemyIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
G_EntityIDHeader header; // 1000-3FFF = enemy, 4000-FFFF = object
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t flags = 0;
|
||||
} __packed_ws__(G_SetEntitySetFlags_6x76, 8);
|
||||
@@ -5289,8 +5212,10 @@ check_struct_size(G_BattleScoresBE_6x7F, 0x24);
|
||||
|
||||
struct G_TriggerTrap_6x80 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
// Traps set by players are numbered according to their type and who set
|
||||
// them. The trap number is computed as (client_id * 80) + (trap_type * 20).
|
||||
le_uint16_t trap_number = 0;
|
||||
le_uint16_t what = 0; // Must be 0, 1, or 2
|
||||
} __packed_ws__(G_TriggerTrap_6x80, 8);
|
||||
|
||||
// 6x81: Disable drop weapon on death (protected on V3/V4)
|
||||
@@ -5334,10 +5259,7 @@ struct G_Unknown_6x85 {
|
||||
|
||||
// 6x86: Hit destructible object (not valid on Episode 3)
|
||||
|
||||
struct G_HitDestructibleObject_6x86 {
|
||||
G_ObjectIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint32_t unknown_a2 = 0;
|
||||
struct G_HitDestructibleObject_6x86 : G_UpdateObjectState_6x0B {
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
} __packed_ws__(G_HitDestructibleObject_6x86, 0x10);
|
||||
@@ -5355,13 +5277,14 @@ struct G_RestoreShrunkenPlayer_6x88 {
|
||||
G_ClientIDHeader header;
|
||||
} __packed_ws__(G_RestoreShrunkenPlayer_6x88, 4);
|
||||
|
||||
// 6x89: Player killed by monster (protected on V3/V4)
|
||||
// 6x89: Set killer entity ID (protected on V3/V4)
|
||||
// This is used to specify which enemy killed a player.
|
||||
|
||||
struct G_PlayerKilledByMonster_6x89 {
|
||||
struct G_SetKillerEntityID_6x89 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t killer_entity_id = 0;
|
||||
le_uint16_t unused = 0;
|
||||
} __packed_ws__(G_PlayerKilledByMonster_6x89, 8);
|
||||
} __packed_ws__(G_SetKillerEntityID_6x89, 8);
|
||||
|
||||
// 6x8A: Show Challenge time records window (not valid on Episode 3)
|
||||
// The leader sends this command to tell other clients to show, hide, or update
|
||||
@@ -5400,34 +5323,31 @@ struct G_SetTechniqueLevelOverride_6x8D {
|
||||
// 6x8E: Unknown (not valid on Episode 3)
|
||||
// This command has a handler, but it does nothing.
|
||||
|
||||
// 6x8F: Unknown (not valid on Episode 3)
|
||||
// 6x8F: Add battle damage scores (not valid on Episode 3)
|
||||
|
||||
struct G_Unknown_6x8F {
|
||||
struct G_AddBattleDamageScores_6x8F {
|
||||
G_ClientIDHeader header; // client_id is the attacking player
|
||||
le_uint16_t target_entity_id = 0;
|
||||
le_uint16_t amount = 0;
|
||||
} __packed_ws__(G_AddBattleDamageScores_6x8F, 8);
|
||||
|
||||
// 6x90: Set player battle team (not valid on Episode 3) (protected on V3/V4)
|
||||
|
||||
struct G_SetPlayerBattleTeam_6x90 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t client_id2 = 0;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x8F, 8);
|
||||
|
||||
// 6x90: Unknown (not valid on Episode 3) (protected on V3/V4)
|
||||
|
||||
struct G_Unknown_6x90 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x90, 8);
|
||||
le_uint32_t team_number = 0; // 0 or 1
|
||||
} __packed_ws__(G_SetPlayerBattleTeam_6x90, 8);
|
||||
|
||||
// 6x91: Unknown (supported; game only)
|
||||
// TODO: Deals with TOAttackableCol objects. Figoure out exactly what it does.
|
||||
// TODO: Deals with TOAttackableCol objects. Figure out exactly what it does.
|
||||
|
||||
struct G_Unknown_6x91 {
|
||||
G_ObjectIDHeader header;
|
||||
le_uint32_t object_flags = 0;
|
||||
le_uint32_t object_index = 0; // < 0xBA0
|
||||
struct G_UpdateAttackableColState_6x91 : G_UpdateObjectState_6x0B {
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint16_t unknown_a4 = 0;
|
||||
le_uint16_t switch_flag_num = 0;
|
||||
uint8_t should_set = 0; // The switch flag is only set if this is equal to 1; otherwise it's cleared
|
||||
uint8_t switch_flag_floor = 0;
|
||||
} __packed_ws__(G_Unknown_6x91, 0x14);
|
||||
uint8_t floor = 0;
|
||||
} __packed_ws__(G_UpdateAttackableColState_6x91, 0x14);
|
||||
|
||||
// 6x92: Unknown (not valid on Episode 3)
|
||||
// This does something with the TObjOnlineEndingHexMove object. TODO: Figure
|
||||
@@ -5522,7 +5442,7 @@ struct G_LevelUpAllTechniques_6x9B {
|
||||
// TODO: Figure out what this does.
|
||||
|
||||
struct G_Unknown_6x9C {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
} __packed_ws__(G_Unknown_6x9C, 8);
|
||||
|
||||
@@ -5547,7 +5467,7 @@ struct G_PlayerCameraShutterSound_6x9E {
|
||||
// 6x9F: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_GalGryphonBossActions_6x9F {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_float unknown_a2 = 0.0f;
|
||||
le_float unknown_a3 = 0.0f;
|
||||
@@ -5556,10 +5476,8 @@ struct G_GalGryphonBossActions_6x9F {
|
||||
// 6xA0: Gal Gryphon boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_GalGryphonBossActions_6xA0 {
|
||||
G_EnemyIDHeader header;
|
||||
le_float x = 0.0f;
|
||||
le_float y = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
G_EntityIDHeader header;
|
||||
VectorXYZF pos;
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
@@ -5586,7 +5504,7 @@ struct G_SpecializableItemDropRequest_6xA2 : G_StandardDropItemRequest_PC_V3_BB_
|
||||
// 6xA3: Olga Flow boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowBossActions_6xA3 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t unknown_a1 = 0;
|
||||
uint8_t unknown_a2 = 0;
|
||||
parray<uint8_t, 2> unknown_a3;
|
||||
@@ -5595,7 +5513,7 @@ struct G_OlgaFlowBossActions_6xA3 {
|
||||
// 6xA4: Olga Flow phase 1 boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowPhase1BossActions_6xA4 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t what = 0;
|
||||
parray<uint8_t, 3> unknown_a3;
|
||||
} __packed_ws__(G_OlgaFlowPhase1BossActions_6xA4, 8);
|
||||
@@ -5603,7 +5521,7 @@ struct G_OlgaFlowPhase1BossActions_6xA4 {
|
||||
// 6xA5: Olga Flow phase 2 boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_OlgaFlowPhase2BossActions_6xA5 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
uint8_t what = 0;
|
||||
parray<uint8_t, 3> unknown_a3;
|
||||
} __packed_ws__(G_OlgaFlowPhase2BossActions_6xA5, 8);
|
||||
@@ -5637,7 +5555,7 @@ struct G_ModifyTradeProposal_6xA6 {
|
||||
|
||||
template <bool BE>
|
||||
struct G_GolDragonBossActionsT_6xA8 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint16_t unknown_a3 = 0;
|
||||
le_uint32_t unknown_a4 = 0;
|
||||
@@ -5654,7 +5572,7 @@ check_struct_size(G_GolDragonBossActions_GC_6xA8, 0x18);
|
||||
// 6xA9: Barba Ray boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_BarbaRayBossActions_6xA9 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_BarbaRayBossActions_6xA9, 8);
|
||||
@@ -5662,7 +5580,7 @@ struct G_BarbaRayBossActions_6xA9 {
|
||||
// 6xAA: Barba Ray boss actions (not valid on pre-V3 or Episode 3)
|
||||
|
||||
struct G_BarbaRayBossActions_6xAA {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
@@ -5673,7 +5591,7 @@ struct G_BarbaRayBossActions_6xAA {
|
||||
// It's not known what it does on GC NTE.
|
||||
|
||||
struct G_Unknown_GCNTE_6xAB {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
} __packed_ws__(G_Unknown_GCNTE_6xAB, 8);
|
||||
@@ -5685,12 +5603,11 @@ struct G_CreateLobbyChair_6xAB {
|
||||
} __packed_ws__(G_CreateLobbyChair_6xAB, 8);
|
||||
|
||||
// 6xAC: Unknown (not valid on pre-V3) (protected on V3/V4)
|
||||
// This command's appears to be different on GC NTE than on any other version.
|
||||
// It also seems that no version (other than perhaps GC NTE) ever sends this
|
||||
// command.
|
||||
// This command appears to be different on GC NTE than on any other version. It
|
||||
// also seems that no version (except perhaps GC NTE) ever sends this command.
|
||||
|
||||
struct G_Unknown_GCNTE_6xAC {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_uint16_t unknown_a2 = 0;
|
||||
le_uint32_t unknown_a3 = 0;
|
||||
@@ -6079,8 +5996,7 @@ struct G_SplitStackedItem_BB_6xC3 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unused2 = 0;
|
||||
le_float x = 0.0f;
|
||||
le_float z = 0.0f;
|
||||
VectorXZF pos;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t amount = 0;
|
||||
} __packed_ws__(G_SplitStackedItem_BB_6xC3, 0x18);
|
||||
@@ -6098,7 +6014,7 @@ struct G_MedicalCenterUsed_BB_6xC5 {
|
||||
G_ClientIDHeader header;
|
||||
} __packed_ws__(G_MedicalCenterUsed_BB_6xC5, 4);
|
||||
|
||||
// 6xC6: Steal experience (BB)
|
||||
// 6xC6: Steal experience (BB; handled by the server)
|
||||
|
||||
struct G_StealEXP_BB_6xC6 {
|
||||
G_ClientIDHeader header;
|
||||
@@ -6118,7 +6034,7 @@ struct G_ChargeAttack_BB_6xC7 {
|
||||
// 6xC8: Enemy EXP request (BB; handled by the server)
|
||||
|
||||
struct G_EnemyEXPRequest_BB_6xC8 {
|
||||
G_EnemyIDHeader header;
|
||||
G_EntityIDHeader header;
|
||||
le_uint16_t enemy_index = 0;
|
||||
le_uint16_t requesting_client_id = 0;
|
||||
uint8_t is_killer = 0;
|
||||
@@ -6182,8 +6098,7 @@ struct G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint16_t floor = 0;
|
||||
le_uint16_t unknown_a1 = 0;
|
||||
le_float x = 0;
|
||||
le_float z = 0;
|
||||
VectorXZF pos;
|
||||
le_uint32_t item_type = 0; // Should be < 6
|
||||
} __packed_ws__(G_ChallengeModeGraveRecoveryItemRequest_BB_6xD1, 0x14);
|
||||
|
||||
@@ -6281,6 +6196,9 @@ struct G_UpgradeWeaponAttribute_BB_6xDA {
|
||||
|
||||
struct G_ExchangeItemInQuest_BB_6xDB {
|
||||
G_ClientIDHeader header;
|
||||
// If this is 0, the command is identical to 6x29. If this is 1, a function
|
||||
// similar to find_item_by_id is called instead of find_item_by_id, but I
|
||||
// don't yet know what exactly the logic differences are.
|
||||
le_uint32_t unknown_a1 = 0;
|
||||
le_uint32_t item_id = 0;
|
||||
le_uint32_t amount = 0;
|
||||
@@ -6333,8 +6251,7 @@ struct G_RequestItemDropFromQuest_BB_6xE0 {
|
||||
uint8_t type = 0; // valueA
|
||||
uint8_t unknown_a3 = 0;
|
||||
uint8_t unused = 0;
|
||||
le_float x = 0.0f; // valueB
|
||||
le_float z = 0.0f; // valueC
|
||||
VectorXZF pos; // x = valueB, z = valueC
|
||||
} __packed_ws__(G_RequestItemDropFromQuest_BB_6xE0, 0x10);
|
||||
|
||||
// 6xE1: Exchange Photon Tickets (BB; handled by server)
|
||||
@@ -6359,8 +6276,7 @@ struct G_GetMesetaSlotPrize_BB_6xE2 {
|
||||
uint8_t floor;
|
||||
uint8_t unknown_a2;
|
||||
uint8_t unused;
|
||||
le_float x; // TODO: Verify this guess
|
||||
le_float z; // TODO: Verify this guess
|
||||
VectorXZF pos; // TODO: Verify this guess
|
||||
} __packed_ws__(G_GetMesetaSlotPrize_BB_6xE2, 0x10);
|
||||
|
||||
// 6xE3: Set Meseta slot prize result (BB)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Vector.hh>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
struct VectorXZF {
|
||||
le_float x = 0.0;
|
||||
le_float z = 0.0;
|
||||
|
||||
inline VectorXZF operator-() const {
|
||||
return VectorXZF{-this->x, -this->z};
|
||||
}
|
||||
|
||||
inline VectorXZF operator+(const VectorXZF& other) const {
|
||||
return VectorXZF{this->x + other.x, this->z + other.z};
|
||||
}
|
||||
inline VectorXZF operator-(const VectorXZF& other) const {
|
||||
return VectorXZF{this->x - other.x, this->z - other.z};
|
||||
}
|
||||
|
||||
inline bool operator==(const VectorXZF& other) const {
|
||||
return ((this->x == other.x) && (this->z == other.z));
|
||||
}
|
||||
inline bool operator!=(const VectorXZF& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
inline double norm() const {
|
||||
return sqrt(this->norm2());
|
||||
}
|
||||
inline double norm2() const {
|
||||
return ((this->x * this->x) + (this->z * this->z));
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXZF x=%g z=%g]", this->x.load(), this->z.load());
|
||||
}
|
||||
} __packed_ws__(VectorXZF, 0x08);
|
||||
|
||||
struct VectorXYZF {
|
||||
le_float x = 0.0;
|
||||
le_float y = 0.0;
|
||||
le_float z = 0.0;
|
||||
|
||||
inline operator VectorXZF() const {
|
||||
return VectorXZF{this->x, this->z};
|
||||
}
|
||||
|
||||
inline VectorXYZF operator-() const {
|
||||
return VectorXYZF{-this->x, -this->y, -this->z};
|
||||
}
|
||||
|
||||
inline VectorXYZF operator+(const VectorXYZF& other) const {
|
||||
return VectorXYZF{this->x + other.x, this->y + other.y, this->z + other.z};
|
||||
}
|
||||
inline VectorXYZF operator-(const VectorXYZF& other) const {
|
||||
return VectorXYZF{this->x - other.x, this->y - other.y, this->z - other.z};
|
||||
}
|
||||
|
||||
inline bool operator==(const VectorXYZF& other) const {
|
||||
return ((this->x == other.x) && (this->y == other.y) && (this->z == other.z));
|
||||
}
|
||||
inline bool operator!=(const VectorXYZF& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
inline double norm() const {
|
||||
return sqrt(this->norm2());
|
||||
}
|
||||
inline double norm2() const {
|
||||
return ((this->x * this->x) + (this->y * this->y) + (this->z * this->z));
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXYZF x=%g y=%g z=%g]", this->x.load(), this->y.load(), this->z.load());
|
||||
}
|
||||
} __packed_ws__(VectorXYZF, 0x0C);
|
||||
|
||||
struct VectorXYZTF {
|
||||
le_float x = 0.0;
|
||||
le_float y = 0.0;
|
||||
le_float z = 0.0;
|
||||
le_float t = 0.0;
|
||||
} __packed_ws__(VectorXYZTF, 0x10);
|
||||
|
||||
struct VectorXYZI {
|
||||
le_uint32_t x = 0;
|
||||
le_uint32_t y = 0;
|
||||
le_uint32_t z = 0;
|
||||
} __packed_ws__(VectorXYZI, 0x0C);
|
||||
|
||||
template <bool BE>
|
||||
struct ArrayRefT {
|
||||
static constexpr bool IsBE = BE;
|
||||
/* 00 */ U32T<BE> count;
|
||||
/* 04 */ U32T<BE> offset;
|
||||
/* 08 */
|
||||
} __packed__;
|
||||
using ArrayRef = ArrayRefT<false>;
|
||||
using ArrayRefBE = ArrayRefT<true>;
|
||||
check_struct_size(ArrayRef, 8);
|
||||
check_struct_size(ArrayRefBE, 8);
|
||||
|
||||
template <bool BE>
|
||||
struct RELFileFooterT {
|
||||
static constexpr bool IsBE = BE;
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) containing the number of doublewords (uint32_t) to skip for each
|
||||
// relocation. The relocation pointer starts immediately after the
|
||||
// checksum_size field in the header, and advances by the value of one
|
||||
// relocation word (times 4) before each relocation. At each relocated
|
||||
// doubleword, the address of the first byte of the code (after checksum_size)
|
||||
// is added to the existing value.
|
||||
// For example, if the code segment contains the following data (where R
|
||||
// specifies doublewords to relocate):
|
||||
// RR RR RR RR ?? ?? ?? ?? ?? ?? ?? ?? RR RR RR RR
|
||||
// RR RR RR RR ?? ?? ?? ?? RR RR RR RR
|
||||
// then the relocation words should be 0000, 0003, 0001, and 0002.
|
||||
// If there is a small number of relocations, they may be placed in the unused
|
||||
// fields of this structure to save space and/or confuse reverse engineers.
|
||||
// The game never accesses the last 12 bytes of this structure unless
|
||||
// relocations_offset points there, so those 12 bytes may also be omitted
|
||||
// entirely in situations (e.g. in the B2 command, without changing code_size,
|
||||
// so code_size would technically extend beyond the end of the B2 command).
|
||||
U32T<BE> relocations_offset = 0;
|
||||
U32T<BE> num_relocations = 0;
|
||||
parray<U32T<BE>, 2> unused1;
|
||||
U32T<BE> root_offset = 0;
|
||||
parray<U32T<BE>, 3> unused2;
|
||||
} __attribute__((packed));
|
||||
using RELFileFooter = RELFileFooterT<false>;
|
||||
using RELFileFooterBE = RELFileFooterT<true>;
|
||||
check_struct_size(RELFileFooter, 0x20);
|
||||
check_struct_size(RELFileFooterBE, 0x20);
|
||||
@@ -1121,3 +1121,51 @@ bool enemy_type_is_rare(EnemyType type) {
|
||||
(type == EnemyType::DORPHON_ECLAIR) ||
|
||||
(type == EnemyType::KONDRIEU));
|
||||
}
|
||||
|
||||
EnemyType rare_type_for_enemy_type(EnemyType base_type, Episode episode, uint8_t event, uint8_t floor) {
|
||||
switch (base_type) {
|
||||
case EnemyType::HILDEBEAR:
|
||||
return EnemyType::HILDEBLUE;
|
||||
case EnemyType::RAG_RAPPY:
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
return EnemyType::AL_RAPPY;
|
||||
case Episode::EP2:
|
||||
switch (event) {
|
||||
case 0x01: // rappy_type 1
|
||||
return EnemyType::SAINT_RAPPY;
|
||||
case 0x04: // rappy_type 2
|
||||
return EnemyType::EGG_RAPPY;
|
||||
case 0x05: // rappy_type 3
|
||||
return EnemyType::HALLO_RAPPY;
|
||||
default:
|
||||
return EnemyType::LOVE_RAPPY;
|
||||
}
|
||||
case Episode::EP4:
|
||||
return (floor > 0x05) ? EnemyType::DEL_RAPPY_ALT : EnemyType::DEL_RAPPY;
|
||||
default:
|
||||
throw logic_error("invalid episode");
|
||||
}
|
||||
case EnemyType::POISON_LILY:
|
||||
return EnemyType::NAR_LILY;
|
||||
case EnemyType::POFUILLY_SLIME:
|
||||
return EnemyType::POUILLY_SLIME;
|
||||
case EnemyType::SAND_RAPPY:
|
||||
return EnemyType::DEL_RAPPY;
|
||||
case EnemyType::SAND_RAPPY_ALT:
|
||||
return EnemyType::DEL_RAPPY_ALT;
|
||||
case EnemyType::MERISSA_A:
|
||||
return EnemyType::MERISSA_AA;
|
||||
case EnemyType::ZU:
|
||||
return EnemyType::PAZUZU;
|
||||
case EnemyType::ZU_ALT:
|
||||
return EnemyType::PAZUZU_ALT;
|
||||
case EnemyType::DORPHON:
|
||||
return EnemyType::DORPHON_ECLAIR;
|
||||
case EnemyType::SAINT_MILLION:
|
||||
case EnemyType::SHAMBERTIN:
|
||||
return EnemyType::KONDRIEU;
|
||||
default:
|
||||
return base_type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,3 +148,4 @@ uint8_t battle_param_index_for_enemy_type(Episode episode, EnemyType enemy_type)
|
||||
uint8_t rare_table_index_for_enemy_type(EnemyType enemy_type);
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index);
|
||||
bool enemy_type_is_rare(EnemyType type);
|
||||
EnemyType rare_type_for_enemy_type(EnemyType base_type, Episode episode, uint8_t event, uint8_t floor);
|
||||
|
||||
+22
-15
@@ -8,6 +8,7 @@
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "../CommonFileFormats.hh"
|
||||
#include "../Compression.hh"
|
||||
#include "../Loggers.hh"
|
||||
#include "../PSOEncryption.hh"
|
||||
@@ -2452,28 +2453,34 @@ CardIndex::CardIndex(
|
||||
this->compressed_card_definitions = phosg::load_file(filename);
|
||||
decompressed_data = prs_decompress(this->compressed_card_definitions);
|
||||
}
|
||||
|
||||
// The client can't handle files larger than this
|
||||
if (decompressed_data.size() > 0x36EC0) {
|
||||
throw runtime_error("decompressed card list data is too long");
|
||||
}
|
||||
|
||||
// There's a footer after the card definitions (it's a standard-format REL
|
||||
// file), but we ignore it
|
||||
if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)",
|
||||
decompressed_data.size(), sizeof(CardDefinition), decompressed_data.size() % sizeof(CardDefinition)));
|
||||
// The card definitions file is a standard REL file; the root offset points
|
||||
// to an ArrayRef which specifies an array of CardDefinition structs
|
||||
phosg::StringReader r(decompressed_data);
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
uint32_t offset = r.pget_u32b(footer.root_offset);
|
||||
uint32_t count = r.pget_u32b(footer.root_offset + 4);
|
||||
if (offset > decompressed_data.size() ||
|
||||
((offset + count * sizeof(CardDefinition)) > decompressed_data.size())) {
|
||||
throw runtime_error("definitions array reference out of bounds");
|
||||
}
|
||||
auto* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data());
|
||||
size_t max_cards = decompressed_data.size() / sizeof(CardDefinition);
|
||||
for (size_t x = 0; x < max_cards; x++) {
|
||||
CardDefinition* defs = reinterpret_cast<CardDefinition*>(decompressed_data.data() + offset);
|
||||
for (size_t x = 0; x < count; x++) {
|
||||
auto& def = defs[x];
|
||||
|
||||
// The last card entry has the build date and some other metadata (and
|
||||
// isn't a real card, obviously), so skip it. The game detects this by
|
||||
// checking for a negative value in type, which we also do here.
|
||||
if (static_cast<int8_t>(defs[x].type) < 0) {
|
||||
if (static_cast<int8_t>(def.type) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry = make_shared<CardEntry>(CardEntry{defs[x], "", "", "", {}});
|
||||
auto entry = make_shared<CardEntry>(CardEntry{def, "", "", "", {}});
|
||||
if (!this->card_definitions.emplace(entry->def.card_id, entry).second) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"duplicate card id: %08" PRIX32, entry->def.card_id.load()));
|
||||
@@ -2493,17 +2500,17 @@ CardIndex::CardIndex(
|
||||
|
||||
if (!text_filename.empty() || !decompressed_text_filename.empty()) {
|
||||
try {
|
||||
entry->text = std::move(card_text.at(defs[x].card_id));
|
||||
entry->text = std::move(card_text.at(def.card_id));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
try {
|
||||
entry->debug_tags = std::move(card_tags.at(defs[x].card_id));
|
||||
entry->debug_tags = std::move(card_tags.at(def.card_id));
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
if (!dice_text_filename.empty() || !decompressed_dice_text_filename.empty()) {
|
||||
try {
|
||||
auto& dice_text_it = card_dice_text.at(defs[x].card_id);
|
||||
auto& dice_text_it = card_dice_text.at(def.card_id);
|
||||
entry->dice_caption = std::move(dice_text_it.first);
|
||||
entry->dice_text = std::move(dice_text_it.second);
|
||||
} catch (const out_of_range&) {
|
||||
@@ -2523,7 +2530,7 @@ CardIndex::CardIndex(
|
||||
if (this->compressed_card_definitions.size() > 0x7BF8) {
|
||||
// Try to reduce the compressed size by clearing out text
|
||||
static_game_data_log.info("Compressed card list data is too long (0x%zX bytes); removing text", this->compressed_card_definitions.size());
|
||||
for (size_t x = 0; x < max_cards; x++) {
|
||||
for (size_t x = 0; x < count; x++) {
|
||||
if (static_cast<int8_t>(defs[x].type) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../CommonFileFormats.hh"
|
||||
#include "../PlayerSubordinates.hh"
|
||||
#include "../Text.hh"
|
||||
#include "../TextIndex.hh"
|
||||
@@ -814,18 +815,12 @@ struct CardDefinition {
|
||||
} __packed_ws__(CardDefinition, 0x128);
|
||||
|
||||
struct CardDefinitionsFooter {
|
||||
// Technically the card definitions file is a REL file, so the last 0x20 bytes
|
||||
// here should be a separate structure.
|
||||
/* 00 */ be_uint32_t num_cards1;
|
||||
/* 04 */ be_uint32_t cards_offset; // == 0
|
||||
/* 08 */ be_uint32_t num_cards2;
|
||||
/* 0C */ parray<be_uint32_t, 3> unknown_a2;
|
||||
/* 18 */ parray<be_uint16_t, 0x10> relocations;
|
||||
/* 38 */ be_uint32_t relocations_offset;
|
||||
/* 3C */ be_uint32_t num_relocations;
|
||||
/* 40 */ parray<be_uint32_t, 2> unused1;
|
||||
/* 48 */ be_uint32_t footer_offset;
|
||||
/* 4C */ parray<be_uint32_t, 3> unused2;
|
||||
/* 38 */ RELFileFooterBE rel_footer;
|
||||
/* 58 */
|
||||
} __packed_ws__(CardDefinitionsFooter, 0x58);
|
||||
|
||||
|
||||
@@ -2335,7 +2335,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
if (l) {
|
||||
// Note: Sega's implementation doesn't set EX results values here; they
|
||||
// did it at game join time instead. We do it here for code simplicity.
|
||||
if ((l->base_version != Version::GC_EP3_NTE) && l->ep3_ex_result_values) {
|
||||
if (!this->options.is_nte() && l->ep3_ex_result_values) {
|
||||
this->send(*l->ep3_ex_result_values);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#endif
|
||||
|
||||
#include "CommandFormats.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
@@ -47,16 +48,18 @@ const char* name_for_architecture(CompiledFunctionCode::Architecture arch) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename FooterT>
|
||||
template <bool BE>
|
||||
string CompiledFunctionCode::generate_client_command_t(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
FooterT footer;
|
||||
footer.num_relocations = this->relocation_deltas.size();
|
||||
footer.unused1.clear(0);
|
||||
footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
|
||||
footer.root_offset = this->entrypoint_offset_offset;
|
||||
footer.unused2.clear(0);
|
||||
|
||||
phosg::StringWriter w;
|
||||
@@ -108,10 +111,10 @@ string CompiledFunctionCode::generate_client_command(
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
if (this->arch == Architecture::POWERPC) {
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_GC_B2>(
|
||||
return this->generate_client_command_t<true>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<S_ExecuteCode_Footer_DC_PC_XB_BB_B2>(
|
||||
return this->generate_client_command_t<false>(
|
||||
label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
|
||||
@@ -40,7 +40,7 @@ struct CompiledFunctionCode {
|
||||
|
||||
bool is_big_endian() const;
|
||||
|
||||
template <typename FooterT>
|
||||
template <bool BE>
|
||||
std::string generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data = nullptr,
|
||||
|
||||
+10
-14
@@ -550,8 +550,8 @@ phosg::JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> c,
|
||||
{"SubVersion", c->sub_version},
|
||||
{"Config", HTTPServer::generate_client_config_json_st(c->config)},
|
||||
{"Language", name_for_language_code(c->language())},
|
||||
{"LocationX", c->x},
|
||||
{"LocationZ", c->z},
|
||||
{"LocationX", c->pos.x.load()},
|
||||
{"LocationZ", c->pos.z.load()},
|
||||
{"LocationFloor", c->floor},
|
||||
{"CanChat", c->can_chat},
|
||||
});
|
||||
@@ -735,8 +735,8 @@ phosg::JSON HTTPServer::generate_proxy_client_json_st(shared_ptr<const ProxyServ
|
||||
{"Language", name_for_language_code(ses->language())},
|
||||
{"LobbyClientID", ses->lobby_client_id},
|
||||
{"LeaderClientID", ses->leader_client_id},
|
||||
{"LocationX", ses->x},
|
||||
{"LocationZ", ses->z},
|
||||
{"LocationX", ses->pos.x.load()},
|
||||
{"LocationZ", ses->pos.z.load()},
|
||||
{"LocationFloor", ses->floor},
|
||||
{"IsInGame", ses->is_in_game},
|
||||
{"IsInQuest", ses->is_in_quest},
|
||||
@@ -787,7 +787,6 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
ret.emplace("CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED));
|
||||
ret.emplace("MinLevel", l->min_level + 1);
|
||||
ret.emplace("MaxLevel", l->max_level + 1);
|
||||
ret.emplace("BaseVersion", l->base_version);
|
||||
ret.emplace("Episode", name_for_episode(l->episode));
|
||||
ret.emplace("HasPassword", !l->password.empty());
|
||||
ret.emplace("Name", l->name);
|
||||
@@ -796,11 +795,7 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
ret.emplace("QuestSelectionInProgress", l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS));
|
||||
ret.emplace("QuestInProgress", l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS));
|
||||
ret.emplace("JoinableQuestInProgress", l->check_flag(Lobby::Flag::JOINABLE_QUEST_IN_PROGRESS));
|
||||
auto variations_json = phosg::JSON::list();
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_json.emplace_back(l->variations[z].load());
|
||||
}
|
||||
ret.emplace("Variations", std::move(variations_json));
|
||||
ret.emplace("Variations", l->variations.json());
|
||||
ret.emplace("SectionID", name_for_section_id(l->effective_section_id()));
|
||||
ret.emplace("Mode", name_for_mode(l->mode));
|
||||
ret.emplace("Difficulty", name_for_difficulty(l->difficulty));
|
||||
@@ -845,8 +840,8 @@ phosg::JSON HTTPServer::generate_lobby_json_st(shared_ptr<const Lobby> l, shared
|
||||
const auto& item = it.second;
|
||||
auto item_dict = phosg::JSON::dict({
|
||||
{"LocationFloor", floor},
|
||||
{"LocationX", item->x},
|
||||
{"LocationZ", item->z},
|
||||
{"LocationX", item->pos.x.load()},
|
||||
{"LocationZ", item->pos.z.load()},
|
||||
{"DropNumber", item->drop_number},
|
||||
{"Flags", item->flags},
|
||||
{"Data", item->data.hex()},
|
||||
@@ -1016,7 +1011,9 @@ phosg::JSON HTTPServer::generate_lobbies_json() const {
|
||||
return call_on_event_thread<phosg::JSON>(this->state->base, [&]() {
|
||||
phosg::JSON res = phosg::JSON::list();
|
||||
for (const auto& it : this->state->id_to_lobby) {
|
||||
res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(it.second->base_version)));
|
||||
auto leader = it.second->clients[it.second->leader_id];
|
||||
Version v = leader ? leader->version() : Version::BB_V4;
|
||||
res.emplace_back(this->generate_lobby_json_st(it.second, this->state->item_name_index_opt(v)));
|
||||
}
|
||||
return res;
|
||||
});
|
||||
@@ -1061,7 +1058,6 @@ phosg::JSON HTTPServer::generate_summary_json() const {
|
||||
auto game_json = phosg::JSON::dict({
|
||||
{"ID", l->lobby_id},
|
||||
{"Name", l->name},
|
||||
{"BaseVersion", phosg::name_for_enum(l->base_version)},
|
||||
{"Players", l->count_clients()},
|
||||
{"CheatsEnabled", l->check_flag(Lobby::Flag::CHEATS_ENABLED)},
|
||||
{"Episode", name_for_episode(l->episode)},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "ItemParameterTable.hh"
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
ItemParameterTable::ItemParameterTable(shared_ptr<const string> data, Version version)
|
||||
@@ -416,15 +418,12 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T<BE>::to_v4() const {
|
||||
|
||||
template <bool BE>
|
||||
size_t indirect_lookup_2d_count(const phosg::StringReader& r, size_t root_offset, size_t co_index) {
|
||||
using ArrayRefT = typename std::conditional_t<BE, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
|
||||
return r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index).count;
|
||||
return r.pget<ArrayRefT<BE>>(root_offset + sizeof(ArrayRefT<BE>) * co_index).count;
|
||||
}
|
||||
|
||||
template <typename T, bool BE>
|
||||
const T& indirect_lookup_2d(const phosg::StringReader& r, size_t root_offset, size_t co_index, size_t item_index) {
|
||||
using ArrayRefT = typename std::conditional_t<BE, ItemParameterTable::ArrayRefBE, ItemParameterTable::ArrayRef>;
|
||||
|
||||
const auto& co = r.pget<ArrayRefT>(root_offset + sizeof(ArrayRefT) * co_index);
|
||||
const auto& co = r.pget<ArrayRefT<BE>>(root_offset + sizeof(ArrayRefT<BE>) * co_index);
|
||||
if (item_index >= co.count) {
|
||||
throw out_of_range("item ID out of range");
|
||||
}
|
||||
|
||||
@@ -19,18 +19,6 @@ public:
|
||||
// functions instead of manually branching on various offset table pointers
|
||||
// being null or not in each public function. Rewrite this and make it better.
|
||||
|
||||
template <bool BE>
|
||||
struct ArrayRefT {
|
||||
/* 00 */ U32T<BE> count;
|
||||
/* 04 */ U32T<BE> offset;
|
||||
/* 08 */
|
||||
} __packed__;
|
||||
|
||||
using ArrayRef = ArrayRefT<false>;
|
||||
using ArrayRefBE = ArrayRefT<true>;
|
||||
check_struct_size(ArrayRef, 8);
|
||||
check_struct_size(ArrayRefBE, 8);
|
||||
|
||||
template <bool BE>
|
||||
struct ItemBaseV2T {
|
||||
// id specifies several things; notably, it doubles as the index of the
|
||||
|
||||
+7
-2
@@ -189,9 +189,14 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
item.data.data1.clear_after(3);
|
||||
should_delete_item = false;
|
||||
|
||||
// TODO: It seems that on non-BB, clients don't synchronize this at all
|
||||
// so they could end up thinking the unwrapped item is something
|
||||
// completely different. How does this actually work on console PSO?
|
||||
auto l = c->require_lobby();
|
||||
if (l->base_version == Version::BB_V4) {
|
||||
send_create_inventory_item_to_lobby(c, c->lobby_client_id, item.data);
|
||||
for (const auto& lc : l->clients) {
|
||||
if (lc && (lc->version() == Version::BB_V4)) {
|
||||
send_create_inventory_item_to_client(c, c->lobby_client_id, item.data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+7
-3
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
@@ -63,7 +64,8 @@ LevelTableV2::LevelTableV2(const string& data, bool compressed) {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 9>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 9; char_class++) {
|
||||
@@ -112,7 +114,8 @@ LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
// u32 offset:
|
||||
// u32[12] offsets:
|
||||
// LevelStatsDeltaBE[200] level_deltas
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(r.pget_u32b(r.size() - 0x10)));
|
||||
const auto& footer = r.pget<RELFileFooterBE>(r.size() - sizeof(RELFileFooterBE));
|
||||
const auto& offsets = r.pget<parray<be_uint32_t, 12>>(r.pget_u32b(footer.root_offset));
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
const auto& src_deltas = r.pget<parray<LevelStatsDeltaBE, 200>>(offsets[char_class]);
|
||||
for (size_t level = 0; level < 200; level++) {
|
||||
@@ -189,7 +192,8 @@ LevelTableV4::LevelTableV4(const string& data, bool compressed) {
|
||||
r = phosg::StringReader(data);
|
||||
}
|
||||
|
||||
const auto& offsets = r.pget<Offsets>(r.pget_u32l(r.size() - 0x10));
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& offsets = r.pget<Offsets>(footer.root_offset);
|
||||
const auto& level_deltas_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.level_deltas);
|
||||
const auto& base_stats_offsets = r.pget<parray<le_uint32_t, 12>>(offsets.base_stats);
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
|
||||
+70
-218
@@ -27,12 +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 flags) {
|
||||
void Lobby::FloorItemManager::add(
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
shared_ptr<const MapState::ObjectState> from_obj,
|
||||
shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags) {
|
||||
auto fi = make_shared<FloorItem>();
|
||||
fi->data = item;
|
||||
fi->x = x;
|
||||
fi->z = z;
|
||||
fi->pos = pos;
|
||||
fi->drop_number = this->next_drop_number++;
|
||||
fi->from_obj = from_obj;
|
||||
fi->from_ene = from_ene;
|
||||
fi->flags = flags;
|
||||
this->add(fi);
|
||||
}
|
||||
@@ -52,7 +58,7 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
||||
}
|
||||
}
|
||||
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);
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
}
|
||||
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
||||
@@ -71,7 +77,7 @@ 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 " with flags %03hX",
|
||||
fi->data.id.load(), fi->x, fi->z, fi->drop_number, fi->flags);
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
return fi;
|
||||
}
|
||||
|
||||
@@ -142,7 +148,6 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
min_level(0),
|
||||
max_level(0xFFFFFFFF),
|
||||
next_game_item_id(0xCC000000),
|
||||
base_version(Version::GC_V3),
|
||||
allowed_versions(0x0000),
|
||||
override_section_id(0xFF),
|
||||
episode(Episode::NONE),
|
||||
@@ -196,24 +201,22 @@ shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
||||
return this->challenge_params;
|
||||
}
|
||||
|
||||
void Lobby::set_drop_mode(DropMode new_mode) {
|
||||
this->drop_mode = new_mode;
|
||||
|
||||
bool should_have_item_creator = (this->base_version == Version::BB_V4) ||
|
||||
((new_mode != DropMode::DISABLED) && (new_mode != DropMode::CLIENT));
|
||||
if (should_have_item_creator && !this->item_creator) {
|
||||
this->create_item_creator();
|
||||
} else if (!should_have_item_creator && this->item_creator) {
|
||||
void Lobby::create_item_creator(Version logic_version) {
|
||||
if (!this->is_game() || this->episode == Episode::EP3) {
|
||||
this->item_creator.reset();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::create_item_creator() {
|
||||
auto s = this->require_server_state();
|
||||
|
||||
if (logic_version == Version::UNKNOWN) {
|
||||
auto leader_c = this->clients[this->leader_id];
|
||||
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
|
||||
}
|
||||
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
shared_ptr<const CommonItemSet> common_item_set;
|
||||
switch (this->base_version) {
|
||||
switch (logic_version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::GC_EP3_NTE:
|
||||
@@ -245,6 +248,7 @@ void Lobby::create_item_creator() {
|
||||
default:
|
||||
throw logic_error("invalid lobby base version");
|
||||
}
|
||||
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
common_item_set,
|
||||
rare_item_set,
|
||||
@@ -252,8 +256,8 @@ void Lobby::create_item_creator() {
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table(this->base_version),
|
||||
s->item_stack_limits(this->base_version),
|
||||
s->item_parameter_table(logic_version),
|
||||
s->item_stack_limits(logic_version),
|
||||
this->episode,
|
||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||
this->difficulty,
|
||||
@@ -262,21 +266,6 @@ void Lobby::create_item_creator() {
|
||||
this->quest ? this->quest->battle_rules : nullptr);
|
||||
}
|
||||
|
||||
void Lobby::change_section_id() {
|
||||
if (this->item_creator) {
|
||||
uint8_t new_section_id = this->effective_section_id();
|
||||
if (this->item_creator->get_section_id() != new_section_id) {
|
||||
this->log.info("Changing section ID to %s", name_for_section_id(new_section_id));
|
||||
this->item_creator->set_section_id(new_section_id);
|
||||
for (const auto& c : this->clients) {
|
||||
if (c && c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
send_text_message_printf(c, "$C5Section ID changed\nto %s (%hhu)", name_for_section_id(new_section_id), new_section_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Lobby::effective_section_id() const {
|
||||
if (this->override_section_id != 0xFF) {
|
||||
return this->override_section_id;
|
||||
@@ -291,199 +280,55 @@ uint8_t Lobby::effective_section_id() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
shared_ptr<const string> quest_dat_contents_decompressed) {
|
||||
auto map = make_shared<Map>(version, lobby_id, random_seed, opt_rand_crypt);
|
||||
map->add_entities_from_quest_data(episode, difficulty, event, quest_dat_contents_decompressed, rare_rates);
|
||||
return map;
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
shared_ptr<const SetDataTableBase> sdt,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const phosg::PrefixedLogger* log) {
|
||||
auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::ENEMIES);
|
||||
auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::OBJECTS);
|
||||
auto event_filenames = sdt->map_filenames_for_variations(variations, episode, mode, SetDataTable::FilenameType::EVENTS);
|
||||
return Lobby::load_maps(
|
||||
enemy_filenames,
|
||||
object_filenames,
|
||||
event_filenames,
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
difficulty,
|
||||
event,
|
||||
lobby_id,
|
||||
get_file_data,
|
||||
rare_rates,
|
||||
random_seed,
|
||||
opt_rand_crypt,
|
||||
log);
|
||||
}
|
||||
|
||||
shared_ptr<Map> Lobby::load_maps(
|
||||
const vector<string>& enemy_filenames,
|
||||
const vector<string>& object_filenames,
|
||||
const vector<string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
function<shared_ptr<const string>(Version, const string&)> get_file_data,
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t rare_seed,
|
||||
shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const phosg::PrefixedLogger* log) {
|
||||
auto map = make_shared<Map>(version, lobby_id, rare_seed, opt_rand_crypt);
|
||||
|
||||
// Don't load free-roam maps in Challenge mode, since players can't go to
|
||||
// Ragol without a quest loaded
|
||||
if (mode == GameMode::CHALLENGE) {
|
||||
return map;
|
||||
}
|
||||
|
||||
for (size_t floor = 0; floor < 0x12; floor++) {
|
||||
const auto& floor_enemy_filename = enemy_filenames.at(floor);
|
||||
if (!floor_enemy_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_enemy_filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode,
|
||||
difficulty,
|
||||
event,
|
||||
floor,
|
||||
map_data->data(),
|
||||
map_data->size(),
|
||||
rare_rates);
|
||||
if (log) {
|
||||
log->info("Loaded enemies map %s for floor %02zX", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Enemies map %s for floor %02zX cannot be used; skipping", floor_enemy_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No enemies to load for floor %02zX", floor);
|
||||
}
|
||||
|
||||
const auto& floor_object_filename = object_filenames.at(floor);
|
||||
if (!floor_object_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_object_filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data);
|
||||
if (log) {
|
||||
log->info("Loaded objects map %s for floor %02zX", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Objects map %s for floor %02zX cannot be used; skipping", floor_object_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No objects to load for floor %02zX", floor);
|
||||
}
|
||||
|
||||
const auto& floor_event_filename = event_filenames.at(floor);
|
||||
if (!floor_event_filename.empty()) {
|
||||
auto map_data = get_file_data(version, floor_event_filename);
|
||||
if (map_data) {
|
||||
map->add_events_from_map_data(floor, map_data->data(), map_data->size());
|
||||
if (log) {
|
||||
log->info("Loaded events map %s for floor %02zX", floor_event_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("Events map %s for floor %02zX cannot be used; skipping", floor_event_filename.c_str(), floor);
|
||||
}
|
||||
} else if (log) {
|
||||
log->info("No events to load for floor %02zX", floor);
|
||||
uint16_t Lobby::quest_version_flags() const {
|
||||
uint16_t ret = 0;
|
||||
for (auto lc : this->clients) {
|
||||
if (lc) {
|
||||
ret |= (1 << static_cast<size_t>(lc->version()));
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Lobby::load_maps() {
|
||||
auto rare_rates = ((this->base_version == Version::BB_V4) && this->rare_enemy_rates)
|
||||
? this->rare_enemy_rates
|
||||
: Map::DEFAULT_RARE_ENEMIES;
|
||||
auto rare_rates = this->rare_enemy_rates ? this->rare_enemy_rates : MapState::DEFAULT_RARE_ENEMIES;
|
||||
|
||||
if (this->quest) {
|
||||
auto leader_c = this->clients.at(this->leader_id);
|
||||
if (!leader_c) {
|
||||
throw logic_error("lobby leader is missing");
|
||||
}
|
||||
|
||||
auto vq = this->quest->version(this->base_version, leader_c->language());
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
this->map = this->load_maps(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
if (this->episode == Episode::EP3) {
|
||||
this->map_state = make_shared<MapState>();
|
||||
} else if (this->quest) {
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->lobby_id,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->lobby_id,
|
||||
rare_rates,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
vq->dat_contents_decompressed);
|
||||
|
||||
} else if (this->mode != GameMode::CHALLENGE) {
|
||||
auto s = this->require_server_state();
|
||||
this->map = this->load_maps(
|
||||
this->base_version,
|
||||
this->episode,
|
||||
this->mode,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->lobby_id,
|
||||
s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
this->random_seed,
|
||||
this->opt_rand_crypt,
|
||||
this->variations,
|
||||
&this->log);
|
||||
|
||||
this->quest->get_supermap(this->random_seed));
|
||||
} else {
|
||||
this->map = make_shared<Map>(this->base_version, this->lobby_id, this->random_seed, this->opt_rand_crypt);
|
||||
auto s = this->require_server_state();
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->lobby_id,
|
||||
this->difficulty,
|
||||
this->event,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations));
|
||||
}
|
||||
|
||||
this->log.info("Generated objects list (%zu entries):", this->map->objects.size());
|
||||
for (size_t z = 0; z < this->map->objects.size(); z++) {
|
||||
string o_str = this->map->objects[z].str();
|
||||
this->log.info("(K-%zX) %s", z, o_str.c_str());
|
||||
if (this->check_flag(Lobby::Flag::DEBUG)) {
|
||||
this->log.info("Generated map state:");
|
||||
this->map_state->print(stderr);
|
||||
}
|
||||
this->log.info("Generated enemies list (%zu entries):", this->map->enemies.size());
|
||||
for (size_t z = 0; z < this->map->enemies.size(); z++) {
|
||||
string e_str = this->map->enemies[z].str();
|
||||
this->log.info("(E-%zX) %s", z, e_str.c_str());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Lobby::is_ep3_nte() const {
|
||||
for (const auto& lc : this->clients) {
|
||||
if (lc && (lc->version() != Version::GC_EP3_NTE)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this->log.info("Generated events list (%zu entries):", this->map->events.size());
|
||||
for (size_t z = 0; z < this->map->events.size(); z++) {
|
||||
string e_str = this->map->events[z].str();
|
||||
this->log.info("%s", e_str.c_str());
|
||||
}
|
||||
this->log.info("Loaded maps contain %zu object entries and %zu enemy entries overall (%zu as rares)",
|
||||
this->map->objects.size(), this->map->enemies.size(), this->map->rare_enemy_indexes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lobby::create_ep3_server() {
|
||||
@@ -494,7 +339,8 @@ void Lobby::create_ep3_server() {
|
||||
this->log.info("Recreating Episode 3 server state");
|
||||
}
|
||||
auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr;
|
||||
bool is_nte = this->base_version == Version::GC_EP3_NTE;
|
||||
|
||||
bool is_nte = this->is_ep3_nte();
|
||||
Episode3::Server::Options options = {
|
||||
.card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index,
|
||||
.map_index = s->ep3_map_index,
|
||||
@@ -504,7 +350,7 @@ void Lobby::create_ep3_server() {
|
||||
.tournament = tourn,
|
||||
.trap_card_ids = s->ep3_trap_card_ids,
|
||||
};
|
||||
if (this->base_version == Version::GC_EP3_NTE) {
|
||||
if (is_nte) {
|
||||
options.behavior_flags |= Episode3::BehaviorFlag::IS_TRIAL_EDITION;
|
||||
} else {
|
||||
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
||||
@@ -520,7 +366,7 @@ void Lobby::reassign_leader_on_client_departure(size_t leaving_client_index) {
|
||||
}
|
||||
if (this->clients[x]) {
|
||||
this->leader_id = x;
|
||||
this->change_section_id();
|
||||
this->create_item_creator();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -611,7 +457,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
if (leader_index >= this->max_clients) {
|
||||
this->leader_id = c->lobby_client_id;
|
||||
this->change_section_id();
|
||||
this->create_item_creator();
|
||||
}
|
||||
|
||||
// If this is a lobby or no one was here before this, reassign all the floor
|
||||
@@ -850,9 +696,15 @@ 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 flags) {
|
||||
void Lobby::add_item(
|
||||
uint8_t floor,
|
||||
const ItemData& data,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags) {
|
||||
auto& m = this->floor_item_managers.at(floor);
|
||||
m.add(data, x, z, flags);
|
||||
m.add(data, pos, from_obj, from_ene, flags);
|
||||
this->evict_items_from_floor(floor);
|
||||
}
|
||||
|
||||
|
||||
+28
-51
@@ -26,9 +26,11 @@ struct ServerState;
|
||||
struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
struct FloorItem {
|
||||
ItemData data;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
uint64_t drop_number;
|
||||
// At most one of the following will be non-null
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj;
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene;
|
||||
// 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.)
|
||||
@@ -52,7 +54,12 @@ 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 flags);
|
||||
void add(
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
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();
|
||||
@@ -65,6 +72,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
// clang-format off
|
||||
GAME = 0x00000001,
|
||||
PERSISTENT = 0x00000002,
|
||||
DEBUG = 0x00000004,
|
||||
// Flags used only for games
|
||||
CHEATS_ENABLED = 0x00000100,
|
||||
QUEST_SELECTION_IN_PROGRESS = 0x00000200,
|
||||
@@ -103,15 +111,14 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::array<uint32_t, 12> next_item_id_for_client;
|
||||
uint32_t next_game_item_id;
|
||||
std::vector<FloorItemManager> floor_item_managers;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<Map> map;
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates;
|
||||
std::shared_ptr<MapState> map_state; // Always null for lobbies, never null for games
|
||||
Variations variations;
|
||||
std::unique_ptr<QuestFlags> quest_flags_known; // If null, ALL quest flags are known
|
||||
std::unique_ptr<QuestFlags> quest_flag_values;
|
||||
std::unique_ptr<SwitchFlags> switch_flags;
|
||||
|
||||
// Game config
|
||||
Version base_version;
|
||||
// Bits in allowed_versions specify who is allowed to join this game. The
|
||||
// bits are indexed as (1 << version), where version is a value from the
|
||||
// Version enum.
|
||||
@@ -131,7 +138,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
uint8_t allowed_drop_modes;
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<ItemCreator> item_creator; // Always null for lobbies, never null for games
|
||||
|
||||
struct ChallengeParameters {
|
||||
uint8_t stage_number = 0;
|
||||
@@ -204,55 +211,16 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
std::shared_ptr<ChallengeParameters> require_challenge_params() const;
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
void create_item_creator();
|
||||
void change_section_id();
|
||||
void create_item_creator(Version logic_version = Version::UNKNOWN);
|
||||
uint8_t effective_section_id() const;
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<const std::string> quest_dat_contents_decompressed);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::shared_ptr<const SetDataTableBase> sdt,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const parray<le_uint32_t, 0x20>& variations,
|
||||
const phosg::PrefixedLogger* log = nullptr);
|
||||
static std::shared_ptr<Map> load_maps(
|
||||
const std::vector<std::string>& enemy_filenames,
|
||||
const std::vector<std::string>& object_filenames,
|
||||
const std::vector<std::string>& event_filenames,
|
||||
Version version,
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t lobby_id,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_file_data,
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_rates,
|
||||
uint32_t random_seed,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
const phosg::PrefixedLogger* log = nullptr);
|
||||
uint16_t quest_version_flags() const;
|
||||
void load_maps();
|
||||
void create_ep3_server();
|
||||
|
||||
[[nodiscard]] inline bool is_game() const {
|
||||
return this->check_flag(Flag::GAME);
|
||||
}
|
||||
[[nodiscard]] bool is_ep3_nte() const;
|
||||
[[nodiscard]] inline bool is_ep3() const {
|
||||
return this->episode == Episode::EP3;
|
||||
}
|
||||
@@ -263,6 +231,9 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
inline void allow_version(Version v) {
|
||||
this->allowed_versions |= (1 << static_cast<size_t>(v));
|
||||
}
|
||||
inline void forbid_version(Version v) {
|
||||
this->allowed_versions &= ~(1 << static_cast<size_t>(v));
|
||||
}
|
||||
|
||||
void reassign_leader_on_client_departure(size_t leaving_client_id);
|
||||
size_t count_clients() const;
|
||||
@@ -297,7 +268,13 @@ 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 flags);
|
||||
void add_item(
|
||||
uint8_t floor,
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
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);
|
||||
|
||||
+113
-233
@@ -1407,7 +1407,8 @@ Action a_encode_qst(
|
||||
pvr_data = make_shared<string>(phosg::load_file(pvr_filename));
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
}
|
||||
auto vq = make_shared<VersionedQuest>(0, 0, version, 0, bin_data, dat_data, pvr_data);
|
||||
|
||||
auto vq = make_shared<VersionedQuest>(0, 0, version, 0, bin_data, dat_data, nullptr, pvr_data);
|
||||
if (download) {
|
||||
vq = vq->create_download_quest();
|
||||
}
|
||||
@@ -1446,21 +1447,22 @@ Action a_disassemble_quest_script(
|
||||
});
|
||||
Action a_disassemble_quest_map(
|
||||
"disassemble-quest-map", "\
|
||||
disassemble-quest-map [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
disassemble-quest-map [OPTIONS] [INPUT-FILENAME [OUTPUT-FILENAME]]\n\
|
||||
Disassemble the input quest map (.dat file) into a text representation of\n\
|
||||
the data it contains.\n",
|
||||
the data it contains. If --decompressed is given, don\'t decompress before\n\
|
||||
disassembling.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
string data = read_input_data(args);
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
if (!args.get<bool>("decompressed")) {
|
||||
data = prs_decompress(data);
|
||||
*data = prs_decompress(*data);
|
||||
}
|
||||
string result = Map::disassemble_quest_data(data.data(), data.size());
|
||||
string result = MapFile(data).disassemble();
|
||||
write_output_data(args, result.data(), result.size(), "txt");
|
||||
});
|
||||
Action a_disassemble_free_map(
|
||||
"disassemble-free-map", "\
|
||||
disassemble-free-map INPUT-FILENAME [OUTPUT-FILENAME]\n\
|
||||
Disassemble the input free-roam map (.dat or .evt file) into a text\n\
|
||||
Disassemble the input free-play map (.dat or .evt file) into a text\n\
|
||||
representation of the data it contains. Unlike othe disassembly actions,\n\
|
||||
this action expects its input to be already decompressed. If the input is\n\
|
||||
compressed, use the --compressed option. Also unlike other options, the\n\
|
||||
@@ -1475,18 +1477,19 @@ Action a_disassemble_free_map(
|
||||
throw runtime_error("cannot determine input file type");
|
||||
}
|
||||
|
||||
string data = read_input_data(args);
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
if (args.get<bool>("compressed")) {
|
||||
data = prs_decompress(data);
|
||||
*data = prs_decompress(*data);
|
||||
}
|
||||
|
||||
uint8_t floor = args.get<uint8_t>("floor", 0);
|
||||
string result;
|
||||
if (is_objects) {
|
||||
result = Map::disassemble_objects_data(data.data(), data.size());
|
||||
result = MapFile(floor, data, nullptr, nullptr).disassemble();
|
||||
} else if (is_enemies) {
|
||||
result = Map::disassemble_enemies_data(data.data(), data.size());
|
||||
result = MapFile(floor, nullptr, data, nullptr).disassemble();
|
||||
} else if (is_events) {
|
||||
result = Map::disassemble_wave_events_data(data.data(), data.size());
|
||||
result = MapFile(floor, nullptr, nullptr, data).disassemble();
|
||||
} else {
|
||||
throw logic_error("unhandled input type");
|
||||
}
|
||||
@@ -1912,7 +1915,7 @@ Action a_convert_rare_item_set(
|
||||
auto data = make_shared<string>(read_input_data(args));
|
||||
shared_ptr<RareItemSet> rs;
|
||||
if (phosg::ends_with(input_filename, ".json")) {
|
||||
rs = make_shared<RareItemSet>(phosg::JSON::parse(*data), s->item_name_index(version));
|
||||
rs = make_shared<RareItemSet>(phosg::JSON::parse(*data), s->item_name_index_opt(version));
|
||||
} else if (phosg::ends_with(input_filename, ".gsl")) {
|
||||
rs = make_shared<RareItemSet>(GSLArchive(data, false), false);
|
||||
} else if (phosg::ends_with(input_filename, ".gslb")) {
|
||||
@@ -1931,9 +1934,9 @@ Action a_convert_rare_item_set(
|
||||
|
||||
string output_filename = args.get<string>(2, false);
|
||||
if (output_filename.empty() || (output_filename == "-")) {
|
||||
rs->print_all_collections(stdout, s->item_name_index(version));
|
||||
rs->print_all_collections(stdout, s->item_name_index_opt(version));
|
||||
} else if (phosg::ends_with(output_filename, ".json")) {
|
||||
auto json = rs->json(s->item_name_index(version));
|
||||
auto json = rs->json(s->item_name_index_opt(version));
|
||||
string data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::SORT_DICT_KEYS);
|
||||
write_output_data(args, data.data(), data.size(), nullptr);
|
||||
} else if (phosg::ends_with(output_filename, ".gsl")) {
|
||||
@@ -2088,23 +2091,21 @@ Action a_name_all_items(
|
||||
}
|
||||
|
||||
fprintf(stderr, "IDENT :");
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
Version version = static_cast<Version>(v_s);
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
fprintf(stderr, " %30s ", phosg::name_for_enum(version));
|
||||
fprintf(stderr, " %30s ", phosg::name_for_enum(v));
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
|
||||
for (uint32_t primary_identifier : all_primary_identifiers) {
|
||||
fprintf(stderr, "%08" PRIX32 ":", primary_identifier);
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
Version version = static_cast<Version>(v_s);
|
||||
auto pmt = s->item_parameter_table(version);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(version), primary_identifier);
|
||||
auto pmt = s->item_parameter_table(v);
|
||||
ItemData item = ItemData::from_primary_identifier(*s->item_stack_limits(v), primary_identifier);
|
||||
string name = index->describe_item(item);
|
||||
try {
|
||||
bool is_rare = pmt->is_item_rare(item);
|
||||
@@ -2212,10 +2213,9 @@ Action a_print_item_parameter_tables(
|
||||
s->load_text_index(false);
|
||||
s->load_item_definitions(false);
|
||||
s->load_item_name_indexes(false);
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
const auto& index = s->item_name_indexes.at(v_s);
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
const auto& index = s->item_name_index_opt(v);
|
||||
if (index) {
|
||||
Version v = static_cast<Version>(v_s);
|
||||
fprintf(stdout, "======== %s\n", phosg::name_for_enum(v));
|
||||
index->print_table(stdout);
|
||||
}
|
||||
@@ -2569,224 +2569,104 @@ Action a_show_battle_params(
|
||||
s->battle_params->get_table(true, Episode::EP4).print(stdout);
|
||||
});
|
||||
|
||||
Action a_find_rare_enemy_seeds(
|
||||
"find-rare-enemy-seeds", "\
|
||||
find-rare-enemy-seeds OPTIONS...\n\
|
||||
Search all possible rare seeds to find those that produce one or more rare\n\
|
||||
enemies in any set of variations. A version option (e.g. --gc) is required;\n\
|
||||
an episode option (--ep1, --ep2, or --ep4) is also required. A difficulty\n\
|
||||
option (--normal, --hard, --very-hard, or --ultimate) may be given; this\n\
|
||||
affects which rare rates from config.json are used if --bb was given.\n\
|
||||
Similarly, --battle, --challenge, or --solo may also be given; this affects\n\
|
||||
which variations are used on all versions and which rare rates to use for\n\
|
||||
BB. --threads=COUNT controls the number of threads to use for the search\n\
|
||||
by default, one thread per CPU core is used. --min-count specifies how many\n\
|
||||
rare enemies must be found to output the seed. Finally, --quest=NAME may be\n\
|
||||
given to use that quest\'s map instead of the free-roam maps.\n",
|
||||
+[](phosg::Arguments& args) {
|
||||
auto version = get_cli_version(args);
|
||||
auto episode = get_cli_episode(args);
|
||||
auto difficulty = get_cli_difficulty(args);
|
||||
auto mode = get_cli_game_mode(args);
|
||||
size_t num_threads = args.get<size_t>("threads", 0);
|
||||
size_t min_count = args.get<size_t>("min-count", 1);
|
||||
string quest_name = args.get<string>("quest", false);
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
shared_ptr<const VersionedQuest> vq;
|
||||
if (!quest_name.empty()) {
|
||||
s->load_config_early();
|
||||
s->load_quest_index(false);
|
||||
auto q = s->quest_index(version)->get(quest_name);
|
||||
if (!q) {
|
||||
throw runtime_error("quest does not exist");
|
||||
}
|
||||
vq = q->version(version, 1);
|
||||
if (!vq) {
|
||||
throw runtime_error("quest version does not exist");
|
||||
}
|
||||
} else if (version == Version::BB_V4) {
|
||||
s->load_config_early();
|
||||
} else if (version == Version::PC_V2) {
|
||||
s->load_patch_indexes(false);
|
||||
} else {
|
||||
s->clear_file_caches(false);
|
||||
}
|
||||
|
||||
shared_ptr<const Map::RareEnemyRates> rare_rates;
|
||||
if (version != Version::BB_V4) {
|
||||
rare_rates = Map::DEFAULT_RARE_ENEMIES;
|
||||
} else if (mode == GameMode::CHALLENGE) {
|
||||
rare_rates = s->rare_enemy_rates_challenge;
|
||||
} else {
|
||||
rare_rates = s->rare_enemy_rates_by_difficulty[difficulty];
|
||||
}
|
||||
|
||||
mutex output_lock;
|
||||
auto thread_fn = [&](uint64_t seed, size_t) -> bool {
|
||||
auto random_crypt = make_shared<PSOV2Encryption>(seed);
|
||||
parray<le_uint32_t, 0x20> variations;
|
||||
|
||||
shared_ptr<Map> map;
|
||||
if (vq) {
|
||||
if (!vq->dat_contents_decompressed) {
|
||||
throw runtime_error("quest does not have DAT data");
|
||||
}
|
||||
map = Lobby::load_maps(
|
||||
version, episode, difficulty, 0, 0, rare_rates, seed, random_crypt, vq->dat_contents_decompressed);
|
||||
|
||||
} else {
|
||||
generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO));
|
||||
map = Lobby::load_maps(
|
||||
version,
|
||||
episode,
|
||||
mode,
|
||||
difficulty,
|
||||
0,
|
||||
0,
|
||||
s->set_data_table(version, episode, mode, difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
rare_rates,
|
||||
seed,
|
||||
random_crypt,
|
||||
variations);
|
||||
}
|
||||
|
||||
vector<size_t> rare_indexes;
|
||||
for (size_t z = 0; z < map->enemies.size(); z++) {
|
||||
if (enemy_type_is_rare(map->enemies[z].type)) {
|
||||
rare_indexes.emplace_back(z);
|
||||
}
|
||||
}
|
||||
|
||||
if (rare_indexes.size() >= min_count) {
|
||||
lock_guard g(output_lock);
|
||||
fprintf(stdout, "%08" PRIX64 ":", seed);
|
||||
for (size_t index : rare_indexes) {
|
||||
fprintf(stdout, " E-%zX:%s", index, phosg::name_for_enum(map->enemies[index].type));
|
||||
}
|
||||
fprintf(stdout, "\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
phosg::parallel_range_blocks<uint64_t>(thread_fn, 0, 0x100000000, 0x1000, num_threads, nullptr);
|
||||
});
|
||||
|
||||
Action a_load_maps_test(
|
||||
"load-maps-test", nullptr, +[](phosg::Arguments& args) {
|
||||
using SDT = SetDataTable;
|
||||
bool save_disassembly = args.get<bool>("disassemble");
|
||||
|
||||
auto s = make_shared<ServerState>(get_config_filename(args));
|
||||
s->load_config_early();
|
||||
s->clear_file_caches(false);
|
||||
s->load_patch_indexes(false);
|
||||
s->load_set_data_tables(false);
|
||||
s->load_quest_index(false);
|
||||
for (size_t v_s = NUM_PATCH_VERSIONS; v_s < NUM_VERSIONS; v_s++) {
|
||||
Version v = static_cast<Version>(v_s);
|
||||
if (is_ep3(v)) {
|
||||
continue;
|
||||
}
|
||||
const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
if (episode == Episode::EP4 && !is_v4(v)) {
|
||||
continue;
|
||||
}
|
||||
if (episode == Episode::EP2 && is_v1_or_v2(v) && (v != Version::GC_NTE)) {
|
||||
continue;
|
||||
}
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
if ((mode == GameMode::BATTLE || mode == GameMode::CHALLENGE) && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
if (mode == GameMode::SOLO && !is_v4(v)) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
if (difficulty == 3 && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
auto sdt = s->set_data_table(v, episode, mode, difficulty);
|
||||
for (uint8_t floor = 0; floor < 0x12; floor++) {
|
||||
auto variation_maxes = sdt->num_free_roam_variations_for_floor(episode, mode == GameMode::SOLO, floor);
|
||||
for (size_t var1 = 0; var1 < variation_maxes.first; var1++) {
|
||||
for (size_t var2 = 0; var2 < variation_maxes.second; var2++) {
|
||||
auto enemies_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::ENEMIES);
|
||||
auto objects_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::OBJECTS);
|
||||
auto events_filename = sdt->map_filename_for_variation(
|
||||
floor, var1, var2, episode, mode, SDT::FilenameType::EVENTS);
|
||||
s->load_maps(false);
|
||||
|
||||
fprintf(stderr, "... %s %s %s %s %02hhX %zX %zX",
|
||||
phosg::name_for_enum(v), name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), floor, var1, var2);
|
||||
auto map = make_shared<Map>(v, 0, 0, nullptr);
|
||||
if (!enemies_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", enemies_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, enemies_filename);
|
||||
if (map_data) {
|
||||
map->add_enemies_from_map_data(
|
||||
episode, difficulty, 0, 0, map_data->data(), map_data->size(), Map::DEFAULT_RARE_ENEMIES);
|
||||
fprintf(stderr, "%zu enemies, %zu sets]", map->enemies.size(), map->enemy_set_flags.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
if (!objects_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", objects_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, objects_filename);
|
||||
if (map_data) {
|
||||
map->add_objects_from_map_data(floor, map_data);
|
||||
fprintf(stderr, "%zu objects]", map->objects.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
if (!events_filename.empty()) {
|
||||
fprintf(stderr, " [%s => ", events_filename.c_str());
|
||||
auto map_data = s->load_map_file(v, events_filename);
|
||||
if (map_data) {
|
||||
map->add_events_from_map_data(floor, map_data->data(), map_data->size());
|
||||
fprintf(stderr, "%zu events, %zu action bytes]", map->events.size(), map->event_action_stream.size());
|
||||
} else {
|
||||
fprintf(stderr, "__MISSING__]");
|
||||
}
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : s->supermaps) {
|
||||
auto episode = static_cast<Episode>((it.first >> 28) & 7);
|
||||
auto mode = static_cast<GameMode>((it.first >> 26) & 3);
|
||||
uint8_t difficulty = (it.first >> 24) & 3;
|
||||
uint8_t floor = (it.first >> 16) & 0xFF;
|
||||
uint8_t layout = (it.first >> 8) & 0xFF;
|
||||
uint8_t entities = (it.first >> 0) & 0xFF;
|
||||
|
||||
fprintf(stderr, "FREE MAP: %08" PRIX32 " => %s %s %c floor=%02hhX layout=%02hhX entities=%02hhX\n",
|
||||
it.first,
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
if (save_disassembly) {
|
||||
string filename = phosg::string_printf(
|
||||
"supermap_%s_%s_%c_%02hhX_%02hhx_%02hhX.txt",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty),
|
||||
floor, layout, entities);
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
it.second->print(f.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& q_it : s->default_quest_index->quests_by_number) {
|
||||
for (const auto& vq_it : q_it.second->versions) {
|
||||
auto vq = vq_it.second;
|
||||
shared_ptr<const Map> map = Lobby::load_maps(
|
||||
vq->version,
|
||||
vq->episode,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
0,
|
||||
nullptr,
|
||||
vq->dat_contents_decompressed);
|
||||
fprintf(stderr, "... %" PRIu32 " (%s) %s %s %s => %zu enemies (%zu sets), %zu objects, %zu events\n",
|
||||
vq->quest_number,
|
||||
vq->name.c_str(),
|
||||
name_for_episode(vq->episode),
|
||||
phosg::name_for_enum(vq->version),
|
||||
name_for_language_code(vq->language),
|
||||
map->enemies.size(),
|
||||
map->enemy_set_flags.size(),
|
||||
map->objects.size(),
|
||||
map->events.size());
|
||||
// Generate MapStates for a few random variations
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
static const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
|
||||
Episode episode = episodes[phosg::random_object<uint32_t>() % episodes.size()];
|
||||
GameMode mode = modes[phosg::random_object<uint32_t>() % modes.size()];
|
||||
uint8_t difficulty = phosg::random_object<uint32_t>() % 4;
|
||||
uint8_t event = phosg::random_object<uint32_t>() % 8;
|
||||
uint32_t random_seed = phosg::random_object<uint32_t>();
|
||||
fprintf(stderr, "FREE MAP STATE TEST: %s %s %c\n",
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
abbreviation_for_difficulty(difficulty));
|
||||
|
||||
auto sdt = s->set_data_table(Version::BB_V4, episode, mode, difficulty);
|
||||
auto variations = sdt->generate_variations(episode, (mode == GameMode::SOLO), nullptr);
|
||||
auto supermaps = s->supermaps_for_variations(episode, mode, difficulty, variations);
|
||||
auto map_state = make_shared<MapState>(
|
||||
0, difficulty, event, random_seed, MapState::DEFAULT_RARE_ENEMIES, nullptr, supermaps);
|
||||
map_state->verify();
|
||||
|
||||
fprintf(stderr, " map state ok: 0x%zX objects, 0x%zX enemies, 0x%zX enemy sets, 0x%zX events\n",
|
||||
map_state->object_states.size(),
|
||||
map_state->enemy_states.size(),
|
||||
map_state->enemy_set_states.size(),
|
||||
map_state->event_states.size());
|
||||
}
|
||||
|
||||
s->load_quest_index(false);
|
||||
|
||||
uint32_t random_seed = args.get<uint32_t>("random-seed", 0, phosg::Arguments::IntFormat::HEX);
|
||||
for (const auto& it : s->default_quest_index->quests_by_number) {
|
||||
auto supermap = it.second->get_supermap(random_seed);
|
||||
if (!supermap) {
|
||||
fprintf(stderr, "QUEST MAP: %08" PRIX32 " => (no supermap)\n", it.first);
|
||||
} else {
|
||||
string filename = phosg::string_printf("supermap_quest_%" PRIu32 "_%08" PRIX32 ".txt", it.first, random_seed);
|
||||
fprintf(stderr, "QUEST MAP: %08" PRIX32 " => %s\n", it.first, filename.c_str());
|
||||
if (save_disassembly) {
|
||||
auto f = phosg::fopen_unique(filename, "wt");
|
||||
fprintf(f.get(), "QUEST %" PRIu32 " (%s)\n", it.first, it.second->name.c_str());
|
||||
supermap->print(f.get());
|
||||
}
|
||||
}
|
||||
|
||||
auto map_state = make_shared<MapState>(
|
||||
0,
|
||||
phosg::random_object<uint8_t>() & 3,
|
||||
0,
|
||||
phosg::random_object<uint32_t>(),
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
nullptr,
|
||||
supermap);
|
||||
map_state->verify();
|
||||
|
||||
fprintf(stderr, " map state ok: 0x%zX objects, 0x%zX enemies, 0x%zX enemy sets, 0x%zX events\n",
|
||||
map_state->object_states.size(),
|
||||
map_state->enemy_states.size(),
|
||||
map_state->enemy_set_states.size(),
|
||||
map_state->event_states.size());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+3598
-2140
File diff suppressed because it is too large
Load Diff
+806
-406
File diff suppressed because it is too large
Load Diff
+66
-54
@@ -746,7 +746,7 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->log.info("Wrote code from server to file %s", output_filename.c_str());
|
||||
|
||||
#ifdef HAVE_RESOURCE_FILE
|
||||
using FooterT = S_ExecuteCode_FooterT_B2<BE>;
|
||||
using FooterT = RELFileFooterT<BE>;
|
||||
|
||||
// TODO: Support SH-4 disassembly too
|
||||
bool is_ppc = ::is_ppc(ses->version());
|
||||
@@ -770,9 +770,9 @@ static HandlerResult S_B2(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
reloc_offset += (r.get<U16T<BE>>() * 4);
|
||||
labels.emplace(reloc_offset, phosg::string_printf("reloc%zu", x));
|
||||
}
|
||||
labels.emplace(footer.entrypoint_addr_offset.load(), "entry_ptr");
|
||||
labels.emplace(footer.root_offset.load(), "entry_ptr");
|
||||
labels.emplace(footer_offset, "footer");
|
||||
labels.emplace(r.pget<U32T<BE>>(footer.entrypoint_addr_offset), "start");
|
||||
labels.emplace(r.pget<U32T<BE>>(footer.root_offset), "start");
|
||||
|
||||
string disassembly;
|
||||
if (is_ppc) {
|
||||
@@ -1002,9 +1002,9 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto s = ses->require_server_state();
|
||||
ses->next_drop_item.id = ses->next_item_id++;
|
||||
bool is_box = (cmd.rt_index == 0x30);
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, !is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
uint8_t source_type = (cmd.rt_index == 0x30) ? 2 : 1;
|
||||
send_drop_item_to_channel(s, ses->server_channel, ses->next_drop_item, source_type, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_drop_item_to_channel(s, ses->client_channel, ses->next_drop_item, source_type, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
ses->next_drop_item.clear();
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1025,27 +1025,34 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but item creator is missing");
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
if (!ses->map) {
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but map is missing");
|
||||
if (!ses->map_state) {
|
||||
ses->log.warning("Session is in INTERCEPT drop mode, but map state is missing");
|
||||
return HandlerResult::Type::FORWARD;
|
||||
}
|
||||
|
||||
G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(data.data(), data.size());
|
||||
auto rec = reconcile_drop_request_with_map(
|
||||
ses->log, ses->client_channel, cmd, ses->version(), ses->lobby_episode, ses->config, ses->map, false);
|
||||
ses->log,
|
||||
ses->client_channel,
|
||||
cmd,
|
||||
ses->lobby_episode,
|
||||
ses->lobby_event,
|
||||
ses->config,
|
||||
ses->map_state,
|
||||
false);
|
||||
|
||||
ItemCreator::DropResult res;
|
||||
if (rec.is_box) {
|
||||
if (rec.obj_st) {
|
||||
if (rec.ignore_def) {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
ses->log.info("Creating item from box %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_box_item_drop(cmd.effective_area);
|
||||
} else {
|
||||
ses->log.info("Creating item from box %04hX (area %02hX; specialized with %g %08" PRIX32 " %08" PRIX32 " %08" PRIX32 ")",
|
||||
cmd.entity_id.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
cmd.entity_index.load(), cmd.effective_area, cmd.param3.load(), cmd.param4.load(), cmd.param5.load(), cmd.param6.load());
|
||||
res = ses->item_creator->on_specialized_box_item_drop(cmd.effective_area, cmd.param3, cmd.param4, cmd.param5, cmd.param6);
|
||||
}
|
||||
} else {
|
||||
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_id.load(), cmd.effective_area);
|
||||
ses->log.info("Creating item from enemy %04hX (area %02hX)", cmd.entity_index.load(), cmd.effective_area);
|
||||
res = ses->item_creator->on_monster_item_drop(rec.effective_rt_index, cmd.effective_area);
|
||||
}
|
||||
|
||||
@@ -1054,12 +1061,12 @@ static HandlerResult SC_6x60_6xA2(shared_ptr<ProxyServer::LinkedSession> ses, co
|
||||
} else {
|
||||
auto s = ses->require_server_state();
|
||||
string name = s->describe_item(ses->version(), res.item, false);
|
||||
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_id.load(), cmd.effective_area, name.c_str());
|
||||
ses->log.info("Entity %04hX (area %02hX) created item %s", cmd.entity_index.load(), cmd.effective_area, name.c_str());
|
||||
res.item.id = ses->next_item_id++;
|
||||
ses->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());
|
||||
send_drop_item_to_channel(s, ses->client_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
send_drop_item_to_channel(s, ses->server_channel, res.item, !rec.is_box, cmd.floor, cmd.x, cmd.z, cmd.entity_id);
|
||||
res.item.id.load(), cmd.floor, cmd.pos.x.load(), cmd.pos.z.load());
|
||||
send_drop_item_to_channel(s, ses->client_channel, res.item, rec.obj_st ? 2 : 1, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_drop_item_to_channel(s, ses->server_channel, res.item, rec.obj_st ? 2 : 1, cmd.floor, cmd.pos, cmd.entity_index);
|
||||
send_item_notification_if_needed(s, ses->client_channel, ses->config, res.item, res.is_from_rare_table);
|
||||
}
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
@@ -1144,7 +1151,7 @@ static HandlerResult S_6x(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
const auto& cmd = check_size_t<G_AttackFinished_6x46>(data,
|
||||
offsetof(G_AttackFinished_6x46, targets),
|
||||
sizeof(G_AttackFinished_6x46));
|
||||
if (cmd.count > min<size_t>(cmd.header.size - 2, cmd.targets.size())) {
|
||||
if (cmd.target_count > min<size_t>(cmd.header.size - 2, cmd.targets.size())) {
|
||||
ses->log.warning("Blocking subcommand 6x46 with invalid count");
|
||||
return HandlerResult::Type::SUPPRESS;
|
||||
}
|
||||
@@ -1426,18 +1433,25 @@ static HandlerResult S_13_A7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_
|
||||
if (!sf->is_download && phosg::ends_with(sf->basename, ".dat")) {
|
||||
auto quest_dat_data = make_shared<std::string>(prs_decompress(sf->data));
|
||||
try {
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
auto map_file = make_shared<MapFile>(quest_dat_data);
|
||||
auto materialized_map_file = map_file->materialize_random_sections(ses->lobby_random_seed);
|
||||
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
map_files.at(static_cast<size_t>(ses->version())) = materialized_map_file;
|
||||
auto supermap = make_shared<SuperMap>(ses->lobby_episode, map_files);
|
||||
|
||||
ses->map_state = make_shared<MapState>(
|
||||
ses->id,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
ses->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
quest_dat_data);
|
||||
supermap);
|
||||
|
||||
} catch (const exception& e) {
|
||||
ses->log.warning("Failed to load quest map: %s", e.what());
|
||||
ses->map_state.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1578,7 +1592,7 @@ static HandlerResult S_65_67_68_EB(shared_ptr<ProxyServer::LinkedSession> ses, u
|
||||
ses->lobby_episode = Episode::EP1;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
// This command can cause the client to no longer send D6 responses when
|
||||
// 1A/D5 large message boxes are closed. newserv keeps track of this
|
||||
@@ -1720,7 +1734,7 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
} else {
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
}
|
||||
ses->lobby_random_seed = cmd->rare_seed;
|
||||
ses->lobby_random_seed = cmd->random_seed;
|
||||
if (cmd_ep3) {
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
} else {
|
||||
@@ -1730,27 +1744,24 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
if (ses->version() == Version::GC_NTE) {
|
||||
// GC NTE ignores the variations field entirely, so clear the array to
|
||||
// ensure we'll load the correct maps
|
||||
cmd->variations.clear(0);
|
||||
cmd->variations = Variations();
|
||||
}
|
||||
|
||||
// Recreate the item creator if needed, and load maps
|
||||
auto s = ses->require_server_state();
|
||||
ses->set_drop_mode(ses->drop_mode);
|
||||
if (!is_ep3(ses->version())) {
|
||||
ses->map = Lobby::load_maps(
|
||||
ses->version(),
|
||||
ses->lobby_episode,
|
||||
ses->lobby_mode,
|
||||
if (!is_ep3(ses->version()) && (ses->lobby_mode != GameMode::CHALLENGE)) {
|
||||
auto s = ses->require_server_state();
|
||||
ses->map_state = make_shared<MapState>(
|
||||
ses->id,
|
||||
ses->lobby_difficulty,
|
||||
ses->lobby_event,
|
||||
ses->id,
|
||||
s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty),
|
||||
bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2),
|
||||
Map::DEFAULT_RARE_ENEMIES,
|
||||
ses->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<PSOV2Encryption>(ses->lobby_random_seed),
|
||||
cmd->variations,
|
||||
&ses->log);
|
||||
s->supermaps_for_variations(ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty, cmd->variations));
|
||||
} else {
|
||||
ses->map_state.reset();
|
||||
}
|
||||
|
||||
bool modified = false;
|
||||
@@ -1785,7 +1796,7 @@ static HandlerResult S_64(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
modified = true;
|
||||
}
|
||||
if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
cmd->rare_seed = ses->config.override_random_seed;
|
||||
cmd->random_seed = ses->config.override_random_seed;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -1812,7 +1823,7 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->lobby_episode = Episode::EP3;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
bool modified = false;
|
||||
|
||||
@@ -1850,7 +1861,7 @@ static HandlerResult S_E8(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
modified = true;
|
||||
}
|
||||
if (ses->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
cmd.rare_seed = ses->config.override_random_seed;
|
||||
cmd.random_seed = ses->config.override_random_seed;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
@@ -1899,7 +1910,7 @@ static HandlerResult C_98(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t c
|
||||
ses->lobby_mode = GameMode::NORMAL;
|
||||
ses->lobby_random_seed = 0;
|
||||
ses->item_creator.reset();
|
||||
ses->map.reset();
|
||||
ses->map_state.reset();
|
||||
|
||||
if (is_v3(ses->version()) || is_v4(ses->version())) {
|
||||
return C_GXB_61(ses, command, flag, data);
|
||||
@@ -1999,9 +2010,7 @@ constexpr on_command_t C_B_81 = &C_81<SC_SimpleMail_BB_81>;
|
||||
|
||||
template <typename CmdT>
|
||||
void C_6x_movement(shared_ptr<ProxyServer::LinkedSession> ses, const string& data) {
|
||||
const auto& cmd = check_size_t<CmdT>(data);
|
||||
ses->x = cmd.x;
|
||||
ses->z = cmd.z;
|
||||
ses->pos = check_size_t<CmdT>(data).pos;
|
||||
}
|
||||
|
||||
template <typename SendGuildCardCmdT>
|
||||
@@ -2030,19 +2039,22 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
if (!data.empty()) {
|
||||
if ((data[0] == 0x05) && ses->config.check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
auto& cmd = check_size_t<G_SwitchStateChanged_6x05>(data);
|
||||
if (ses->map && (cmd.flags & 1) && (cmd.header.object_id != 0xFFFF)) {
|
||||
for (auto* door : ses->map->doors_for_switch_flag(cmd.switch_flag_floor, cmd.switch_flag_num)) {
|
||||
if (door->game_flags & 0x0001) {
|
||||
if (ses->map_state && (cmd.flags & 1) && (cmd.header.entity_id != 0xFFFF)) {
|
||||
auto door_states = ses->map_state->door_states_for_switch_flag(
|
||||
ses->version(), cmd.switch_flag_floor, cmd.switch_flag_num);
|
||||
for (auto& door_state : door_states) {
|
||||
if (door_state->game_flags & 0x0001) {
|
||||
continue;
|
||||
}
|
||||
door->game_flags |= 1;
|
||||
door_state->game_flags |= 1;
|
||||
|
||||
uint16_t object_index = ses->map_state->index_for_object_state(ses->version(), door_state);
|
||||
G_UpdateObjectState_6x0B cmd0B;
|
||||
cmd0B.header.subcommand = 0x0B;
|
||||
cmd0B.header.size = sizeof(cmd0B) / 4;
|
||||
cmd0B.header.client_id = door->object_id | 0x4000;
|
||||
cmd0B.flags = door->game_flags;
|
||||
cmd0B.object_index = door->object_id;
|
||||
cmd0B.header.entity_id = object_index | 0x4000;
|
||||
cmd0B.flags = door_state->game_flags;
|
||||
cmd0B.object_index = object_index;
|
||||
ses->client_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B));
|
||||
ses->server_channel.send(0x60, 0x00, &cmd0B, sizeof(cmd0B));
|
||||
}
|
||||
@@ -2075,8 +2087,8 @@ HandlerResult C_6x<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
} else if (data[0] == 0x40) {
|
||||
C_6x_movement<G_WalkToPosition_6x40>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x42) {
|
||||
C_6x_movement<G_RunToPosition_6x42>(ses, data);
|
||||
} else if ((data[0] == 0x41) || (data[0] == 0x42)) {
|
||||
C_6x_movement<G_MoveToPosition_6x41_6x42>(ses, data);
|
||||
|
||||
} else if (data[0] == 0x48) {
|
||||
if (ses->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
|
||||
|
||||
@@ -547,8 +547,6 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
lobby_client_id(0),
|
||||
leader_client_id(0),
|
||||
floor(0),
|
||||
x(0.0),
|
||||
z(0.0),
|
||||
is_in_game(false),
|
||||
is_in_quest(false),
|
||||
lobby_event(0),
|
||||
|
||||
+2
-3
@@ -81,7 +81,7 @@ public:
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<Map> map;
|
||||
std::shared_ptr<MapState> map_state;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
@@ -95,8 +95,7 @@ public:
|
||||
size_t lobby_client_id;
|
||||
size_t leader_client_id;
|
||||
uint16_t floor;
|
||||
float x;
|
||||
float z;
|
||||
VectorXZF pos;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t lobby_event;
|
||||
|
||||
+98
-23
@@ -199,6 +199,7 @@ VersionedQuest::VersionedQuest(
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const MapFile> map_file,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules,
|
||||
ssize_t challenge_template_index,
|
||||
@@ -219,6 +220,7 @@ VersionedQuest::VersionedQuest(
|
||||
is_dlq_encoded(false),
|
||||
bin_contents(bin_contents),
|
||||
dat_contents(dat_contents),
|
||||
map_file(map_file),
|
||||
pvr_contents(pvr_contents),
|
||||
battle_rules(battle_rules),
|
||||
challenge_template_index(challenge_template_index),
|
||||
@@ -226,10 +228,6 @@ VersionedQuest::VersionedQuest(
|
||||
available_expression(available_expression),
|
||||
enabled_expression(enabled_expression) {
|
||||
|
||||
if (this->dat_contents) {
|
||||
this->dat_contents_decompressed = make_shared<string>(prs_decompress(*this->dat_contents));
|
||||
}
|
||||
|
||||
auto bin_decompressed = prs_decompress(*this->bin_contents);
|
||||
|
||||
switch (this->version) {
|
||||
@@ -390,12 +388,13 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
||||
joinable(initial_version->joinable),
|
||||
lock_status_register(initial_version->lock_status_register),
|
||||
name(initial_version->name),
|
||||
supermap(nullptr),
|
||||
battle_rules(initial_version->battle_rules),
|
||||
challenge_template_index(initial_version->challenge_template_index),
|
||||
description_flag(initial_version->description_flag),
|
||||
available_expression(initial_version->available_expression),
|
||||
enabled_expression(initial_version->enabled_expression) {
|
||||
this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version);
|
||||
this->add_version(initial_version);
|
||||
}
|
||||
|
||||
uint32_t Quest::versions_key(Version v, uint8_t language) {
|
||||
@@ -449,6 +448,44 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
||||
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
||||
}
|
||||
|
||||
std::shared_ptr<const SuperMap> Quest::get_supermap(int64_t random_seed) const {
|
||||
if (this->supermap) {
|
||||
return this->supermap;
|
||||
}
|
||||
|
||||
bool save_to_cache = true;
|
||||
bool any_map_file_present = false;
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
|
||||
auto vq = this->version(v, 1);
|
||||
if (vq && vq->map_file) {
|
||||
auto map_file = vq->map_file;
|
||||
if (map_file->has_random_sections()) {
|
||||
if (random_seed < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
save_to_cache = false;
|
||||
map_file = map_file->materialize_random_sections(random_seed);
|
||||
}
|
||||
map_files.at(static_cast<size_t>(v)) = map_file;
|
||||
any_map_file_present = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_map_file_present) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto supermap = make_shared<SuperMap>(this->episode, map_files);
|
||||
if (save_to_cache) {
|
||||
this->supermap = supermap;
|
||||
}
|
||||
static_game_data_log.info("Constructed %s supermap for quest %" PRIu32 " (%s)",
|
||||
save_to_cache ? "cacheable" : "temporary", this->quest_number, this->name.c_str());
|
||||
|
||||
return supermap;
|
||||
}
|
||||
|
||||
bool Quest::has_version(Version v, uint8_t language) const {
|
||||
return this->versions.count(this->versions_key(v, language));
|
||||
}
|
||||
@@ -482,17 +519,22 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, uint8_t language) con
|
||||
|
||||
QuestIndex::QuestIndex(
|
||||
const string& directory,
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index,
|
||||
shared_ptr<const QuestCategoryIndex> category_index,
|
||||
bool is_ep3)
|
||||
: directory(directory),
|
||||
category_index(category_index) {
|
||||
|
||||
struct FileData {
|
||||
std::string filename;
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
};
|
||||
struct DATFileData {
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
shared_ptr<const MapFile> map_file;
|
||||
};
|
||||
map<string, FileData> bin_files;
|
||||
map<string, FileData> dat_files;
|
||||
map<string, DATFileData> dat_files;
|
||||
map<string, FileData> pvr_files;
|
||||
map<string, FileData> json_files;
|
||||
map<string, uint32_t> categories;
|
||||
@@ -519,6 +561,23 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
};
|
||||
|
||||
auto add_dat_file = [&](const string& basename, const string& filename, string&& value) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("file " + basename + " exists in multiple categories");
|
||||
}
|
||||
auto data_ptr = make_shared<string>(std::move(value));
|
||||
auto map_file = make_shared<MapFile>(make_shared<string>(prs_decompress(*data_ptr)));
|
||||
if (!dat_files.emplace(basename, DATFileData{filename, data_ptr, map_file}).second) {
|
||||
throw runtime_error("file " + basename + " already exists");
|
||||
}
|
||||
// There is a bug in the client that prevents quests from loading properly
|
||||
// if any file's size is a multiple of 0x400. See the comments on the 13
|
||||
// command in CommandFormats.hh for more details.
|
||||
if (!(data_ptr->size() & 0x3FF)) {
|
||||
data_ptr->push_back(0x00);
|
||||
}
|
||||
};
|
||||
|
||||
string cat_path = directory + "/" + cat->directory_name;
|
||||
if (!phosg::isdir(cat_path)) {
|
||||
static_game_data_log.warning("Quest category directory %s is missing; skipping it", cat_path.c_str());
|
||||
@@ -570,9 +629,9 @@ QuestIndex::QuestIndex(
|
||||
} else if (extension == "bind" || extension == "mnmd") {
|
||||
add_file(bin_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
} else if (extension == "dat") {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
add_dat_file(file_basename, orig_filename, std::move(file_data));
|
||||
} else if (extension == "datd") {
|
||||
add_file(dat_files, file_basename, orig_filename, prs_compress_optimal(file_data), true);
|
||||
add_dat_file(file_basename, orig_filename, prs_compress_optimal(file_data));
|
||||
} else if (extension == "pvr") {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(file_data), true);
|
||||
} else if (extension == "qst") {
|
||||
@@ -581,7 +640,7 @@ QuestIndex::QuestIndex(
|
||||
if (phosg::ends_with(it.first, ".bin")) {
|
||||
add_file(bin_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else if (phosg::ends_with(it.first, ".dat")) {
|
||||
add_file(dat_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
add_dat_file(file_basename, orig_filename, std::move(it.second));
|
||||
} else if (phosg::ends_with(it.first, ".pvr")) {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else {
|
||||
@@ -655,7 +714,7 @@ QuestIndex::QuestIndex(
|
||||
uint8_t language = language_code_for_char(language_token[0]);
|
||||
|
||||
// Find the corresponding dat and pvr files
|
||||
const FileData* dat_filedata = nullptr;
|
||||
const DATFileData* dat_filedata = nullptr;
|
||||
const FileData* pvr_filedata = nullptr;
|
||||
if (!::is_ep3(version)) {
|
||||
// Look for dat and pvr files with the same basename as the bin file; if
|
||||
@@ -746,6 +805,7 @@ QuestIndex::QuestIndex(
|
||||
language,
|
||||
bin_filedata->data,
|
||||
dat_filedata ? dat_filedata->data : nullptr,
|
||||
dat_filedata ? dat_filedata->map_file : nullptr,
|
||||
pvr_filedata ? pvr_filedata->data : nullptr,
|
||||
battle_rules,
|
||||
challenge_template_index,
|
||||
@@ -796,6 +856,11 @@ QuestIndex::QuestIndex(
|
||||
static_game_data_log.warning("(%s) Failed to index quest file: (%s)", basename.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Create supermaps for all quests that need them (all non-Ep3 quests)
|
||||
for (const auto& it : this->quests_by_number) {
|
||||
it.second->get_supermap(-1);
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
@@ -817,11 +882,11 @@ shared_ptr<const Quest> QuestIndex::get(const std::string& name) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
IncludeCondition include_condition) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> ret;
|
||||
for (const auto& cat : this->category_index->categories) {
|
||||
if (cat->check_flag(menu_type) && !this->filter(episode, version, cat->category_id, include_condition, 1).empty()) {
|
||||
if (cat->check_flag(menu_type) && !this->filter(episode, version_flags, cat->category_id, include_condition, 1).empty()) {
|
||||
ret.emplace_back(cat);
|
||||
}
|
||||
}
|
||||
@@ -830,7 +895,7 @@ vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
|
||||
vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition,
|
||||
size_t limit) const {
|
||||
@@ -843,17 +908,27 @@ vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filt
|
||||
return ret;
|
||||
}
|
||||
for (auto it : category_it->second) {
|
||||
if (((effective_episode == Episode::NONE) || (it.second->episode == effective_episode)) &&
|
||||
it.second->has_version_any_language(version)) {
|
||||
IncludeState state = include_condition ? include_condition(it.second) : IncludeState::AVAILABLE;
|
||||
if (state == IncludeState::HIDDEN) {
|
||||
continue;
|
||||
}
|
||||
ret.emplace_back(make_pair(state, it.second));
|
||||
if (limit && (ret.size() >= limit)) {
|
||||
if ((effective_episode != Episode::NONE) && (it.second->episode != effective_episode)) {
|
||||
continue;
|
||||
}
|
||||
bool all_required_versions_present = true;
|
||||
for (size_t v_s = 0; v_s < NUM_VERSIONS; v_s++) {
|
||||
if ((version_flags & (1 << v_s)) && !it.second->has_version_any_language(static_cast<Version>(v_s))) {
|
||||
all_required_versions_present = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!all_required_versions_present) {
|
||||
continue;
|
||||
}
|
||||
IncludeState state = include_condition ? include_condition(it.second) : IncludeState::AVAILABLE;
|
||||
if (state == IncludeState::HIDDEN) {
|
||||
continue;
|
||||
}
|
||||
ret.emplace_back(make_pair(state, it.second));
|
||||
if (limit && (ret.size() >= limit)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
+19
-14
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "IntegralExpression.hh"
|
||||
#include "Map.hh"
|
||||
#include "PlayerSubordinates.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "StaticGameData.hh"
|
||||
@@ -63,25 +64,25 @@ struct QuestCategoryIndex {
|
||||
};
|
||||
|
||||
struct VersionedQuest {
|
||||
uint32_t quest_number;
|
||||
uint32_t category_id;
|
||||
Episode episode;
|
||||
bool allow_start_from_chat_command;
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
uint32_t quest_number = 0;
|
||||
uint32_t category_id = 0;
|
||||
Episode episode = Episode::NONE;
|
||||
bool allow_start_from_chat_command = false;
|
||||
bool joinable = false;
|
||||
int16_t lock_status_register = -1;
|
||||
std::string name;
|
||||
Version version;
|
||||
uint8_t language;
|
||||
bool is_dlq_encoded;
|
||||
Version version = Version::UNKNOWN;
|
||||
uint8_t language = 1;
|
||||
bool is_dlq_encoded = false;
|
||||
std::string short_description;
|
||||
std::string long_description;
|
||||
std::shared_ptr<const std::string> bin_contents;
|
||||
std::shared_ptr<const std::string> dat_contents;
|
||||
std::shared_ptr<const std::string> dat_contents_decompressed;
|
||||
std::shared_ptr<const MapFile> map_file;
|
||||
std::shared_ptr<const std::string> pvr_contents;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
ssize_t challenge_template_index = -1;
|
||||
uint8_t description_flag = 0;
|
||||
std::shared_ptr<const IntegralExpression> available_expression;
|
||||
std::shared_ptr<const IntegralExpression> enabled_expression;
|
||||
|
||||
@@ -92,6 +93,7 @@ struct VersionedQuest {
|
||||
uint8_t language,
|
||||
std::shared_ptr<const std::string> bin_contents,
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const MapFile> map_file,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
||||
ssize_t challenge_template_index = -1,
|
||||
@@ -120,6 +122,8 @@ public:
|
||||
Quest& operator=(const Quest&) = default;
|
||||
Quest& operator=(Quest&&) = default;
|
||||
|
||||
std::shared_ptr<const SuperMap> get_supermap(int64_t random_seed) const;
|
||||
|
||||
void add_version(std::shared_ptr<const VersionedQuest> vq);
|
||||
bool has_version(Version v, uint8_t language) const;
|
||||
bool has_version_any_language(Version v) const;
|
||||
@@ -134,6 +138,7 @@ public:
|
||||
bool joinable;
|
||||
int16_t lock_status_register;
|
||||
std::string name;
|
||||
mutable std::shared_ptr<const SuperMap> supermap;
|
||||
std::shared_ptr<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
uint8_t description_flag;
|
||||
@@ -165,11 +170,11 @@ struct QuestIndex {
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
IncludeCondition include_condition = nullptr) const;
|
||||
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint16_t version_flags,
|
||||
uint32_t category_id,
|
||||
IncludeCondition include_condition = nullptr,
|
||||
size_t limit = 0) const;
|
||||
|
||||
+6
-13
@@ -59,13 +59,6 @@ using AttackData = BattleParamsIndex::AttackData;
|
||||
using ResistData = BattleParamsIndex::ResistData;
|
||||
using MovementData = BattleParamsIndex::MovementData;
|
||||
|
||||
struct Vector4F {
|
||||
le_float x;
|
||||
le_float y;
|
||||
le_float z;
|
||||
le_float t;
|
||||
} __packed_ws__(Vector4F, 0x10);
|
||||
|
||||
// bit_cast isn't in the standard place on macOS (it is apparently implicitly
|
||||
// included by resource_dasm, but newserv can be built without resource_dasm)
|
||||
// and I'm too lazy to go find the right header to include
|
||||
@@ -2213,7 +2206,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
||||
// valueD = loop flag (0 = no, 1 = yes)
|
||||
// regsE[0-2] = result point (x, y, z as floats)
|
||||
// regsE[3] = the result code (0 = failed, 1 = success)
|
||||
// labelF = control point entries (array of valueA Vector4F structures)
|
||||
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
||||
{0xF8DB, "get_vector_from_path", "unknownF8DB", {INT32, FLOAT32, FLOAT32, INT32, {REG_SET_FIXED, 4}, SCRIPT16}, F_V3_V4 | F_ARGS},
|
||||
|
||||
// Same as npc_text, but only applies to a specific player slot.
|
||||
@@ -2309,7 +2302,7 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
||||
// valueD = loop flag (0 = no, 1 = yes)
|
||||
// regsE[0-2] = result point (x, y, z as floats)
|
||||
// regsE[3] = the result code (0 = failed, 1 = success)
|
||||
// labelF = control point entries (array of valueA Vector4F structures)
|
||||
// labelF = control point entries (array of valueA VectorXYZTF structures)
|
||||
{0xF8F2, "compute_bezier_curve_point", "load_unk_data", {INT32, FLOAT32, FLOAT32, INT32, {REG_SET_FIXED, 4}, {LABEL16, Arg::DataType::BEZIER_CONTROL_POINT_DATA}}, F_V3_V4 | F_ARGS},
|
||||
|
||||
// Creates a timed particle effect. Like the particle opcode, but the
|
||||
@@ -3626,11 +3619,11 @@ std::string disassemble_quest_script(
|
||||
}
|
||||
if (l->has_data_type(Arg::DataType::BEZIER_CONTROL_POINT_DATA)) {
|
||||
phosg::StringReader r = cmd_r.sub(l->offset, size);
|
||||
lines.emplace_back(" // As Vector4F");
|
||||
while (r.remaining() >= sizeof(Vector4F)) {
|
||||
lines.emplace_back(" // As VectorXYZTF");
|
||||
while (r.remaining() >= sizeof(VectorXYZTF)) {
|
||||
size_t offset = l->offset + cmd_r.where();
|
||||
const auto& e = r.get<Vector4F>();
|
||||
lines.emplace_back(phosg::string_printf(" %04zX vector4f x=%g, y=%g, z=%g, t=%g", offset, e.x.load(), e.y.load(), e.z.load(), e.t.load()));
|
||||
const auto& e = r.get<VectorXYZTF>();
|
||||
lines.emplace_back(phosg::string_printf(" %04zX vector x=%g, y=%g, z=%g, t=%g", offset, e.x.load(), e.y.load(), e.z.load(), e.t.load()));
|
||||
}
|
||||
if (r.remaining() > 0) {
|
||||
size_t struct_end_offset = l->offset + r.where();
|
||||
|
||||
+10
-10
@@ -5,6 +5,7 @@
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "BattleParamsIndex.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
@@ -94,8 +95,8 @@ RareItemSet::ExpandedDrop RareItemSet::ParsedRELData::PackedDrop::expand() const
|
||||
|
||||
template <bool BE>
|
||||
void RareItemSet::ParsedRELData::parse_t(phosg::StringReader r, bool is_v1) {
|
||||
uint32_t root_offset = r.pget<U32T<BE>>(r.size() - 0x10);
|
||||
const auto& root = r.pget<OffsetsT<BE>>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& root = r.pget<OffsetsT<BE>>(footer.root_offset);
|
||||
|
||||
phosg::StringReader monsters_r = r.sub(root.monster_rares_offset);
|
||||
for (size_t z = 0; z < (is_v1 ? 0x33 : 0x65); z++) {
|
||||
@@ -160,14 +161,13 @@ std::string RareItemSet::ParsedRELData::serialize_t(bool is_v1) const {
|
||||
while (w.size() & 0x1F) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
w.put<U32T<BE>>(relocations_offset);
|
||||
w.put<U32T<BE>>(3); // num_relocations
|
||||
w.put<U32T<BE>>(1); // TODO: What is this used for?
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(root_offset);
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(0);
|
||||
w.put<U32T<BE>>(0);
|
||||
|
||||
RELFileFooterT<BE> footer;
|
||||
footer.relocations_offset = relocations_offset;
|
||||
footer.num_relocations = 3;
|
||||
footer.unused1[0] = 1; // TODO: What is this used for?
|
||||
footer.root_offset = root_offset;
|
||||
w.put<RELFileFooterT<BE>>(footer);
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
|
||||
+130
-142
@@ -1393,7 +1393,7 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr<Lobby> l, int16_t ta
|
||||
// Figure out which clients are at this table. If any client has declined, we
|
||||
// never start a match, but we may start a match even if all clients have not
|
||||
// yet accepted (in case of a tournament match).
|
||||
Version base_version = Version::UNKNOWN;
|
||||
Version game_version = Version::UNKNOWN;
|
||||
unordered_map<size_t, shared_ptr<Client>> table_clients;
|
||||
bool all_clients_accepted = true;
|
||||
for (const auto& c : l->clients) {
|
||||
@@ -1401,9 +1401,9 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr<Lobby> l, int16_t ta
|
||||
continue;
|
||||
}
|
||||
// Prevent match from starting unless all players are on the same version
|
||||
if (base_version == Version::UNKNOWN) {
|
||||
base_version = c->version();
|
||||
} else if (base_version != c->version()) {
|
||||
if (game_version == Version::UNKNOWN) {
|
||||
game_version = c->version();
|
||||
} else if (game_version != c->version()) {
|
||||
return false;
|
||||
}
|
||||
if (c->card_battle_table_seat_number >= 4) {
|
||||
@@ -1920,10 +1920,31 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
for (size_t x = 0; x < game->max_clients; x++) {
|
||||
const auto& game_c = game->clients[x];
|
||||
if (game_c.get()) {
|
||||
static_assert(NUM_VERSIONS == 14, "Don\'t forget to update the game player listing version tokens");
|
||||
static const array<const char*, NUM_VERSIONS> version_tokens = {
|
||||
" $C4P2$C7",
|
||||
" $C4P4$C7",
|
||||
" $C5DCN$C7",
|
||||
" $C5DCP$C7",
|
||||
" $C2DC1$C7",
|
||||
" $C2DC2$C7",
|
||||
" $C5PCN$C7",
|
||||
" $C2PC$C7",
|
||||
" $C5GCN$C7",
|
||||
" $C2GC$C7",
|
||||
" $C5Ep3N$C7",
|
||||
" $C2Ep3$C7",
|
||||
" $C2XB$C7",
|
||||
" $C2BB$C7",
|
||||
};
|
||||
const char* version_token = (game_c->version() != c->version())
|
||||
? version_tokens.at(static_cast<size_t>(game_c->version()))
|
||||
: "";
|
||||
auto player = game_c->character();
|
||||
string name = escape_player_name(player->disp.name.decode(game_c->language()));
|
||||
info += phosg::string_printf("%s\n %s Lv%" PRIu32 " %c\n",
|
||||
info += phosg::string_printf("%s%s\n %s Lv%" PRIu32 " %c\n",
|
||||
name.c_str(),
|
||||
version_token,
|
||||
name_for_char_class(player->disp.visual.char_class),
|
||||
player->disp.stats.level + 1,
|
||||
char_for_language_code(game_c->language()));
|
||||
@@ -1942,10 +1963,6 @@ static void on_09(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
info += phosg::string_printf("Req. level: %" PRIu32 "+\n", game->min_level + 1);
|
||||
}
|
||||
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
info += phosg::string_printf("%s\n", phosg::name_for_enum(game->base_version));
|
||||
}
|
||||
|
||||
if (game->check_flag(Lobby::Flag::CHEATS_ENABLED)) {
|
||||
info += "$C6Cheats enabled$C7\n";
|
||||
}
|
||||
@@ -2080,15 +2097,10 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
||||
throw logic_error("on_quest_loaded called without a quest loaded");
|
||||
}
|
||||
|
||||
auto s = l->require_server_state();
|
||||
// Replace the free-play map with the quest's map
|
||||
l->load_maps();
|
||||
|
||||
// For BB Challenge quests, don't replace the map now - the leader will send
|
||||
// an 02DF command to create overlays, which also replaces the map. (We do
|
||||
// this because 02DF is also sent when a challenge is failed and retried,
|
||||
// which reloads the map and recreates character overlays anyway.)
|
||||
if ((l->base_version != Version::BB_V4) || (l->quest->challenge_template_index < 0)) {
|
||||
l->load_maps();
|
||||
}
|
||||
auto s = l->require_server_state();
|
||||
|
||||
// Delete all floor items
|
||||
for (auto& m : l->floor_item_managers) {
|
||||
@@ -2100,26 +2112,20 @@ static void on_quest_loaded(shared_ptr<Lobby> l) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((lc->version() == Version::BB_V4) && l->map) {
|
||||
send_rare_enemy_index_list(lc, l->map->rare_enemy_indexes);
|
||||
if (lc->version() == Version::BB_V4) {
|
||||
send_rare_enemy_index_list(lc, l->map_state->bb_rare_enemy_indexes);
|
||||
}
|
||||
|
||||
// On non-BB versions, overlays are created when the quest starts because
|
||||
// the server is not informed when the clients have replaced their player
|
||||
// data. On BB, this is instead done in the 6xCF handler (for battle) or
|
||||
// the 02DF handler (for challenge).
|
||||
if (l->base_version != Version::BB_V4) {
|
||||
lc->delete_overlay();
|
||||
if (l->quest->battle_rules) {
|
||||
lc->use_default_bank();
|
||||
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
|
||||
lc->log.info("Created battle overlay");
|
||||
} else if (l->quest->challenge_template_index >= 0) {
|
||||
lc->use_default_bank();
|
||||
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
|
||||
lc->log.info("Created challenge overlay");
|
||||
l->assign_inventory_and_bank_item_ids(lc, true);
|
||||
}
|
||||
lc->delete_overlay();
|
||||
if (l->quest->battle_rules) {
|
||||
lc->use_default_bank();
|
||||
lc->create_battle_overlay(l->quest->battle_rules, s->level_table(lc->version()));
|
||||
lc->log.info("Created battle overlay");
|
||||
} else if (l->quest->challenge_template_index >= 0) {
|
||||
lc->use_default_bank();
|
||||
lc->create_challenge_overlay(lc->version(), l->quest->challenge_template_index, s->level_table(lc->version()));
|
||||
lc->log.info("Created challenge overlay");
|
||||
l->assign_inventory_and_bank_item_ids(lc, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2151,12 +2157,10 @@ void set_lobby_quest(shared_ptr<Lobby> l, shared_ptr<const Quest> q, bool substi
|
||||
l->clear_flag(Lobby::Flag::PERSISTENT);
|
||||
|
||||
l->quest = q;
|
||||
if (!is_ep3(l->base_version)) {
|
||||
if (l->episode != Episode::EP3) {
|
||||
l->episode = q->episode;
|
||||
}
|
||||
if (l->item_creator) {
|
||||
l->create_item_creator();
|
||||
}
|
||||
l->create_item_creator();
|
||||
|
||||
// There is no such thing as command AC on PSO V1 and V2 - quests just start
|
||||
// immediately when they're done downloading. (This is also the case on V3
|
||||
@@ -2296,9 +2300,10 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
// online quests, but they're served via a server data request
|
||||
// instead of the file download paradigm that other versions use.)
|
||||
auto quest_index = s->quest_index(c->version());
|
||||
const auto& categories = quest_index->categories(menu_type, Episode::EP3, c->version());
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version()));
|
||||
const auto& categories = quest_index->categories(menu_type, Episode::EP3, version_flags);
|
||||
if (categories.size() == 1) {
|
||||
auto quests = quest_index->filter(Episode::EP3, c->version(), categories[0]->category_id);
|
||||
auto quests = quest_index->filter(Episode::EP3, version_flags, categories[0]->category_id);
|
||||
send_quest_menu(c, quests, true);
|
||||
break;
|
||||
}
|
||||
@@ -2618,12 +2623,13 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
|
||||
shared_ptr<Lobby> l = c->lobby.lock();
|
||||
Episode episode = l ? l->episode : Episode::NONE;
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version())) | (l ? l->quest_version_flags() : 0);
|
||||
QuestIndex::IncludeCondition include_condition = nullptr;
|
||||
if (l && !c->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||
include_condition = l->quest_include_condition();
|
||||
}
|
||||
|
||||
const auto& quests = quest_index->filter(episode, c->version(), item_id, include_condition);
|
||||
const auto& quests = quest_index->filter(episode, version_flags, item_id, include_condition);
|
||||
send_quest_menu(c, quests, !l);
|
||||
break;
|
||||
}
|
||||
@@ -3944,7 +3950,7 @@ static void on_DF_BB(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
|
||||
}
|
||||
}
|
||||
|
||||
l->load_maps();
|
||||
l->map_state->reset();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4286,7 +4292,7 @@ static void on_C9_XB(shared_ptr<Client> c, uint16_t, uint32_t flag, string& data
|
||||
|
||||
shared_ptr<Lobby> create_game_generic(
|
||||
shared_ptr<ServerState> s,
|
||||
shared_ptr<Client> c,
|
||||
shared_ptr<Client> creator_c,
|
||||
const std::string& name,
|
||||
const std::string& password,
|
||||
Episode episode,
|
||||
@@ -4307,86 +4313,68 @@ shared_ptr<Lobby> create_game_generic(
|
||||
throw invalid_argument("incorrect difficulty level");
|
||||
}
|
||||
|
||||
auto current_lobby = c->require_lobby();
|
||||
auto current_lobby = creator_c->require_lobby();
|
||||
|
||||
size_t min_level = s->default_min_level_for_game(c->version(), episode, difficulty);
|
||||
size_t min_level = s->default_min_level_for_game(creator_c->version(), episode, difficulty);
|
||||
|
||||
auto p = c->character();
|
||||
if (!c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
|
||||
auto p = creator_c->character();
|
||||
if (!creator_c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
|
||||
// Note: We don't throw here because this is a situation players might
|
||||
// actually encounter while playing the game normally
|
||||
string msg = phosg::string_printf("You must be level %zu\nor above to play\nthis difficulty.", static_cast<size_t>(min_level + 1));
|
||||
send_lobby_message_box(c, msg);
|
||||
send_lobby_message_box(creator_c, msg);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> game = s->create_lobby(true);
|
||||
game->name = name;
|
||||
game->base_version = c->version();
|
||||
game->allowed_versions = 0;
|
||||
switch (game->base_version) {
|
||||
case Version::DC_NTE:
|
||||
game->allow_version(Version::DC_NTE);
|
||||
break;
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
game->allow_version(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
break;
|
||||
case Version::DC_V1:
|
||||
game->allow_version(Version::DC_V1);
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::PC_V2);
|
||||
}
|
||||
break;
|
||||
case Version::DC_V2:
|
||||
if (allow_v1 && (difficulty <= 2) && (mode == GameMode::NORMAL)) {
|
||||
game->allow_version(Version::DC_V1);
|
||||
}
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::PC_V2);
|
||||
}
|
||||
break;
|
||||
case Version::PC_NTE:
|
||||
game->allow_version(Version::PC_NTE);
|
||||
break;
|
||||
case Version::PC_V2:
|
||||
game->allow_version(Version::PC_V2);
|
||||
if (s->allow_dc_pc_games) {
|
||||
game->allow_version(Version::DC_V2);
|
||||
if (allow_v1 && (difficulty <= 2) && (mode == GameMode::NORMAL)) {
|
||||
game->allow_version(Version::DC_V1);
|
||||
game->episode = episode;
|
||||
game->mode = mode;
|
||||
game->difficulty = difficulty;
|
||||
game->allowed_versions = s->compatibility_groups.at(static_cast<size_t>(creator_c->version()));
|
||||
static_assert(NUM_VERSIONS == 14, "Don't forget to update the group compatibility restrictions");
|
||||
if (!allow_v1 || (difficulty > 2) || (mode != GameMode::NORMAL)) {
|
||||
game->forbid_version(Version::DC_NTE);
|
||||
game->forbid_version(Version::DC_V1_11_2000_PROTOTYPE);
|
||||
game->forbid_version(Version::DC_V1);
|
||||
}
|
||||
switch (game->episode) {
|
||||
case Episode::NONE:
|
||||
throw logic_error("game episode not set at creation time");
|
||||
case Episode::EP1:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
game->allow_version(Version::GC_NTE);
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
game->allow_version(Version::GC_V3);
|
||||
if (s->allow_gc_xb_games) {
|
||||
game->allow_version(Version::XB_V3);
|
||||
case Episode::EP2:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_v3(v) || is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
game->allow_version(Version::GC_EP3_NTE);
|
||||
break;
|
||||
case Version::GC_EP3:
|
||||
game->allow_version(Version::GC_EP3);
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
game->allow_version(Version::XB_V3);
|
||||
if (s->allow_gc_xb_games) {
|
||||
game->allow_version(Version::GC_V3);
|
||||
case Episode::EP3:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_ep3(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
game->allow_version(Version::BB_V4);
|
||||
case Episode::EP4:
|
||||
for (Version v : ALL_VERSIONS) {
|
||||
if (!is_v4(v)) {
|
||||
game->forbid_version(v);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid quest script version");
|
||||
}
|
||||
if (c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)) {
|
||||
|
||||
if (creator_c->login->account->check_flag(Account::Flag::DEBUG)) {
|
||||
game->set_flag(Lobby::Flag::DEBUG);
|
||||
}
|
||||
if (creator_c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION)) {
|
||||
game->set_flag(Lobby::Flag::IS_CLIENT_CUSTOMIZATION);
|
||||
}
|
||||
|
||||
@@ -4409,15 +4397,12 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->password = password;
|
||||
|
||||
game->creator_section_id = p->disp.visual.section_id;
|
||||
game->override_section_id = c->config.override_section_id;
|
||||
game->episode = episode;
|
||||
game->mode = mode;
|
||||
game->override_section_id = creator_c->config.override_section_id;
|
||||
if (game->mode == GameMode::CHALLENGE) {
|
||||
game->challenge_params = make_shared<Lobby::ChallengeParameters>();
|
||||
}
|
||||
game->difficulty = difficulty;
|
||||
if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
game->random_seed = c->config.override_random_seed;
|
||||
if (creator_c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) {
|
||||
game->random_seed = creator_c->config.override_random_seed;
|
||||
game->opt_rand_crypt = make_shared<PSOV2Encryption>(game->random_seed);
|
||||
}
|
||||
if (battle_player) {
|
||||
@@ -4428,7 +4413,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->exp_share_multiplier = s->exp_share_multiplier;
|
||||
|
||||
const unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites;
|
||||
switch (game->base_version) {
|
||||
switch (creator_c->version()) {
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_V1_11_2000_PROTOTYPE:
|
||||
case Version::DC_V1:
|
||||
@@ -4437,13 +4422,13 @@ shared_ptr<Lobby> create_game_generic(
|
||||
case Version::PC_V2:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v1_v2;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_battle);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v1_v2_normal);
|
||||
game->drop_mode = s->default_drop_mode_v1_v2_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_normal;
|
||||
}
|
||||
break;
|
||||
@@ -4452,32 +4437,32 @@ shared_ptr<Lobby> create_game_generic(
|
||||
case Version::XB_V3:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v3;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_battle);
|
||||
game->drop_mode = s->default_drop_mode_v3_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v3_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v3_normal);
|
||||
game->drop_mode = s->default_drop_mode_v3_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v3_normal;
|
||||
}
|
||||
break;
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
quest_flag_rewrites = nullptr;
|
||||
game->set_drop_mode(Lobby::DropMode::DISABLED);
|
||||
game->drop_mode = Lobby::DropMode::DISABLED;
|
||||
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
quest_flag_rewrites = &s->quest_flag_rewrites_v4;
|
||||
if (game->mode == GameMode::BATTLE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_battle);
|
||||
game->drop_mode = s->default_drop_mode_v4_battle;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_battle;
|
||||
} else if (game->mode == GameMode::CHALLENGE) {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_challenge);
|
||||
game->drop_mode = s->default_drop_mode_v4_challenge;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_challenge;
|
||||
} else {
|
||||
game->set_drop_mode(s->default_drop_mode_v4_normal);
|
||||
game->drop_mode = s->default_drop_mode_v4_normal;
|
||||
game->allowed_drop_modes = s->allowed_drop_modes_v4_normal;
|
||||
}
|
||||
// Disallow CLIENT mode on BB
|
||||
@@ -4491,6 +4476,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
default:
|
||||
throw logic_error("invalid quest script version");
|
||||
}
|
||||
game->create_item_creator(creator_c->version());
|
||||
|
||||
game->event = current_lobby->event;
|
||||
game->block = 0xFF;
|
||||
@@ -4512,23 +4498,27 @@ shared_ptr<Lobby> create_game_generic(
|
||||
|
||||
if (game->episode != Episode::EP3) {
|
||||
// GC NTE ignores the passed-in variations and always uses all zeroes
|
||||
if (game->base_version == Version::GC_NTE) {
|
||||
game->variations.clear(0);
|
||||
} else if (c->override_variations) {
|
||||
game->variations = *c->override_variations;
|
||||
c->override_variations.reset();
|
||||
if (creator_c->version() == Version::GC_NTE) {
|
||||
game->variations = Variations();
|
||||
game->log.info("Base version is GC_NTE; using blank variations");
|
||||
} else if (creator_c->override_variations) {
|
||||
game->variations = *creator_c->override_variations;
|
||||
creator_c->override_variations.reset();
|
||||
auto vars_str = game->variations.str();
|
||||
game->log.info("Using variations from client override: %s", vars_str.c_str());
|
||||
} else {
|
||||
auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty);
|
||||
auto sdt = s->set_data_table(creator_c->version(), game->episode, game->mode, game->difficulty);
|
||||
game->variations = sdt->generate_variations(game->episode, is_solo, game->opt_rand_crypt);
|
||||
auto vars_str = game->variations.str();
|
||||
game->log.info("Using random variations: %s", vars_str.c_str());
|
||||
}
|
||||
game->load_maps();
|
||||
} else {
|
||||
game->variations.clear(0);
|
||||
game->map = make_shared<Map>(game->base_version, game->lobby_id, game->random_seed, game->opt_rand_crypt);
|
||||
game->variations = Variations();
|
||||
}
|
||||
game->load_maps(); // Load free-play maps
|
||||
|
||||
// The game's quest flags are inherited from the creator, if known
|
||||
if (c->version() == Version::BB_V4) {
|
||||
if (creator_c->version() == Version::BB_V4) {
|
||||
game->quest_flag_values = make_unique<QuestFlags>(p->quest_flags);
|
||||
game->quest_flags_known = nullptr;
|
||||
} else {
|
||||
@@ -4540,10 +4530,10 @@ shared_ptr<Lobby> create_game_generic(
|
||||
IntegralExpression::Env env = {
|
||||
.flags = &p->quest_flags.data.at(difficulty),
|
||||
.challenge_records = &p->challenge_records,
|
||||
.team = c->team(),
|
||||
.team = creator_c->team(),
|
||||
.num_players = 1,
|
||||
.event = game->event,
|
||||
.v1_present = is_v1(game->base_version),
|
||||
.v1_present = is_v1(creator_c->version()),
|
||||
};
|
||||
for (const auto& it : *quest_flag_rewrites) {
|
||||
bool should_set = it.second.evaluate(env);
|
||||
@@ -4557,7 +4547,7 @@ shared_ptr<Lobby> create_game_generic(
|
||||
game->quest_flags_known->set(game->difficulty, it.first);
|
||||
}
|
||||
}
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
creator_c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
|
||||
game->switch_flags = make_unique<SwitchFlags>();
|
||||
@@ -4761,11 +4751,11 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
send_server_time(c);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
string variations_str;
|
||||
for (size_t z = 0; z < l->variations.size(); z++) {
|
||||
variations_str += phosg::string_printf("%" PRIX32, l->variations[z].load());
|
||||
for (size_t z = 0; z < l->variations.entries.size(); z++) {
|
||||
const auto& e = l->variations.entries[z];
|
||||
variations_str += phosg::string_printf(" %" PRIX32 "%" PRIX32, e.layout.load(), e.entities.load());
|
||||
}
|
||||
send_text_message_printf(c, "Rare seed: %08" PRIX32 "\nRare enemies: %zu\nVariations: %s\n",
|
||||
l->random_seed, l->map->rare_enemy_indexes.size(), variations_str.c_str());
|
||||
send_text_message_printf(c, "Rare seed: %08" PRIX32 "\nVariations:%s\n", l->random_seed, variations_str.c_str());
|
||||
}
|
||||
|
||||
bool should_resume_game = true;
|
||||
@@ -4800,9 +4790,7 @@ static void on_6F(shared_ptr<Client> c, uint16_t command, uint32_t, string& data
|
||||
c->config.clear_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST);
|
||||
c->log.info("LOADING_RUNNING_JOINABLE_QUEST flag cleared");
|
||||
}
|
||||
if (l->map) {
|
||||
send_rare_enemy_index_list(c, l->map->rare_enemy_indexes);
|
||||
}
|
||||
send_rare_enemy_index_list(c, l->map_state->bb_rare_enemy_indexes);
|
||||
}
|
||||
|
||||
// We should resume the game if:
|
||||
|
||||
+1569
-1270
File diff suppressed because it is too large
Load Diff
@@ -5,11 +5,11 @@
|
||||
#include "Client.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "Map.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_subcommand_multi(std::shared_ptr<Client> c, uint8_t command, uint8_t flag, std::string& data);
|
||||
bool subcommand_is_implemented(uint8_t which);
|
||||
|
||||
void send_item_notification_if_needed(
|
||||
std::shared_ptr<ServerState> s,
|
||||
@@ -21,8 +21,9 @@ void send_item_notification_if_needed(
|
||||
G_SpecializableItemDropRequest_6xA2 normalize_drop_request(const void* data, size_t size);
|
||||
|
||||
struct DropReconcileResult {
|
||||
std::shared_ptr<MapState::ObjectState> obj_st;
|
||||
std::shared_ptr<MapState::EnemyState> ene_st;
|
||||
uint8_t effective_rt_index;
|
||||
bool is_box;
|
||||
bool should_drop;
|
||||
bool ignore_def;
|
||||
};
|
||||
@@ -31,10 +32,10 @@ DropReconcileResult reconcile_drop_request_with_map(
|
||||
phosg::PrefixedLogger& log,
|
||||
Channel& client_channel,
|
||||
G_SpecializableItemDropRequest_6xA2& cmd,
|
||||
Version version,
|
||||
Episode episode,
|
||||
uint8_t event,
|
||||
const Client::Config& config,
|
||||
std::shared_ptr<Map> map,
|
||||
std::shared_ptr<MapState> map,
|
||||
bool mark_drop);
|
||||
|
||||
class Parsed6x70Data {
|
||||
|
||||
+10
-10
@@ -286,19 +286,19 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
case 0x64:
|
||||
if ((version == Version::PC_NTE) || (version == Version::PC_V2)) {
|
||||
auto& mask = check_size_t<S_JoinGame_PC_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
} else if (version == Version::XB_V3) {
|
||||
auto& mask = check_size_t<S_JoinGame_XB_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
} else if (version == Version::DC_NTE || version == Version::DC_V1_11_2000_PROTOTYPE) {
|
||||
auto& mask = check_size_t<S_JoinGame_DCNTE_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.variations = Variations();
|
||||
} else {
|
||||
auto& mask = check_size_t<S_JoinGame_DC_64>(mask_data, mask_size, sizeof(S_JoinGame_Ep3_64));
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
for (size_t offset = sizeof(S_JoinGame_GC_64) +
|
||||
offsetof(S_JoinGame_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum);
|
||||
offset + 4 <= mask_size;
|
||||
@@ -348,7 +348,7 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
case 0xE8:
|
||||
if (is_gc(version)) {
|
||||
auto& mask = check_size_t<S_JoinSpectatorTeam_Ep3_E8>(mask_data, mask_size);
|
||||
mask.rare_seed = 0;
|
||||
mask.random_seed = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
mask.players[z].disp.visual.name_color_checksum = 0;
|
||||
}
|
||||
@@ -430,8 +430,8 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
}
|
||||
case 0x0064: {
|
||||
auto& mask = check_size_t<S_JoinGame_BB_64>(mask_data, mask_size);
|
||||
mask.variations.clear(0);
|
||||
mask.rare_seed = 0;
|
||||
mask.variations = Variations();
|
||||
mask.random_seed = 0;
|
||||
break;
|
||||
}
|
||||
case 0x00B1: {
|
||||
|
||||
+166
-97
@@ -520,8 +520,7 @@ bool send_protected_command(std::shared_ptr<Client> c, const void* data, size_t
|
||||
try {
|
||||
auto s = c->require_server_state();
|
||||
auto fn = s->function_code_index->get_patch("CallProtectedHandler", c->config.specific_version);
|
||||
uint32_t size_label_value = is_big_endian(c->version()) ? data.size() : phosg::bswap32(data.size());
|
||||
send_function_call(c, fn, {{"size", size_label_value}}, data.data(), data.size());
|
||||
send_function_call(c, fn, {{"size", data.size()}}, data.data(), data.size());
|
||||
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
|
||||
if (echo_to_lobby) {
|
||||
auto l = c->lobby.lock();
|
||||
@@ -1627,8 +1626,14 @@ void send_quest_categories_menu_t(
|
||||
include_condition = l ? l->quest_include_condition() : nullptr;
|
||||
}
|
||||
|
||||
uint16_t version_flags = (1 << static_cast<size_t>(c->version()));
|
||||
auto l = c->lobby.lock();
|
||||
if (l) {
|
||||
version_flags |= l->quest_version_flags();
|
||||
}
|
||||
|
||||
vector<EntryT> entries;
|
||||
for (const auto& cat : quest_index->categories(menu_type, episode, c->version(), include_condition)) {
|
||||
for (const auto& cat : quest_index->categories(menu_type, episode, version_flags, include_condition)) {
|
||||
auto& e = entries.emplace_back();
|
||||
e.menu_id = cat->use_ep2_icon() ? MenuID::QUEST_CATEGORIES_EP2 : MenuID::QUEST_CATEGORIES_EP1;
|
||||
e.item_id = cat->category_id;
|
||||
@@ -1806,11 +1811,11 @@ static void send_join_spectator_team(shared_ptr<Client> c, shared_ptr<Lobby> l)
|
||||
|
||||
S_JoinSpectatorTeam_Ep3_E8 cmd;
|
||||
|
||||
cmd.variations.clear(0);
|
||||
cmd.variations = Variations();
|
||||
cmd.client_id = c->lobby_client_id;
|
||||
cmd.event = l->event;
|
||||
cmd.section_id = l->effective_section_id();
|
||||
cmd.rare_seed = l->random_seed;
|
||||
cmd.random_seed = l->random_seed;
|
||||
cmd.episode = 0xFF;
|
||||
|
||||
uint8_t player_count = 0;
|
||||
@@ -1953,7 +1958,7 @@ void send_join_game(shared_ptr<Client> c, shared_ptr<Lobby> l) {
|
||||
cmd.event = l->event;
|
||||
cmd.section_id = l->effective_section_id();
|
||||
cmd.challenge_mode = (l->mode == GameMode::CHALLENGE) ? 1 : 0;
|
||||
cmd.rare_seed = l->random_seed;
|
||||
cmd.random_seed = l->random_seed;
|
||||
return populate_lobby_data(cmd);
|
||||
};
|
||||
auto populate_v3_cmd = [&](auto& cmd) -> size_t {
|
||||
@@ -2568,7 +2573,6 @@ void send_warp(Channel& ch, uint8_t client_id, uint32_t floor, bool is_private)
|
||||
|
||||
void send_warp(shared_ptr<Client> c, uint32_t floor, bool is_private) {
|
||||
send_warp(c->channel, c->lobby_client_id, floor, is_private);
|
||||
c->floor = floor;
|
||||
}
|
||||
|
||||
void send_warp(shared_ptr<Lobby> l, uint32_t floor, bool is_private) {
|
||||
@@ -2587,6 +2591,10 @@ void send_ep3_change_music(Channel& ch, uint32_t song) {
|
||||
void send_game_join_sync_command(
|
||||
shared_ptr<Client> c, const void* data, size_t size, uint8_t dc_nte_sc, uint8_t dc_11_2000_sc, uint8_t sc) {
|
||||
string compressed_data = bc0_compress(data, size);
|
||||
if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) {
|
||||
c->log.info("Compressed sync data from (%zX -> %zX bytes):", size, compressed_data.size());
|
||||
phosg::print_data(stderr, data, size);
|
||||
}
|
||||
send_game_join_sync_command_compressed(c, compressed_data.data(), compressed_data.size(), size, dc_nte_sc, dc_11_2000_sc, sc);
|
||||
}
|
||||
|
||||
@@ -2639,6 +2647,9 @@ void send_game_join_sync_command_compressed(
|
||||
|
||||
void send_game_item_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send item state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
phosg::StringWriter floor_items_w;
|
||||
|
||||
@@ -2652,7 +2663,7 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
decompressed_header.next_item_id_per_player[2].load(),
|
||||
decompressed_header.next_item_id_per_player[3].load());
|
||||
|
||||
for (size_t floor = 0; floor < 0x10; floor++) {
|
||||
for (size_t floor = 0; floor < 0x0F; floor++) {
|
||||
const auto& m = l->floor_item_managers.at(floor);
|
||||
// It's important that these are added in increasing order of item_id (hence
|
||||
// why items is a map and not an unordered_map), since the game uses binary
|
||||
@@ -2667,10 +2678,9 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
|
||||
FloorItem fi;
|
||||
fi.floor = floor;
|
||||
fi.from_enemy = 0;
|
||||
fi.entity_id = 0xFFFF;
|
||||
fi.x = item->x;
|
||||
fi.z = item->z;
|
||||
fi.source_type = 0;
|
||||
fi.entity_index = 0xFFFF;
|
||||
fi.pos = item->pos;
|
||||
fi.unknown_a2 = 0;
|
||||
fi.drop_number = (floor == 0) ? 0xFFFF : (decompressed_header.next_drop_number_per_floor.at(floor - 1)++);
|
||||
fi.item = item->data;
|
||||
@@ -2686,55 +2696,84 @@ void send_game_item_state(shared_ptr<Client> c) {
|
||||
decompressed_w.write(floor_items_w.str());
|
||||
const auto& data = decompressed_w.str();
|
||||
send_game_join_sync_command(c, data.data(), data.size(), 0x5E, 0x65, 0x6D);
|
||||
}
|
||||
|
||||
void send_game_enemy_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
// Items on floors 0x0F and above can't be sent in the 6x6D command, so we
|
||||
// manually send 6x5D commands to create them if needed
|
||||
phosg::StringWriter w;
|
||||
for (size_t floor = 0x0F; floor < l->floor_item_managers.size(); floor++) {
|
||||
const auto& m = l->floor_item_managers[floor];
|
||||
for (const auto& it : m.items) {
|
||||
const auto& item = it.second;
|
||||
if (!item->visible_to_client(c->lobby_client_id)) {
|
||||
continue;
|
||||
}
|
||||
uint8_t subcommand = get_pre_v1_subcommand(c->version(), 0x4F, 0x56, 0x5D);
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, item->pos, item->data}, 0};
|
||||
cmd.item_data.encode_for_version(c->version(), s->item_parameter_table_for_encode(c->version()));
|
||||
w.put(cmd);
|
||||
}
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<G_SyncEnemyState_6x6B_Entry_Decompressed> entries;
|
||||
entries.reserve(l->map->enemies.size());
|
||||
for (size_t z = 0; z < l->map->enemies.size(); z++) {
|
||||
const auto& enemy = l->map->enemies[z];
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = enemy.game_flags;
|
||||
entry.item_drop_id = (enemy.server_flags & Map::Enemy::Flag::ITEM_DROPPED) ? 0xFFFF : (0xCA0 + z);
|
||||
entry.total_damage = enemy.total_damage;
|
||||
if (!w.str().empty()) {
|
||||
send_command(c, 0x6D, c->lobby_client_id, w.str());
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5C, 0x63, 0x6B);
|
||||
}
|
||||
|
||||
void send_game_object_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send object state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<G_SyncObjectState_6x6C_Entry_Decompressed> entries;
|
||||
entries.reserve(l->map->objects.size());
|
||||
for (size_t z = 0; z < l->map->objects.size(); z++) {
|
||||
const auto& obj = l->map->objects[z];
|
||||
vector<SyncObjectStateEntry> entries;
|
||||
for (auto obj_st : l->map_state->iter_object_states(c->version())) {
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = obj.game_flags;
|
||||
entry.item_drop_id = (obj.item_drop_checked) ? 0xFFFF : (0x100 + z);
|
||||
entry.flags = obj_st->game_flags;
|
||||
entry.item_drop_id = (obj_st->item_drop_checked)
|
||||
? 0xFFFF
|
||||
: (0x100 + l->map_state->index_for_object_state(c->version(), obj_st));
|
||||
}
|
||||
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5D, 0x64, 0x6C);
|
||||
}
|
||||
|
||||
void send_game_set_state(shared_ptr<Client> c) {
|
||||
void send_game_enemy_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->map) {
|
||||
return;
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send enemy state in non-game lobby");
|
||||
}
|
||||
auto s = c->require_server_state();
|
||||
|
||||
vector<SyncEnemyStateEntry> entries;
|
||||
for (auto ene_st : l->map_state->iter_enemy_states(c->version())) {
|
||||
auto& entry = entries.emplace_back();
|
||||
entry.flags = ene_st->game_flags;
|
||||
entry.item_drop_id = (ene_st->server_flags & MapState::EnemyState::Flag::ITEM_DROPPED)
|
||||
? 0xFFFF
|
||||
: (0xCA0 + l->map_state->index_for_enemy_state(c->version(), ene_st));
|
||||
entry.total_damage = ene_st->total_damage;
|
||||
}
|
||||
|
||||
size_t num_object_sets = l->map->objects.size();
|
||||
size_t num_enemy_sets = l->map->enemy_set_flags.size();
|
||||
send_game_join_sync_command(c, entries.data(), entries.size() * sizeof(entries[0]), 0x5C, 0x63, 0x6B);
|
||||
}
|
||||
|
||||
void send_game_set_state(shared_ptr<Client> c) {
|
||||
auto l = c->require_lobby();
|
||||
if (!l->is_game()) {
|
||||
throw logic_error("cannot send set state in non-game lobby");
|
||||
}
|
||||
|
||||
size_t num_object_sets = 0;
|
||||
size_t num_enemy_sets = 0;
|
||||
size_t num_events = 0;
|
||||
for (const auto& fc : l->map_state->floor_config_entries) {
|
||||
if (fc.super_map) {
|
||||
const auto& entities = fc.super_map->version(c->version());
|
||||
num_object_sets += entities.objects.size();
|
||||
num_enemy_sets += entities.enemy_sets.size();
|
||||
num_events += entities.events.size();
|
||||
}
|
||||
}
|
||||
|
||||
G_SyncSetFlagState_6x6E_Decompressed::EntitySetFlags entity_set_flags_header;
|
||||
entity_set_flags_header.object_set_flags_offset = sizeof(entity_set_flags_header);
|
||||
@@ -2744,22 +2783,47 @@ void send_game_set_state(shared_ptr<Client> c) {
|
||||
|
||||
G_SyncSetFlagState_6x6E_Decompressed header;
|
||||
header.entity_set_flags_size = sizeof(entity_set_flags_header) + (num_object_sets + num_enemy_sets) * sizeof(le_uint16_t);
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * l->map->events.size();
|
||||
header.event_set_flags_size = sizeof(le_uint16_t) * num_events;
|
||||
header.switch_flags_size = is_v1(c->version()) ? 0x200 : 0x240;
|
||||
header.total_size = header.entity_set_flags_size + header.event_set_flags_size + header.switch_flags_size;
|
||||
|
||||
phosg::StringWriter w;
|
||||
w.put(header);
|
||||
w.put(entity_set_flags_header);
|
||||
for (const auto& obj : l->map->objects) {
|
||||
w.put_u16l(obj.set_flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& obj_st : l->map_state->iter_object_states(c->version())) {
|
||||
w.put_u16l(obj_st->set_flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_object_sets * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of object set flags added");
|
||||
}
|
||||
}
|
||||
for (uint16_t enemy_set_flags : l->map->enemy_set_flags) {
|
||||
w.put_u16l(enemy_set_flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& ene_st : l->map_state->iter_enemy_set_states(c->version())) {
|
||||
w.put_u16l(ene_st->set_flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_enemy_sets * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of enemy set flags added");
|
||||
}
|
||||
}
|
||||
for (const auto& event : l->map->events) {
|
||||
w.put_u16l(event.flags);
|
||||
|
||||
{
|
||||
size_t size_before = w.size();
|
||||
for (const auto& ev_st : l->map_state->iter_event_states(c->version())) {
|
||||
w.put_u16l(ev_st->flags);
|
||||
}
|
||||
size_t bytes_added = w.size() - size_before;
|
||||
if (bytes_added != num_events * sizeof(le_uint16_t)) {
|
||||
throw logic_error("incorrect number of event flags added");
|
||||
}
|
||||
}
|
||||
|
||||
if (l->switch_flags) {
|
||||
static_assert(sizeof(SwitchFlags) == 0x240, "switch_flags size is incorrect");
|
||||
w.write(l->switch_flags->data.data(), header.switch_flags_size);
|
||||
@@ -2869,16 +2933,15 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
|
||||
to_send.telepipe.owner_client_id = from_c->telepipe_state.client_id2;
|
||||
to_send.telepipe.floor = from_c->telepipe_state.floor;
|
||||
to_send.telepipe.unknown_a1 = from_c->telepipe_state.unknown_b3;
|
||||
to_send.telepipe.x = from_c->telepipe_state.x;
|
||||
to_send.telepipe.y = from_c->telepipe_state.y;
|
||||
to_send.telepipe.z = from_c->telepipe_state.z;
|
||||
to_send.telepipe.pos = from_c->telepipe_state.pos;
|
||||
to_send.telepipe.unknown_a3 = from_c->telepipe_state.unknown_a3;
|
||||
}
|
||||
|
||||
if (apply_overrides) {
|
||||
auto from_p = from_c->character();
|
||||
to_send.base.x = from_c->x;
|
||||
to_send.base.z = from_c->z;
|
||||
to_send.base.pos.x = from_c->pos.x;
|
||||
to_send.base.pos.y = 0.0;
|
||||
to_send.base.pos.z = from_c->pos.z;
|
||||
to_send.bonus_hp_from_materials = from_p->inventory.hp_from_materials;
|
||||
to_send.bonus_tp_from_materials = from_p->inventory.tp_from_materials;
|
||||
to_send.language = from_c->language();
|
||||
@@ -2928,41 +2991,44 @@ void send_game_player_state(shared_ptr<Client> to_c, shared_ptr<Client> from_c,
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_item_to_channel(shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{subcommand, 0x0B, 0x0000}, {floor, from_enemy, entity_id, x, z, 0, 0, item}}, 0};
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t entity_id) {
|
||||
auto s = l->require_server_state();
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_item_to_channel(s, c->channel, item, from_enemy, floor, x, z, entity_id);
|
||||
void send_drop_item_to_channel(
|
||||
shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const ItemData& item,
|
||||
uint8_t source_type,
|
||||
uint8_t floor,
|
||||
const VectorXZF& pos,
|
||||
uint16_t entity_index) {
|
||||
if (entity_index == 0xFFFF) {
|
||||
send_drop_stacked_item_to_channel(s, ch, item, floor, pos);
|
||||
} else {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x51, 0x58, 0x5F);
|
||||
G_DropItem_PC_V3_BB_6x5F cmd = {
|
||||
{{subcommand, 0x0B, 0x0000}, {floor, source_type, entity_index, pos, 0, 0, item}}, 0};
|
||||
cmd.item.item.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void send_drop_stacked_item_to_channel(
|
||||
shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z) {
|
||||
shared_ptr<ServerState> s,
|
||||
Channel& ch,
|
||||
const ItemData& item,
|
||||
uint8_t floor,
|
||||
const VectorXZF& pos) {
|
||||
uint8_t subcommand = get_pre_v1_subcommand(ch.version, 0x4F, 0x56, 0x5D);
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, x, z, item}, 0};
|
||||
G_DropStackedItem_PC_V3_BB_6x5D cmd = {{{subcommand, 0x0A, 0x0000}, floor, 0, pos, item}, 0};
|
||||
cmd.item_data.encode_for_version(ch.version, s->item_parameter_table_for_encode(ch.version));
|
||||
ch.send(0x60, 0x00, &cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z) {
|
||||
void send_drop_stacked_item_to_lobby(shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, const VectorXZF& pos) {
|
||||
auto s = l->require_server_state();
|
||||
for (auto& c : l->clients) {
|
||||
if (!c) {
|
||||
continue;
|
||||
}
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, floor, x, z);
|
||||
send_drop_stacked_item_to_channel(s, c->channel, item, floor, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3644,18 +3710,17 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
{
|
||||
G_SetGameMetadata_Ep3_6xB4x52 cmd;
|
||||
cmd.total_spectators = total_spectators;
|
||||
if ((l->base_version != Version::GC_EP3_NTE) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
|
||||
}
|
||||
// Note: We can't use send_command_t(l, ...) here because that would send
|
||||
// the same command to l and to all watcher lobbies. The commands should
|
||||
// have different values depending on who's in each watcher lobby, so we
|
||||
// have to manually send to each client here.
|
||||
for (auto c : l->clients) {
|
||||
if (c) {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
if ((c->version() == Version::GC_EP3) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
|
||||
send_command_t(c, 0xC9, 0x00, masked_cmd);
|
||||
} else {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3683,12 +3748,19 @@ void send_ep3_update_game_metadata(shared_ptr<Lobby> l) {
|
||||
cmd.total_spectators = total_spectators;
|
||||
cmd.text_size = text.size();
|
||||
cmd.text.encode(text, 1);
|
||||
if ((watcher_l->base_version != Version::GC_EP3_NTE) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key);
|
||||
for (auto c : watcher_l->clients) {
|
||||
if (c) {
|
||||
if ((c->version() == Version::GC_EP3) &&
|
||||
!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
|
||||
G_SetGameMetadata_Ep3_6xB4x52 masked_cmd = cmd;
|
||||
uint8_t mask_key = (phosg::random_object<uint32_t>() % 0xFF) + 1;
|
||||
set_mask_for_ep3_game_command(&masked_cmd, sizeof(masked_cmd), mask_key);
|
||||
send_command_t(c, 0xC9, 0x00, masked_cmd);
|
||||
} else {
|
||||
send_command_t(c, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
send_command_t(watcher_l, 0xC9, 0x00, cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3958,15 +4030,13 @@ void send_ep3_card_auction(shared_ptr<Lobby> l) {
|
||||
}
|
||||
num_cards = min<uint16_t>(num_cards, 0x14);
|
||||
|
||||
auto card_index = l->is_ep3_nte() ? s->ep3_card_index_trial : s->ep3_card_index;
|
||||
|
||||
uint64_t distribution_size = 0;
|
||||
for (const auto& e : s->ep3_card_auction_pool) {
|
||||
distribution_size += e.probability;
|
||||
}
|
||||
|
||||
auto card_index = (l->base_version == Version::GC_EP3_NTE)
|
||||
? s->ep3_card_index_trial
|
||||
: s->ep3_card_index;
|
||||
|
||||
S_StartCardAuction_Ep3_EF cmd;
|
||||
cmd.points_available = s->ep3_card_auction_points;
|
||||
for (size_t z = 0; z < num_cards; z++) {
|
||||
@@ -4002,10 +4072,9 @@ void send_server_time(shared_ptr<Client> c) {
|
||||
gmtime_r(&t_secs, &t_parsed);
|
||||
|
||||
string time_str(128, 0);
|
||||
size_t len = strftime(time_str.data(), time_str.size(),
|
||||
"%Y:%m:%d: %H:%M:%S.000", &t_parsed);
|
||||
size_t len = strftime(time_str.data(), time_str.size(), "%Y:%m:%d: %H:%M:%S.000", &t_parsed);
|
||||
if (len == 0) {
|
||||
throw runtime_error("phosg::format_time buffer too short");
|
||||
throw logic_error("strftime buffer too short");
|
||||
}
|
||||
time_str.resize(len);
|
||||
|
||||
|
||||
+3
-5
@@ -354,13 +354,11 @@ void send_game_set_state(std::shared_ptr<Client> c);
|
||||
void send_game_flag_state(std::shared_ptr<Client> c);
|
||||
void send_game_player_state(std::shared_ptr<Client> to_c, std::shared_ptr<Client> from_c, bool apply_overrides);
|
||||
void send_drop_item_to_channel(std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
void send_drop_item_to_lobby(std::shared_ptr<Lobby> l, const ItemData& item,
|
||||
bool from_enemy, uint8_t floor, float x, float z, uint16_t request_id);
|
||||
uint8_t source_type, uint8_t floor, const VectorXZF& pos, uint16_t entity_index);
|
||||
void send_drop_stacked_item_to_channel(
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, float x, float z);
|
||||
std::shared_ptr<ServerState> s, Channel& ch, const ItemData& item, uint8_t floor, const VectorXZF& pos);
|
||||
void send_drop_stacked_item_to_lobby(
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, float x, float z);
|
||||
std::shared_ptr<Lobby> l, const ItemData& item, uint8_t floor, const VectorXZF& pos);
|
||||
void send_pick_up_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, uint32_t id, uint8_t floor);
|
||||
void send_create_inventory_item_to_client(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item);
|
||||
void send_create_inventory_item_to_lobby(std::shared_ptr<Client> c, uint8_t client_id, const ItemData& item, bool exclude_c = false);
|
||||
|
||||
+4
-2
@@ -256,6 +256,8 @@ CommandDefinition c_reload(
|
||||
args.s->load_bb_private_keys(true);
|
||||
} else if (type == "accounts") {
|
||||
args.s->load_accounts(true);
|
||||
} else if (type == "maps") {
|
||||
args.s->load_maps(true);
|
||||
} else if (type == "caches") {
|
||||
args.s->clear_file_caches(true);
|
||||
} else if (type == "patch-files") {
|
||||
@@ -1175,8 +1177,8 @@ void fn_create_item(CommandArgs& args) {
|
||||
send_text_message(ses->client_channel, "$C7Next drop:\n" + name);
|
||||
|
||||
} else {
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->x, ses->z);
|
||||
send_drop_stacked_item_to_channel(s, ses->client_channel, item, ses->floor, ses->pos);
|
||||
send_drop_stacked_item_to_channel(s, ses->server_channel, item, ses->floor, ses->pos);
|
||||
|
||||
string name = s->describe_item(ses->version(), ses->next_drop_item, true);
|
||||
send_text_message(ses->client_channel, "$C7Item created:\n" + name);
|
||||
|
||||
+200
-17
@@ -3,6 +3,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Image.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Platform.hh>
|
||||
@@ -563,11 +564,6 @@ shared_ptr<const string> ServerState::load_bb_file(
|
||||
}
|
||||
|
||||
shared_ptr<const string> ServerState::load_map_file(Version version, const string& filename) const {
|
||||
auto& cache = this->map_file_caches.at(static_cast<size_t>(version));
|
||||
return cache->get(filename, bind(&ServerState::load_map_file_uncached, this, version, placeholders::_1));
|
||||
}
|
||||
|
||||
shared_ptr<const string> ServerState::load_map_file_uncached(Version version, const string& filename) const {
|
||||
if (version == Version::BB_V4) {
|
||||
try {
|
||||
return this->load_bb_file(filename);
|
||||
@@ -1044,8 +1040,32 @@ void ServerState::load_config_early() {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true);
|
||||
this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true);
|
||||
try {
|
||||
const auto& groups = this->config_json->get_list("CompatibilityGroups");
|
||||
this->compatibility_groups.fill(0);
|
||||
for (size_t v_s = 0; v_s < groups.size(); v_s++) {
|
||||
this->compatibility_groups[v_s] = groups[v_s]->as_int();
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
static_assert(NUM_VERSIONS == 14, "Don't forget to update the default compatibility groups");
|
||||
this->compatibility_groups = {
|
||||
0x0000, // PC_PATCH
|
||||
0x0000, // BB_PATCH
|
||||
0x0004, // DC_NTE compatible only with itself
|
||||
0x0008, // DC_V1_11_2000_PROTOTYPE compatible only with itself
|
||||
0x00B0, // DC_V1 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x00B0, // DC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0040, // PC_NTE compatible only with itself
|
||||
0x00B0, // PC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0100, // GC_NTE compatible only with itself
|
||||
0x1200, // GC_V3 compatible with GC_V3 and XB_V3
|
||||
0x0400, // GC_EP3_NTE compatible only with itself
|
||||
0x0800, // GC_EP3 compatible only with itself
|
||||
0x1200, // XB_V3 compatible with GC_V3 and XB_V3
|
||||
0x2000, // BB_V4 compatible only with itself
|
||||
};
|
||||
}
|
||||
|
||||
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
|
||||
|
||||
this->version_name_colors.reset();
|
||||
@@ -1216,20 +1236,20 @@ void ServerState::load_config_early() {
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
shared_ptr<const Map::RareEnemyRates> prev = Map::DEFAULT_RARE_ENEMIES;
|
||||
shared_ptr<const MapState::RareEnemyRates> prev = MapState::DEFAULT_RARE_ENEMIES;
|
||||
try {
|
||||
string key = "RareEnemyRates-";
|
||||
key += token_name_for_difficulty(z);
|
||||
this->rare_enemy_rates_by_difficulty[z] = make_shared<Map::RareEnemyRates>(this->config_json->at(key));
|
||||
this->rare_enemy_rates_by_difficulty[z] = make_shared<MapState::RareEnemyRates>(this->config_json->at(key));
|
||||
prev = this->rare_enemy_rates_by_difficulty[z];
|
||||
} catch (const out_of_range&) {
|
||||
this->rare_enemy_rates_by_difficulty[z] = prev;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this->rare_enemy_rates_challenge = make_shared<Map::RareEnemyRates>(this->config_json->at("RareEnemyRates-Challenge"));
|
||||
this->rare_enemy_rates_challenge = make_shared<MapState::RareEnemyRates>(this->config_json->at("RareEnemyRates-Challenge"));
|
||||
} catch (const out_of_range&) {
|
||||
this->rare_enemy_rates_challenge = Map::DEFAULT_RARE_ENEMIES;
|
||||
this->rare_enemy_rates_challenge = MapState::DEFAULT_RARE_ENEMIES;
|
||||
}
|
||||
|
||||
this->min_levels_v4[0] = DEFAULT_MIN_LEVELS_V4_EP1;
|
||||
@@ -1398,7 +1418,7 @@ void ServerState::load_config_late() {
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
auto parse_primary_identifier_list = [&](const char* key, Version base_version) -> unordered_set<uint32_t> {
|
||||
auto parse_primary_identifier_list = [&](const char* key, Version v) -> unordered_set<uint32_t> {
|
||||
unordered_set<uint32_t> ret;
|
||||
try {
|
||||
for (const auto& pi_json : this->config_json->get_list(key)) {
|
||||
@@ -1406,7 +1426,7 @@ void ServerState::load_config_late() {
|
||||
ret.emplace(pi_json->as_int());
|
||||
} else {
|
||||
try {
|
||||
auto item = this->parse_item_description(base_version, pi_json->as_string());
|
||||
auto item = this->parse_item_description(v, pi_json->as_string());
|
||||
ret.emplace(item.primary_identifier());
|
||||
} catch (const exception& e) {
|
||||
config_log.warning("Cannot parse item description \"%s\": %s (skipping entry)", pi_json->as_string().c_str(), e.what());
|
||||
@@ -1532,12 +1552,174 @@ void ServerState::load_patch_indexes(bool from_non_event_thread) {
|
||||
this->forward_or_call(from_non_event_thread, std::move(set));
|
||||
}
|
||||
|
||||
void ServerState::load_maps(bool from_non_event_thread) {
|
||||
using SDT = SetDataTable;
|
||||
|
||||
config_log.info("Loading free play map files");
|
||||
|
||||
unordered_map<uint64_t, shared_ptr<const MapFile>> map_file_for_source_hash;
|
||||
map<uint32_t, array<shared_ptr<const MapFile>, NUM_VERSIONS>> map_files;
|
||||
for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) {
|
||||
const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
if ((episode == Episode::EP2 && is_v1_or_v2(v) && (v != Version::GC_NTE)) ||
|
||||
(episode == Episode::EP4 && !is_v4(v))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const array<GameMode, 4> modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
for (GameMode mode : modes) {
|
||||
if (((mode == GameMode::BATTLE || mode == GameMode::CHALLENGE) && is_v1(v)) ||
|
||||
(mode == GameMode::SOLO && !is_v4(v))) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t difficulty = 0; difficulty < 4; difficulty++) {
|
||||
if ((difficulty == 3) && is_v1(v)) {
|
||||
continue;
|
||||
}
|
||||
auto sdt = this->set_data_table(v, episode, mode, difficulty);
|
||||
for (uint8_t floor = 0; floor < 0x12; floor++) {
|
||||
auto variation_maxes = sdt->num_free_play_variations_for_floor(episode, mode == GameMode::SOLO, floor);
|
||||
for (size_t var_layout = 0; var_layout < variation_maxes.layout; var_layout++) {
|
||||
for (size_t var_entities = 0; var_entities < variation_maxes.entities; var_entities++) {
|
||||
uint32_t supermap_key = this->supermap_key(episode, mode, difficulty, floor, var_layout, var_entities);
|
||||
|
||||
auto objects_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::OBJECT_SETS);
|
||||
auto enemies_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::ENEMY_SETS);
|
||||
auto events_filename = sdt->map_filename_for_variation(
|
||||
episode, mode, floor, var_layout, var_entities, SDT::FilenameType::EVENTS);
|
||||
auto objects_data = objects_filename.empty() ? nullptr : this->load_map_file(v, objects_filename);
|
||||
auto enemies_data = enemies_filename.empty() ? nullptr : this->load_map_file(v, enemies_filename);
|
||||
auto events_data = enemies_filename.empty() ? nullptr : this->load_map_file(v, events_filename);
|
||||
|
||||
if (objects_data || enemies_data || events_data) {
|
||||
// TODO: This is ugly; the hash computation probably should be factored into MapFile
|
||||
uint64_t source_hash = ((objects_data ? phosg::fnv1a64(*objects_data) : 0) ^
|
||||
(enemies_data ? phosg::fnv1a64(*enemies_data) : 0) ^
|
||||
(events_data ? phosg::fnv1a64(*events_data) : 0));
|
||||
shared_ptr<const MapFile> map_file;
|
||||
try {
|
||||
map_file = map_file_for_source_hash.at(source_hash);
|
||||
} catch (const out_of_range&) {
|
||||
map_file = make_shared<MapFile>(floor, objects_data, enemies_data, events_data);
|
||||
if (map_file->source_hash() != source_hash) {
|
||||
throw logic_error("incorrect source hash");
|
||||
}
|
||||
map_file_for_source_hash.emplace(map_file->source_hash(), map_file);
|
||||
}
|
||||
|
||||
// Uncomment for debugging
|
||||
// config_log.info("Maps for %s %s %s %s %02hhX %02zu %02zu (%08" PRIX32 " => %016" PRIX64 "): objects=%s(%s)+0x%zX enemies=%s(%s)+0x%zX events=%s(%s)+0x%zX",
|
||||
// phosg::name_for_enum(v),
|
||||
// name_for_episode(episode),
|
||||
// name_for_mode(mode),
|
||||
// name_for_difficulty(difficulty),
|
||||
// floor,
|
||||
// var_layout,
|
||||
// var_entities,
|
||||
// supermap_key,
|
||||
// map_file->source_hash(),
|
||||
// objects_filename.empty() ? "(none)" : objects_filename.c_str(),
|
||||
// objects_data ? "present" : "missing",
|
||||
// map_file->count_object_sets(),
|
||||
// enemies_filename.empty() ? "(none)" : enemies_filename.c_str(),
|
||||
// enemies_data ? "present" : "missing",
|
||||
// map_file->count_enemy_sets(),
|
||||
// events_filename.empty() ? "(none)" : events_filename.c_str(),
|
||||
// events_data ? "present" : "missing",
|
||||
// map_file->count_events());
|
||||
|
||||
map_files[supermap_key].at(static_cast<size_t>(v)) = map_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_log.info("Constructing free play supermaps");
|
||||
|
||||
unordered_map<uint64_t, shared_ptr<const SuperMap>> supermap_for_source_hash_sum;
|
||||
unordered_map<uint32_t, shared_ptr<const SuperMap>> new_supermaps;
|
||||
for (const auto& it : map_files) {
|
||||
uint64_t source_hash_sum = 0;
|
||||
for (auto map_file : it.second) {
|
||||
source_hash_sum += map_file ? map_file->source_hash() : 0;
|
||||
}
|
||||
|
||||
Episode episode = static_cast<Episode>((it.first >> 28) & 7);
|
||||
// Uncomment for debugging
|
||||
// auto mode = static_cast<GameMode>((it.first >> 26) & 3);
|
||||
// uint8_t difficulty = (it.first >> 24) & 3;
|
||||
// uint8_t floor = (it.first >> 16) & 0xFF;
|
||||
// uint8_t layout = (it.first >> 8) & 0xFF;
|
||||
// uint8_t entities = (it.first >> 0) & 0xFF;
|
||||
// fprintf(stderr, "SuperMap for %s %s %s %02hhX %02hhX %02hhX (%08" PRIX32 "): %016" PRIX64 " from",
|
||||
// name_for_episode(episode),
|
||||
// name_for_mode(mode),
|
||||
// name_for_difficulty(difficulty),
|
||||
// floor,
|
||||
// layout,
|
||||
// entities,
|
||||
// it.first,
|
||||
// source_hash_sum);
|
||||
// for (const auto& map_file : it.second) {
|
||||
// if (map_file) {
|
||||
// fprintf(stderr, " %016" PRIX64, map_file->source_hash());
|
||||
// } else {
|
||||
// fprintf(stderr, " ----------------");
|
||||
// }
|
||||
// }
|
||||
// fputc('\n', stderr);
|
||||
|
||||
shared_ptr<const SuperMap> supermap;
|
||||
try {
|
||||
supermap = supermap_for_source_hash_sum.at(source_hash_sum);
|
||||
static_game_data_log.info("Linking existing free play supermap %016" PRIX64 " for key %08" PRIX32, source_hash_sum, it.first);
|
||||
} catch (const out_of_range&) {
|
||||
supermap = make_shared<SuperMap>(episode, it.second);
|
||||
supermap_for_source_hash_sum.emplace(source_hash_sum, supermap);
|
||||
static_game_data_log.info("Constructed free play supermap %016" PRIX64 " for key %08" PRIX32, source_hash_sum, it.first);
|
||||
}
|
||||
new_supermaps.emplace(it.first, supermap);
|
||||
}
|
||||
|
||||
auto set = [s = this->shared_from_this(), new_supermaps = std::move(new_supermaps)]() {
|
||||
s->supermaps = std::move(new_supermaps);
|
||||
};
|
||||
this->forward_or_call(from_non_event_thread, std::move(set));
|
||||
}
|
||||
|
||||
vector<shared_ptr<const SuperMap>> ServerState::supermaps_for_variations(
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
const Variations& variations) const {
|
||||
vector<shared_ptr<const SuperMap>> ret;
|
||||
for (size_t floor = 0; floor < 0x12; floor++) {
|
||||
Variations::Entry e;
|
||||
if (floor < variations.entries.size()) {
|
||||
e = variations.entries[floor];
|
||||
}
|
||||
ret.push_back(this->get_supermap(episode, mode, difficulty, floor, e.layout, e.entities));
|
||||
if (ret.back()) {
|
||||
static_game_data_log.info("Using supermap %08" PRIX32 " for floor %02zX layout %" PRIX32 " entities %" PRIX32,
|
||||
this->supermap_key(episode, mode, difficulty, floor, e.layout, e.entities),
|
||||
floor, e.layout.load(), e.entities.load());
|
||||
} else {
|
||||
static_game_data_log.info("No supermap available for floor %02zX layout %" PRIX32 " entities %" PRIX32,
|
||||
floor, e.layout.load(), e.entities.load());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ServerState::clear_file_caches(bool from_non_event_thread) {
|
||||
auto set = [s = this->shared_from_this()]() {
|
||||
config_log.info("Clearing map file caches");
|
||||
for (auto& cache : s->map_file_caches) {
|
||||
cache = make_shared<ThreadSafeFileCache>();
|
||||
}
|
||||
config_log.info("Clearing BB stream file cache");
|
||||
s->bb_stream_files_cache.reset(new FileContentsCache(3600000000ULL));
|
||||
config_log.info("Clearing BB system cache");
|
||||
@@ -2041,6 +2223,7 @@ void ServerState::load_all() {
|
||||
this->load_dol_files(false);
|
||||
this->create_default_lobbies();
|
||||
this->load_set_data_tables(false);
|
||||
this->load_maps(false);
|
||||
this->load_battle_params(false);
|
||||
this->load_level_tables(false);
|
||||
this->load_text_index(false);
|
||||
|
||||
+28
-5
@@ -115,8 +115,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
bool allow_unregistered_users = false;
|
||||
bool allow_pc_nte = false;
|
||||
bool use_temp_accounts_for_prototypes = true;
|
||||
bool allow_dc_pc_games = true;
|
||||
bool allow_gc_xb_games = true;
|
||||
std::array<uint16_t, NUM_VERSIONS> compatibility_groups = {};
|
||||
bool enable_chat_commands = true;
|
||||
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
|
||||
uint32_t client_customization_name_color = 0x00000000;
|
||||
@@ -173,7 +172,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<const FunctionCodeIndex> function_code_index;
|
||||
std::shared_ptr<const PatchFileIndex> pc_patch_file_index;
|
||||
std::shared_ptr<const PatchFileIndex> bb_patch_file_index;
|
||||
std::array<std::shared_ptr<ThreadSafeFileCache>, NUM_VERSIONS> map_file_caches;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<const SuperMap>> supermaps; // Keyed by supermap_key
|
||||
std::shared_ptr<FileContentsCache> bb_stream_files_cache;
|
||||
std::shared_ptr<FileContentsCache> bb_system_cache;
|
||||
std::shared_ptr<FileContentsCache> gba_files_cache;
|
||||
@@ -210,8 +209,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::array<std::shared_ptr<const SetDataTableBase>, NUM_VERSIONS> set_data_tables_ep1_ult;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table;
|
||||
std::shared_ptr<const SetDataTableBase> bb_solo_set_data_table_ep1_ult;
|
||||
std::array<std::shared_ptr<const Map::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const Map::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::shared_ptr<const MapState::RareEnemyRates>, 4> rare_enemy_rates_by_difficulty;
|
||||
std::shared_ptr<const MapState::RareEnemyRates> rare_enemy_rates_challenge;
|
||||
std::array<std::array<size_t, 4>, 3> min_levels_v4; // Indexed as [episode][difficulty]
|
||||
std::vector<std::string> bb_required_patches;
|
||||
CheatFlags cheat_flags;
|
||||
@@ -401,6 +400,29 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
std::shared_ptr<PatchServer::Config> generate_patch_server_config(bool is_bb) const;
|
||||
void update_dependent_server_configs() const;
|
||||
|
||||
static constexpr uint32_t supermap_key(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) {
|
||||
return (static_cast<uint32_t>(episode) << 28) |
|
||||
(static_cast<uint32_t>(mode) << 26) |
|
||||
(static_cast<uint32_t>(difficulty) << 24) |
|
||||
(static_cast<uint32_t>(floor) << 16) |
|
||||
(static_cast<uint32_t>(layout) << 8) |
|
||||
(static_cast<uint32_t>(entities) << 0);
|
||||
}
|
||||
inline std::shared_ptr<const SuperMap> get_supermap(
|
||||
Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) const {
|
||||
try {
|
||||
return this->supermaps.at(this->supermap_key(episode, mode, difficulty, floor, layout, entities));
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<const SuperMap>> supermaps_for_variations(
|
||||
Episode episode,
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
const Variations& variations) const;
|
||||
|
||||
// The following functions may only be called from a non-event thread if they
|
||||
// take a from_non_event_thread argument; any function that does not have this
|
||||
// argument must be called only from the event thread.
|
||||
@@ -413,6 +435,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
void load_accounts(bool from_non_event_thread);
|
||||
void load_teams(bool from_non_event_thread);
|
||||
void load_patch_indexes(bool from_non_event_thread);
|
||||
void load_maps(bool from_non_event_thread);
|
||||
void clear_file_caches(bool from_non_event_thread);
|
||||
void load_battle_params(bool from_non_event_thread);
|
||||
void load_level_tables(bool from_non_event_thread);
|
||||
|
||||
+2
-1
@@ -6,6 +6,7 @@
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
@@ -178,7 +179,7 @@ BinaryTextSet::BinaryTextSet(const std::string& pr2_data, size_t collection_coun
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
size_t root_offset = has_rel_footer
|
||||
? r.pget_u32l(r.size() - 0x10)
|
||||
? r.pget<RELFileFooter>(r.size() - 0x20).root_offset.load()
|
||||
: (r.size() - collection_count * sizeof(le_uint32_t));
|
||||
|
||||
phosg::StringReader collection_offsets_r = r.sub(root_offset, collection_count * sizeof(le_uint32_t));
|
||||
|
||||
+63
-20
@@ -24,7 +24,50 @@ enum class Version {
|
||||
UNKNOWN = 15,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = static_cast<size_t>(Version::BB_V4) + 1;
|
||||
constexpr std::array<Version, 14> ALL_VERSIONS = {
|
||||
Version::PC_PATCH,
|
||||
Version::BB_PATCH,
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::GC_EP3_NTE,
|
||||
Version::GC_EP3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
constexpr std::array<Version, 12> ALL_NON_PATCH_VERSIONS = {
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::GC_EP3_NTE,
|
||||
Version::GC_EP3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
constexpr std::array<Version, 10> ALL_ARPG_SEMANTIC_VERSIONS = {
|
||||
Version::DC_NTE,
|
||||
Version::DC_V1_11_2000_PROTOTYPE,
|
||||
Version::DC_V1,
|
||||
Version::DC_V2,
|
||||
Version::PC_NTE,
|
||||
Version::PC_V2,
|
||||
Version::GC_NTE,
|
||||
Version::GC_V3,
|
||||
Version::XB_V3,
|
||||
Version::BB_V4,
|
||||
};
|
||||
|
||||
constexpr size_t NUM_VERSIONS = ALL_VERSIONS.size();
|
||||
constexpr size_t NUM_PATCH_VERSIONS = static_cast<size_t>(Version::BB_PATCH) + 1;
|
||||
constexpr size_t NUM_NON_PATCH_VERSIONS = NUM_VERSIONS - NUM_PATCH_VERSIONS;
|
||||
|
||||
@@ -35,7 +78,7 @@ const char* phosg::name_for_enum<Version>(Version v);
|
||||
template <>
|
||||
Version phosg::enum_for_name<Version>(const char* name);
|
||||
|
||||
inline bool is_any_nte(Version version) {
|
||||
constexpr bool is_any_nte(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
@@ -43,55 +86,55 @@ inline bool is_any_nte(Version version) {
|
||||
(version == Version::GC_EP3_NTE);
|
||||
}
|
||||
|
||||
inline bool is_patch(Version version) {
|
||||
constexpr bool is_patch(Version version) {
|
||||
return (version == Version::PC_PATCH) || (version == Version::BB_PATCH);
|
||||
}
|
||||
inline bool is_pre_v1(Version version) {
|
||||
constexpr bool is_pre_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE);
|
||||
}
|
||||
inline bool is_v1(Version version) {
|
||||
constexpr bool is_v1(Version version) {
|
||||
return (version == Version::DC_NTE) || (version == Version::DC_V1_11_2000_PROTOTYPE) || (version == Version::DC_V1);
|
||||
}
|
||||
inline bool is_v2(Version version) {
|
||||
constexpr bool is_v2(Version version) {
|
||||
return (version == Version::DC_V2) || (version == Version::PC_NTE) || (version == Version::PC_V2) || (version == Version::GC_NTE);
|
||||
}
|
||||
inline bool is_v1_or_v2(Version version) {
|
||||
constexpr bool is_v1_or_v2(Version version) {
|
||||
return is_v1(version) || is_v2(version);
|
||||
}
|
||||
inline bool is_v3(Version version) {
|
||||
constexpr bool is_v3(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool is_v4(Version version) {
|
||||
constexpr bool is_v4(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool is_ep3(Version version) {
|
||||
constexpr bool is_ep3(Version version) {
|
||||
return (version == Version::GC_EP3_NTE) || (version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_dc(Version version) {
|
||||
constexpr bool is_dc(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_gc(Version version) {
|
||||
constexpr bool is_gc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_sh4(Version version) {
|
||||
constexpr bool is_sh4(Version version) {
|
||||
return (version == Version::DC_NTE) ||
|
||||
(version == Version::DC_V1_11_2000_PROTOTYPE) ||
|
||||
(version == Version::DC_V1) ||
|
||||
(version == Version::DC_V2);
|
||||
}
|
||||
inline bool is_x86(Version version) {
|
||||
constexpr bool is_x86(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
@@ -99,20 +142,20 @@ inline bool is_x86(Version version) {
|
||||
(version == Version::XB_V3) ||
|
||||
(version == Version::BB_V4);
|
||||
}
|
||||
inline bool is_ppc(Version version) {
|
||||
constexpr bool is_ppc(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
|
||||
inline bool is_big_endian(Version version) {
|
||||
constexpr bool is_big_endian(Version version) {
|
||||
return (version == Version::GC_NTE) ||
|
||||
(version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3);
|
||||
}
|
||||
inline bool uses_v2_encryption(Version version) {
|
||||
constexpr bool uses_v2_encryption(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::DC_NTE) ||
|
||||
@@ -122,17 +165,17 @@ inline bool uses_v2_encryption(Version version) {
|
||||
(version == Version::PC_V2) ||
|
||||
(version == Version::GC_NTE);
|
||||
}
|
||||
inline bool uses_v3_encryption(Version version) {
|
||||
constexpr bool uses_v3_encryption(Version version) {
|
||||
return (version == Version::GC_V3) ||
|
||||
(version == Version::GC_EP3_NTE) ||
|
||||
(version == Version::GC_EP3) ||
|
||||
(version == Version::XB_V3);
|
||||
}
|
||||
inline bool uses_v4_encryption(Version version) {
|
||||
constexpr bool uses_v4_encryption(Version version) {
|
||||
return (version == Version::BB_V4);
|
||||
}
|
||||
|
||||
inline bool uses_utf16(Version version) {
|
||||
constexpr bool uses_utf16(Version version) {
|
||||
return (version == Version::PC_PATCH) ||
|
||||
(version == Version::BB_PATCH) ||
|
||||
(version == Version::PC_NTE) ||
|
||||
|
||||
@@ -70,8 +70,8 @@ struct BBRoot {
|
||||
template <bool BE, size_t StringTableCount, size_t TokenCount>
|
||||
void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis) {
|
||||
phosg::StringReader r(data);
|
||||
uint32_t root_offset = r.pget<U32T<BE>>(r.size() - 0x10);
|
||||
const auto& root = r.pget<NonWindowsRootT<BE>>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooterT<BE>>(r.size() - sizeof(RELFileFooterT<BE>));
|
||||
const auto& root = r.pget<NonWindowsRootT<BE>>(footer.root_offset);
|
||||
|
||||
{
|
||||
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T<BE>) * StringTableCount);
|
||||
@@ -96,8 +96,8 @@ void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<s
|
||||
}
|
||||
|
||||
phosg::StringReader r(data);
|
||||
uint32_t root_offset = r.pget<le_uint32_t>(r.size() - 0x10);
|
||||
const auto& root = r.pget<RootT>(root_offset);
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
const auto& root = r.pget<RootT>(footer.root_offset);
|
||||
this->strings = *unitxt_collection;
|
||||
// this->table1 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table1, Table1Count);
|
||||
// this->table2 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table2, Table2Count);
|
||||
@@ -325,7 +325,5 @@ WordSelectMessage WordSelectTable::translate(
|
||||
}
|
||||
|
||||
WordSelectTable::Token::Token() {
|
||||
for (size_t z = 0; z < this->values_by_version.size(); z++) {
|
||||
this->values_by_version[z] = 0xFFFF;
|
||||
}
|
||||
this->values_by_version.fill(0xFFFF);
|
||||
}
|
||||
|
||||
@@ -1116,15 +1116,37 @@
|
||||
// in infinite HP mode.)
|
||||
"EnableV3V4ProtectedSubcommands": false,
|
||||
|
||||
// Whether to allow cross-play for various game versions. DCv1 and DCv2 are
|
||||
// always allowed to join each other's games (though DCv2 can deny permission
|
||||
// for DCv1 players to join when creating a game); if AllowDCPCGames is
|
||||
// enabled, then PC players are allowed in DC games and vice versa. Similarly,
|
||||
// if AllowGCXBGames is enabled, then GameCube and Xbox players are allowed to
|
||||
// join each other's games. Note that this behavior is experimental; you are
|
||||
// likely to encounter bugs in cross-play games, especially in the DC/PC case.
|
||||
"AllowDCPCGames": false,
|
||||
"AllowGCXBGames": true,
|
||||
// These flags specify which versions to allow to join games, depending on
|
||||
// the version that created the game. Each entry here is a bit field
|
||||
// specifying which versions can join. The bits are in the same order as
|
||||
// specified here in the comments, starting from the right (so DC_NTE is the
|
||||
// third bit from the right). By default, all non-prototype v1 and v2
|
||||
// versions can play with each other, and GC and Xbox can play with each
|
||||
// other also.
|
||||
// Note that there are some built-in restrictions. If a game is in the
|
||||
// Ultimate difficulty level, for example, it will not be accessible to v1
|
||||
// players regardless of what's specified below. There are similar
|
||||
// restrictions based on the game mode and episode number.
|
||||
// I don't plan to implement the ability for NTE or prototype versions to
|
||||
// play with each other or with non-NTE versions. If you allow any NTE or
|
||||
// prototype versions to play with other versions and you encounter a bug in
|
||||
// that scenario, don't submit a ticket about it.
|
||||
"CompatibilityGroups": [
|
||||
0x0000, // PC_PATCH
|
||||
0x0000, // BB_PATCH
|
||||
0x0004, // DC_NTE compatible only with itself
|
||||
0x0008, // DC_V1_11_2000_PROTOTYPE compatible only with itself
|
||||
0x00B0, // DC_V1 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x00B0, // DC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0040, // PC_NTE compatible only with itself
|
||||
0x00B0, // PC_V2 compatible with DC_V1, DC_V2, and PC_V2
|
||||
0x0100, // GC_NTE compatible only with itself
|
||||
0x1200, // GC_V3 compatible with GC_V3 and XB_V3
|
||||
0x0400, // GC_EP3_NTE compatible only with itself
|
||||
0x0800, // GC_EP3 compatible only with itself
|
||||
0x1200, // XB_V3 compatible with GC_V3 and XB_V3
|
||||
0x2000, // BB_V4 compatible only with itself
|
||||
],
|
||||
|
||||
// This option causes the server to override name colors for all players based
|
||||
// on which version of the game they're using. If this option is missing or
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -64,6 +64,9 @@ I 91446 2023-12-31 21:04:44 - [Commands] Sending to C-1 (version=DC_NTE command=
|
||||
0000 | 04 00 0C 00 00 00 01 00 F5 5D C2 1A | UUUU
|
||||
I 91446 2023-12-31 21:04:44 - [C-1] Client is a prototype version and the license was created during this session; converting permanent license to temporary license
|
||||
I 91446 2023-12-31 21:04:44 - [Commands] Sending to C-1 (version=DC_NTE command=07 flag=02)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 32 3A 35 36 3A 33 36 2E 30 30 30 00 01 00 00 | 22:56:36.000
|
||||
I 91446 2023-12-31 21:04:44 - [Commands] Sending to C-1 (version=DC_NTE command=07 flag=02)
|
||||
0000 | 07 02 58 00 11 00 00 11 FF FF FF FF 04 00 41 6C | X Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
@@ -588,6 +591,9 @@ I 91446 2023-12-31 21:05:43 - [Commands] Sending to C-3 (version=DC_NTE command=
|
||||
0000 | 04 00 0C 00 00 00 01 00 F5 79 DE 25 |
|
||||
I 91446 2023-12-31 21:05:43 - [C-3] Client is a prototype version and the license was created during this session; converting permanent license to temporary license
|
||||
I 91446 2023-12-31 21:05:43 - [Commands] Sending to C-3 (version=DC_NTE command=07 flag=02)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 32 3A 35 36 3A 33 36 2E 30 30 30 00 01 00 00 | 22:56:36.000
|
||||
I 91446 2023-12-31 21:05:43 - [Commands] Sending to C-3 (version=DC_NTE command=07 flag=02)
|
||||
0000 | 07 02 58 00 11 00 00 11 FF FF FF FF 04 00 41 6C | X Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
@@ -1176,6 +1182,10 @@ I 91446 2023-12-31 21:06:09 - [Commands] Received from C-2 (ABCDEFGHIJKL) (versi
|
||||
I 91446 2023-12-31 21:06:09 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 10 00 46 03 01 00 0A 00 00 00 00 40 00 00 | ` F @
|
||||
I 91446 2023-12-31 21:06:24 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=0C flag=03)
|
||||
0000 | 06 00 30 00 00 00 00 00 00 00 00 00 24 76 61 72 | $var
|
||||
0010 | 69 61 74 69 6F 6E 73 20 30 30 30 30 30 31 30 31 | iations 00000101
|
||||
0020 | 30 31 30 31 30 30 30 30 31 31 30 31 31 30 00 00 | 01010000110110
|
||||
I 91446 2023-12-31 21:06:24 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=0C flag=03)
|
||||
0000 | 0C 03 2C 00 00 00 00 00 00 00 00 00 82 A0 82 A0 | ,
|
||||
0010 | 82 A0 82 A0 82 A0 81 90 00 00 00 00 00 00 00 00 |
|
||||
0020 | 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
@@ -4383,9 +4393,9 @@ I 91446 2023-12-31 21:07:49 - [Lobby:00000015:FloorItems:01] Added floor item 00
|
||||
I 91446 2023-12-31 21:07:49 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 91446 2023-12-31 21:07:49 - [Game:15] Player 0 (leader) created floor item 00000170 (5 Meseta) at 1:(485.634, 586.012)
|
||||
I 91446 2023-12-31 21:07:49 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 2C 00 51 0A 60 40 01 02 70 00 1C D1 F2 43 | ` , Q `@ p C
|
||||
0010 | C2 80 12 44 10 00 00 00 04 00 00 00 00 00 00 00 | D
|
||||
0020 | 00 00 00 00 70 01 00 00 05 00 00 00 | p
|
||||
0000 | 60 00 30 00 51 0B 00 00 01 02 70 00 1C D1 F2 43 | ` 0 Q p C
|
||||
0010 | C2 80 12 44 00 00 00 00 04 00 00 00 00 00 00 00 | D
|
||||
0020 | 00 00 00 00 70 01 00 00 05 00 00 00 00 00 00 00 | p
|
||||
I 91446 2023-12-31 21:07:49 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 2C 00 51 0A 60 40 01 02 6F 00 65 1F E8 43 | ` , Q `@ o e C
|
||||
0010 | 5E 83 10 44 10 00 00 00 00 0A 00 00 00 00 02 FB | ^ D
|
||||
@@ -4394,9 +4404,9 @@ I 91446 2023-12-31 21:07:49 - [Lobby:00000015:FloorItems:01] Added floor item 00
|
||||
I 91446 2023-12-31 21:07:49 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 91446 2023-12-31 21:07:49 - [Game:15] Player 0 (leader) created floor item 0000016F (Cane 0/-5/0/0/0) at 1:(464.245, 578.053)
|
||||
I 91446 2023-12-31 21:07:49 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 2C 00 51 0A 60 40 01 02 6F 00 65 1F E8 43 | ` , Q `@ o e C
|
||||
0010 | 5E 83 10 44 10 00 00 00 00 0A 00 00 00 00 02 FB | ^ D
|
||||
0020 | 00 00 00 00 6F 01 00 00 00 00 00 00 | o
|
||||
0000 | 60 00 30 00 51 0B 00 00 01 02 6F 00 65 1F E8 43 | ` 0 Q o e C
|
||||
0010 | 5E 83 10 44 00 00 00 00 00 0A 00 00 00 00 02 FB | ^ D
|
||||
0020 | 00 00 00 00 6F 01 00 00 00 00 00 00 00 00 00 00 | o
|
||||
I 91446 2023-12-31 21:07:50 - [Commands] Received from C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 1C 00 36 06 01 00 00 00 56 CE 01 00 10 00 | ` 6 V
|
||||
0010 | AB 3F F6 43 E8 8E EA 40 2A 35 0D 44 | ? C @*5 D
|
||||
@@ -4750,9 +4760,9 @@ I 91446 2023-12-31 21:08:38 - [Lobby:00000015:FloorItems:01] Added floor item 00
|
||||
I 91446 2023-12-31 21:08:38 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 91446 2023-12-31 21:08:38 - [Game:15] Player 0 (leader) created floor item 00000173 (7 Meseta) at 1:(538.379, 394.393)
|
||||
I 91446 2023-12-31 21:08:38 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 2C 00 51 0A 60 40 01 02 73 00 3E 98 06 44 | ` , Q `@ s > D
|
||||
0010 | 5E 32 C5 43 10 00 00 00 04 00 00 00 00 00 00 00 | ^2 C
|
||||
0020 | 00 00 00 00 73 01 00 00 07 00 00 00 | s
|
||||
0000 | 60 00 30 00 51 0B 00 00 01 02 73 00 3E 98 06 44 | ` 0 Q s > D
|
||||
0010 | 5E 32 C5 43 00 00 00 00 04 00 00 00 00 00 00 00 | ^2 C
|
||||
0020 | 00 00 00 00 73 01 00 00 07 00 00 00 00 00 00 00 | s
|
||||
I 91446 2023-12-31 21:08:39 - [Commands] Received from C-2 (ABCDEFGHIJKL) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 1C 00 36 06 00 00 00 00 3D 5C 01 00 10 00 | ` 6 =\
|
||||
0010 | C4 11 03 44 DE 24 3E 41 9E D2 CC 43 | D $>A C
|
||||
@@ -4897,9 +4907,9 @@ I 91446 2023-12-31 21:08:46 - [Lobby:00000015:FloorItems:01] Added floor item 00
|
||||
I 91446 2023-12-31 21:08:46 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 91446 2023-12-31 21:08:46 - [Game:15] Player 0 (leader) created floor item 00000172 (3 Meseta) at 1:(556.451, 393.417)
|
||||
I 91446 2023-12-31 21:08:46 - [Commands] Sending to C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 2C 00 51 0A 60 40 01 02 72 00 D5 1C 0B 44 | ` , Q `@ r D
|
||||
0010 | 69 B5 C4 43 10 00 00 00 04 00 00 00 00 00 00 00 | i C
|
||||
0020 | 00 00 00 00 72 01 00 00 03 00 00 00 | r
|
||||
0000 | 60 00 30 00 51 0B 00 00 01 02 72 00 D5 1C 0B 44 | ` 0 Q r D
|
||||
0010 | 69 B5 C4 43 00 00 00 00 04 00 00 00 00 00 00 00 | i C
|
||||
0020 | 00 00 00 00 72 01 00 00 03 00 00 00 00 00 00 00 | r
|
||||
I 91446 2023-12-31 21:08:47 - [Commands] Received from C-4 (BBBBBBBBBBBB) (version=DC_NTE command=60 flag=00)
|
||||
0000 | 60 00 1C 00 36 06 01 00 00 00 C1 7B 01 00 10 00 | ` 6 {
|
||||
0010 | 91 17 0B 44 BC CD 24 41 98 1D CB 43 | D $A C
|
||||
|
||||
@@ -60,6 +60,9 @@ I 40469 2023-05-26 10:40:55 - [C-1] Game version changed to DC
|
||||
I 40469 2023-05-26 10:40:55 - [Commands] Sending to C-1 (version=DC command=04 flag=00)
|
||||
0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww
|
||||
I 40469 2023-05-26 10:40:55 - [Commands] Sending to C-1 (version=DC command=07 flag=05)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 30 37 3A 33 37 2E 30 30 30 00 01 00 00 | 23:07:37.000
|
||||
I 40469 2023-05-26 10:40:55 - [Commands] Sending to C-1 (version=DC command=07 flag=05)
|
||||
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
|
||||
@@ -59,6 +59,9 @@ I 40992 2023-05-26 10:52:51 - [C-1] Game version changed to DC
|
||||
I 40992 2023-05-26 10:52:51 - [Commands] Sending to C-1 (version=DC command=04 flag=00)
|
||||
0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww
|
||||
I 40992 2023-05-26 10:52:51 - [Commands] Sending to C-1 (version=DC command=07 flag=05)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 30 37 3A 33 37 2E 30 30 30 00 01 00 00 | 23:07:37.000
|
||||
I 40992 2023-05-26 10:52:51 - [Commands] Sending to C-1 (version=DC command=07 flag=05)
|
||||
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
@@ -1597,6 +1600,9 @@ I 40992 2023-05-26 10:55:37 - [C-3] Game version changed to DC
|
||||
I 40992 2023-05-26 10:55:37 - [Commands] Sending to C-3 (version=DC command=04 flag=00)
|
||||
0000 | 04 00 0C 00 00 00 01 00 77 77 77 77 | , wwww
|
||||
I 40992 2023-05-26 10:55:37 - [Commands] Sending to C-3 (version=DC command=07 flag=05)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 30 37 3A 33 37 2E 30 30 30 00 01 00 00 | 23:07:37.000
|
||||
I 40992 2023-05-26 10:55:37 - [Commands] Sending to C-3 (version=DC command=07 flag=05)
|
||||
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
|
||||
@@ -64,6 +64,9 @@ I 88613 2024-06-23 23:51:26 - [Commands] Sending to C-1 @ ipss:N-1:127.0.0.1:502
|
||||
0010 | 32 45 4F 33 00 01 02 40 20 00 00 01 00 00 00 00 | 2EO3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 88613 2024-06-23 23:51:26 - [Commands] Sending to C-1 @ ipss:N-1:127.0.0.1:50245 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 31 3A 32 38 2E 30 30 30 00 01 00 00 | 23:41:28.00
|
||||
I 88613 2024-06-23 23:51:26 - [Commands] Sending to C-1 @ ipss:N-1:127.0.0.1:50245 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
@@ -5083,6 +5086,9 @@ I 88613 2024-06-23 23:51:31 - [Commands] Sending to C-3 @ ipss:N-2: (version=GC_
|
||||
0010 | 30 50 4F 33 00 11 00 40 20 00 00 00 00 00 00 00 | 0PO3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 88613 2024-06-23 23:51:31 - [Commands] Sending to C-3 @ ipss:N-2: (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 31 3A 32 38 2E 30 30 30 00 01 00 00 | 23:41:28.000
|
||||
I 88613 2024-06-23 23:51:31 - [Commands] Sending to C-3 @ ipss:N-2: (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
|
||||
@@ -63,6 +63,9 @@ I 94381 2023-12-29 15:36:14 - [Commands] Sending to C-1 (version=GC_V3 command=0
|
||||
0000 | 04 00 2C 00 00 00 01 00 1E B1 05 17 32 AC 99 83 | , 2
|
||||
0010 | 30 50 4F 33 00 11 00 40 20 00 00 00 00 00 00 00 | 0PO3 B
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 94381 2023-12-29 15:36:14 - [Commands] Sending to C-1 (version=GC_V3 command=04 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 32 3A 33 38 3A 34 35 2E 30 30 30 00 01 00 00 | 22:38:45.000
|
||||
I 94381 2023-12-29 15:36:14 - [Commands] Sending to C-1 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
@@ -470,6 +473,9 @@ I 94381 2023-12-29 15:36:19 - [Commands] Sending to C-4 (version=GC_V3 command=0
|
||||
0010 | 00 00 4F 33 00 10 00 40 20 00 00 00 00 00 00 00 | 3 B
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 94381 2023-12-29 15:36:19 - [Commands] Sending to C-4 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 32 3A 33 39 3A 35 32 2E 30 30 30 00 01 00 00 | 22:39:52.000
|
||||
I 94381 2023-12-29 15:36:19 - [Commands] Sending to C-4 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
@@ -4182,7 +4188,7 @@ I 94381 2023-12-29 15:37:47 - [Game:15] Creating item CC000000 at 02:1100,685 fo
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Added floor item CC000000 at 1100, 685 with drop number 0 visible to 001
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:37:47 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C3 01 00 80 89 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C3 01 00 80 89 44 | ` 0 _ D
|
||||
0010 | 00 40 2B 44 00 00 00 00 04 00 00 00 00 00 00 00 | @+D
|
||||
0020 | 00 00 00 00 00 00 00 CC 09 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:37:47 - [Game:15] Creating item from box 01C3 (area 14)
|
||||
@@ -4195,7 +4201,7 @@ I 94381 2023-12-29 15:37:47 - [Game:15] Creating item CC000001 at 02:1100,685 fo
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Added floor item CC000001 at 1100, 685 with drop number 1 visible to 002
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:37:47 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C3 01 00 80 89 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C3 01 00 80 89 44 | ` 0 _ D
|
||||
0010 | 00 40 2B 44 00 00 00 00 04 00 00 00 00 00 00 00 | @+D
|
||||
0020 | 00 00 00 00 01 00 00 CC 0A 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:37:47 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
@@ -4228,7 +4234,7 @@ I 94381 2023-12-29 15:37:47 - [Game:15] Creating item CC000002 at 02:1100,705 fo
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Added floor item CC000002 at 1100, 705 with drop number 2 visible to 001
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:37:47 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C2 01 00 80 89 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C2 01 00 80 89 44 | ` 0 _ D
|
||||
0010 | 00 40 30 44 00 00 00 00 04 00 00 00 00 00 00 00 | @0D
|
||||
0020 | 00 00 00 00 02 00 00 CC 16 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:37:47 - [Game:15] Creating item from box 01C2 (area 14)
|
||||
@@ -4241,7 +4247,7 @@ I 94381 2023-12-29 15:37:47 - [Game:15] Creating item CC000003 at 02:1100,705 fo
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Added floor item CC000003 at 1100, 705 with drop number 3 visible to 002
|
||||
I 94381 2023-12-29 15:37:47 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:37:47 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C2 01 00 80 89 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C2 01 00 80 89 44 | ` 0 _ D
|
||||
0010 | 00 40 30 44 00 00 00 00 04 00 00 00 00 00 00 00 | @0D
|
||||
0020 | 00 00 00 00 03 00 00 CC 19 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:37:48 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
@@ -4274,7 +4280,7 @@ I 94381 2023-12-29 15:37:48 - [Game:15] Creating item CC000004 at 02:1100,725 fo
|
||||
I 94381 2023-12-29 15:37:48 - [Lobby:00000015:FloorItems:02] Added floor item CC000004 at 1100, 725 with drop number 4 visible to 001
|
||||
I 94381 2023-12-29 15:37:48 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:37:48 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C1 01 00 80 89 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C1 01 00 80 89 44 | ` 0 _ D
|
||||
0010 | 00 40 35 44 00 00 00 00 04 00 00 00 00 00 00 00 | @5D
|
||||
0020 | 00 00 00 00 04 00 00 CC 0E 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:37:48 - [Game:15] Creating item from box 01C1 (area 14)
|
||||
@@ -4483,7 +4489,7 @@ I 94381 2023-12-29 15:38:10 - [Game:15] Creating item CC000005 at 02:1070,745 fo
|
||||
I 94381 2023-12-29 15:38:10 - [Lobby:00000015:FloorItems:02] Added floor item CC000005 at 1070, 745 with drop number 5 visible to 001
|
||||
I 94381 2023-12-29 15:38:10 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:38:10 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 BF 01 00 C0 85 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 BF 01 00 C0 85 44 | ` 0 _ D
|
||||
0010 | 00 40 3A 44 00 00 00 00 04 00 00 00 00 00 00 00 | @:D
|
||||
0020 | 00 00 00 00 05 00 00 CC 15 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:38:10 - [Game:15] Creating item from box 01BF (area 14)
|
||||
@@ -4698,7 +4704,7 @@ I 94381 2023-12-29 15:39:09 - [Game:15] Creating item CC000006 at 02:1050,745 fo
|
||||
I 94381 2023-12-29 15:39:09 - [Lobby:00000015:FloorItems:02] Added floor item CC000006 at 1050, 745 with drop number 6 visible to 001
|
||||
I 94381 2023-12-29 15:39:09 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:39:09 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 BE 01 00 40 83 44 | ` 0 _ @ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 BE 01 00 40 83 44 | ` 0 _ @ D
|
||||
0010 | 00 40 3A 44 00 00 00 00 04 00 00 00 00 00 00 00 | @:D
|
||||
0020 | 00 00 00 00 06 00 00 CC 10 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:39:09 - [Game:15] Creating item from box 01BE (area 14)
|
||||
@@ -4711,7 +4717,7 @@ I 94381 2023-12-29 15:39:09 - [Game:15] Creating item CC000007 at 02:1050,745 fo
|
||||
I 94381 2023-12-29 15:39:09 - [Lobby:00000015:FloorItems:02] Added floor item CC000007 at 1050, 745 with drop number 7 visible to 002
|
||||
I 94381 2023-12-29 15:39:09 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:39:09 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 BE 01 00 40 83 44 | ` 0 _ @ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 BE 01 00 40 83 44 | ` 0 _ @ D
|
||||
0010 | 00 40 3A 44 00 00 00 00 03 02 00 00 00 00 00 00 | @:D
|
||||
0020 | 00 00 00 00 07 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:39:10 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
@@ -4744,7 +4750,7 @@ I 94381 2023-12-29 15:39:10 - [Game:15] Creating item CC000008 at 02:1030,745 fo
|
||||
I 94381 2023-12-29 15:39:10 - [Lobby:00000015:FloorItems:02] Added floor item CC000008 at 1030, 745 with drop number 8 visible to 001
|
||||
I 94381 2023-12-29 15:39:10 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:39:10 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C0 01 00 C0 80 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C0 01 00 C0 80 44 | ` 0 _ D
|
||||
0010 | 00 40 3A 44 00 00 00 00 04 00 00 00 00 00 00 00 | @:D
|
||||
0020 | 00 00 00 00 08 00 00 CC 0E 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:39:10 - [Game:15] Creating item from box 01C0 (area 14)
|
||||
@@ -4757,7 +4763,7 @@ I 94381 2023-12-29 15:39:10 - [Game:15] Creating item CC000009 at 02:1030,745 fo
|
||||
I 94381 2023-12-29 15:39:10 - [Lobby:00000015:FloorItems:02] Added floor item CC000009 at 1030, 745 with drop number 9 visible to 002
|
||||
I 94381 2023-12-29 15:39:10 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:39:10 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 C0 01 00 C0 80 44 | ` 0 _ D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 C0 01 00 C0 80 44 | ` 0 _ D
|
||||
0010 | 00 40 3A 44 00 00 00 00 04 00 00 00 00 00 00 00 | @:D
|
||||
0020 | 00 00 00 00 09 00 00 CC 17 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:39:11 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
@@ -5188,7 +5194,7 @@ I 94381 2023-12-29 15:39:46 - [Game:15] Creating item CC00000A at 02:620,455 for
|
||||
I 94381 2023-12-29 15:39:46 - [Lobby:00000015:FloorItems:02] Added floor item CC00000A at 620, 455 with drop number 10 visible to 001
|
||||
I 94381 2023-12-29 15:39:46 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:39:46 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 66 01 00 00 1B 44 | ` 0 _ f D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 66 01 00 00 1B 44 | ` 0 _ f D
|
||||
0010 | 00 80 E3 43 00 00 00 00 04 00 00 00 00 00 00 00 | C
|
||||
0020 | 00 00 00 00 0A 00 00 CC 0D 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:39:46 - [Game:15] Creating item from box 0166 (area 14)
|
||||
@@ -6798,7 +6804,7 @@ I 94381 2023-12-29 15:41:06 - [Game:15] Creating item CC000012 at 02:580,455 for
|
||||
I 94381 2023-12-29 15:41:06 - [Lobby:00000015:FloorItems:02] Added floor item CC000012 at 580, 455 with drop number 18 visible to 001
|
||||
I 94381 2023-12-29 15:41:06 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:41:06 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 64 01 00 00 11 44 | ` 0 _ d D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 64 01 00 00 11 44 | ` 0 _ d D
|
||||
0010 | 00 80 E3 43 00 00 00 00 03 00 00 00 00 01 00 00 | C
|
||||
0020 | 00 00 00 00 12 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:41:06 - [Game:15] Creating item from box 0164 (area 14)
|
||||
@@ -6811,7 +6817,7 @@ I 94381 2023-12-29 15:41:06 - [Game:15] Creating item CC000013 at 02:580,455 for
|
||||
I 94381 2023-12-29 15:41:06 - [Lobby:00000015:FloorItems:02] Added floor item CC000013 at 580, 455 with drop number 19 visible to 002
|
||||
I 94381 2023-12-29 15:41:06 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:41:06 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 64 01 00 00 11 44 | ` 0 _ d D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 64 01 00 00 11 44 | ` 0 _ d D
|
||||
0010 | 00 80 E3 43 00 00 00 00 03 00 00 00 00 01 00 00 | C
|
||||
0020 | 00 00 00 00 13 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:41:07 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
@@ -6904,7 +6910,7 @@ I 94381 2023-12-29 15:41:09 - [Game:15] Creating item CC000014 at 02:540,455 for
|
||||
I 94381 2023-12-29 15:41:09 - [Lobby:00000015:FloorItems:02] Added floor item CC000014 at 540, 455 with drop number 20 visible to 001
|
||||
I 94381 2023-12-29 15:41:09 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:41:09 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 65 01 00 00 07 44 | ` 0 _ e D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 65 01 00 00 07 44 | ` 0 _ e D
|
||||
0010 | 02 80 E3 43 00 00 00 00 04 00 00 00 00 00 00 00 | C
|
||||
0020 | 00 00 00 00 14 00 00 CC 17 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:41:09 - [Game:15] Creating item from box 0165 (area 14)
|
||||
@@ -13245,9 +13251,9 @@ I 94381 2023-12-29 15:43:04 - [Game:15] Creating item CC000015 at 02:660,455 for
|
||||
I 94381 2023-12-29 15:43:04 - [Lobby:00000015:FloorItems:02] Added floor item CC000015 at 660, 455 with drop number 21 visible to 001
|
||||
I 94381 2023-12-29 15:43:04 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:43:04 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 67 01 00 00 25 44 | ` 0 _ g %D
|
||||
0010 | FE 7F E3 43 00 00 00 00 03 00 00 00 00 01 00 00 | C
|
||||
0020 | 00 00 00 00 15 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 67 01 00 00 25 44 | ` 0 _ g %D
|
||||
0010 | FE 7F E3 43 00 00 00 00 04 00 00 00 00 00 00 00 | C
|
||||
0020 | 00 00 00 00 15 00 00 CC 09 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:43:04 - [Game:15] Creating item from box 0167 (area 14)
|
||||
I 94381 2023-12-29 15:43:04 - [ItemCreator:GC_V3/Ep2/Nml/N/5] Box drop checks for area_norm 01; random state: 991824DA 0000005F
|
||||
I 94381 2023-12-29 15:43:04 - [ItemCreator:GC_V3/Ep2/Nml/N/5] Box spec 00380000 did not produce item 030E1D
|
||||
@@ -13258,9 +13264,9 @@ I 94381 2023-12-29 15:43:04 - [Game:15] Creating item CC000016 at 02:660,455 for
|
||||
I 94381 2023-12-29 15:43:04 - [Lobby:00000015:FloorItems:02] Added floor item CC000016 at 660, 455 with drop number 22 visible to 002
|
||||
I 94381 2023-12-29 15:43:04 - [Lobby:00000015:FloorItems:02] Evicted 0 items
|
||||
I 94381 2023-12-29 15:43:04 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 00 67 01 00 00 25 44 | ` 0 _ g %D
|
||||
0010 | FE 7F E3 43 00 00 00 00 03 00 00 00 00 01 00 00 | C
|
||||
0020 | 00 00 00 00 16 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
0000 | 60 00 30 00 5F 0B 00 00 02 02 67 01 00 00 25 44 | ` 0 _ g %D
|
||||
0010 | FE 7F E3 43 00 00 00 00 04 00 00 00 00 00 00 00 | C
|
||||
0020 | 00 00 00 00 16 00 00 CC 0A 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:43:04 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 0B 03 67 41 01 00 00 00 67 01 00 00 | ` gA g
|
||||
I 94381 2023-12-29 15:43:04 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
@@ -13313,8 +13319,8 @@ I 94381 2023-12-29 15:43:06 - [Game:15] Player 0 picked up CC000015 (Monomate x1
|
||||
I 94381 2023-12-29 15:43:06 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 59 03 00 00 00 00 02 00 15 00 00 CC | ` Y
|
||||
I 94381 2023-12-29 15:43:06 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 20 00 2B 07 00 00 03 00 00 00 00 01 00 00 | ` +
|
||||
0010 | 00 00 00 00 15 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
0000 | 60 00 20 00 2B 07 00 00 04 00 00 00 00 00 00 00 | ` +
|
||||
0010 | 00 00 00 00 15 00 00 CC 09 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:43:07 - [Commands] Received from C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 14 00 40 04 00 00 8A 57 22 44 A4 B8 E8 43 | ` @ W"D C
|
||||
0010 | 00 00 00 00 |
|
||||
@@ -13505,8 +13511,8 @@ I 94381 2023-12-29 15:43:14 - [Game:15] Player 1 picked up CC000016 (Monomate x1
|
||||
[PlayerInventory] 5: [+00000050] 03080000 00010000 00000000 (00210007) 00000000 (Trap Vision x1)
|
||||
[PlayerInventory] 6: [+00000050] 03000000 00020000 00000000 (00210008) 00000000 (Monomate x2)
|
||||
I 94381 2023-12-29 15:43:14 - [Commands] Sending to C-2 (Jess) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 20 00 2B 07 01 00 03 00 00 00 00 01 00 00 | ` +
|
||||
0010 | 00 00 00 00 16 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
0000 | 60 00 20 00 2B 07 01 00 04 00 00 00 00 00 00 00 | ` +
|
||||
0010 | 00 00 00 00 16 00 00 CC 0A 00 00 00 00 00 00 00 |
|
||||
I 94381 2023-12-29 15:43:14 - [Commands] Sending to C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
0000 | 60 00 10 00 59 03 01 00 01 00 02 00 16 00 00 CC | ` Y
|
||||
I 94381 2023-12-29 15:43:14 - [Commands] Received from C-5 (Jonah) (version=GC_V3 command=60 flag=00)
|
||||
|
||||
@@ -2796,6 +2796,9 @@ I 94811 2024-09-01 16:33:08 - [Commands] Received from C-1 (Tali) @ ipss:N-1:127
|
||||
I 94811 2024-09-01 16:33:08 - [Commands] Received from C-1 (Tali) @ ipss:N-1:127.0.0.1:51929 (version=GC_EP3 command=84 flag=00)
|
||||
0000 | 84 00 0C 00 00 00 00 00 00 00 00 00 |
|
||||
I 94811 2024-09-01 16:33:08 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:51929 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 30 39 3A 33 37 2E 30 30 30 00 01 00 00 | 23:09:37.000
|
||||
I 94811 2024-09-01 16:33:08 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:51929 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 94811 2024-09-01 16:33:08 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:51929 (version=GC_EP3 command=95 flag=00)
|
||||
|
||||
@@ -2796,6 +2796,9 @@ I 64538 2024-09-01 15:24:02 - [Commands] Received from C-7 (Tali) @ ipss:N-4: (v
|
||||
I 64538 2024-09-01 15:24:02 - [Commands] Received from C-7 (Tali) @ ipss:N-4: (version=GC_EP3 command=84 flag=00)
|
||||
0000 | 84 00 0C 00 00 00 00 00 00 00 00 00 |
|
||||
I 64538 2024-09-01 15:24:02 - [Commands] Sending to C-7 (Tali) @ ipss:N-4: (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 64538 2024-09-01 15:24:02 - [Commands] Sending to C-7 (Tali) @ ipss:N-4: (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 64538 2024-09-01 15:24:02 - [Commands] Sending to C-7 (Tali) @ ipss:N-4: (version=GC_EP3 command=95 flag=00)
|
||||
@@ -7377,6 +7380,9 @@ I 64538 2024-09-01 15:24:44 - [Commands] Received from C-9 (Dono) @ ipss:N-6:127
|
||||
I 64538 2024-09-01 15:24:44 - [Commands] Received from C-9 (Dono) @ ipss:N-6:127.0.0.1:59366 (version=GC_EP3 command=84 flag=00)
|
||||
0000 | 84 00 0C 00 00 00 00 00 00 00 00 00 |
|
||||
I 64538 2024-09-01 15:24:44 - [Commands] Sending to C-9 (Dono) @ ipss:N-6:127.0.0.1:59366 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 64538 2024-09-01 15:24:44 - [Commands] Sending to C-9 (Dono) @ ipss:N-6:127.0.0.1:59366 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 64538 2024-09-01 15:24:44 - [Commands] Sending to C-9 (Dono) @ ipss:N-6:127.0.0.1:59366 (version=GC_EP3 command=95 flag=00)
|
||||
@@ -9390,6 +9396,9 @@ I 64538 2024-09-01 15:24:47 - [Commands] Sending to C-B @ ipss:N-5:127.0.0.1:593
|
||||
0010 | 00 4A 53 33 00 B1 00 40 20 00 00 00 00 00 00 00 | JS3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 64538 2024-09-01 15:24:47 - [Commands] Sending to C-B @ ipss:N-5:127.0.0.1:59362 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 64538 2024-09-01 15:24:47 - [Commands] Sending to C-B @ ipss:N-5:127.0.0.1:59362 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 64538 2024-09-01 15:24:47 - [Commands] Sending to C-B @ ipss:N-5:127.0.0.1:59362 (version=GC_EP3 command=95 flag=00)
|
||||
|
||||
@@ -2800,6 +2800,9 @@ I 54825 2024-11-05 21:17:58 - [Commands] Received from C-1 (Tali) @ ipss:N-1:127
|
||||
I 54825 2024-11-05 21:17:58 - [Commands] Received from C-1 (Tali) @ ipss:N-1:127.0.0.1:62373 (version=GC_EP3 command=84 flag=00)
|
||||
0000 | 84 00 0C 00 00 00 00 00 00 00 00 00 |
|
||||
I 54825 2024-11-05 21:17:58 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:62373 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 54825 2024-11-05 21:17:58 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:62373 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 54825 2024-11-05 21:17:58 - [Commands] Sending to C-1 (Tali) @ ipss:N-1:127.0.0.1:62373 (version=GC_EP3 command=95 flag=00)
|
||||
@@ -7923,6 +7926,9 @@ I 54825 2024-11-05 21:18:17 - [Commands] Received from C-3 (11) @ ipss:N-2: (ver
|
||||
I 54825 2024-11-05 21:18:17 - [Commands] Received from C-3 (11) @ ipss:N-2: (version=GC_EP3 command=84 flag=00)
|
||||
0000 | 84 00 0C 00 00 00 00 00 00 00 00 00 |
|
||||
I 54825 2024-11-05 21:18:17 - [Commands] Sending to C-3 (11) @ ipss:N-2: (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 54825 2024-11-05 21:18:17 - [Commands] Sending to C-3 (11) @ ipss:N-2: (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 54825 2024-11-05 21:18:17 - [Commands] Sending to C-3 (11) @ ipss:N-2: (version=GC_EP3 command=95 flag=00)
|
||||
|
||||
@@ -65,6 +65,9 @@ I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=
|
||||
0010 | 00 4A 53 33 00 B1 00 40 20 00 00 00 00 00 00 00 | 3 B`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 25793 2023-11-24 23:03:38 - [Commands] Sending to C-4 (version=GC_EP3 command=95 flag=00)
|
||||
@@ -2696,6 +2699,9 @@ I 25793 2023-11-24 23:04:08 - [Commands] Sending to C-6 (version=GC_EP3 command=
|
||||
0010 | 54 4A 53 33 00 B3 00 4C 20 00 00 00 00 00 00 00 | TJS3 L`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 25793 2023-11-24 23:04:08 - [Commands] Sending to C-6 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 25793 2023-11-24 23:04:08 - [Commands] Sending to C-6 (version=GC_EP3 command=B7 flag=00)
|
||||
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
|
||||
I 25793 2023-11-24 23:04:08 - [Commands] Sending to C-6 (version=GC_EP3 command=95 flag=00)
|
||||
|
||||
@@ -61,6 +61,9 @@ I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=04 f
|
||||
0010 | 30 50 4F 33 00 11 00 40 20 00 00 00 00 00 00 00 | 3 `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 49108 2023-05-26 16:18:01 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
@@ -10014,6 +10017,9 @@ I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=04 f
|
||||
0010 | 30 50 4F 33 00 13 00 44 20 00 00 00 00 00 00 00 | 3 `
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=07 flag=06)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 49108 2023-05-26 16:28:29 - [Commands] Sending to C-3 (version=GC command=07 flag=06)
|
||||
0000 | 07 05 AC 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
|
||||
@@ -62,6 +62,9 @@ I 23921 2024-03-03 21:20:43 - [Commands] Sending to C-1 (version=GC_V3 command=0
|
||||
0010 | 30 50 4F 33 00 11 00 40 20 00 00 00 00 00 00 00 | 0PO3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 23921 2024-03-03 21:20:43 - [Commands] Sending to C-1 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 23921 2024-03-03 21:20:43 - [Commands] Sending to C-1 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
|
||||
@@ -62,6 +62,9 @@ I 56327 2024-03-03 23:38:31 - [Commands] Sending to C-1 (version=GC_V3 command=0
|
||||
0010 | 30 50 4F 33 00 11 00 40 20 00 00 00 00 00 00 00 | 0PO3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 56327 2024-03-03 23:38:31 - [Commands] Sending to C-1 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 56327 2024-03-03 23:38:31 - [Commands] Sending to C-1 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
@@ -23190,6 +23193,9 @@ I 56327 2024-03-03 23:56:37 - [Commands] Sending to C-4 (version=GC_V3 command=0
|
||||
0010 | 00 00 4F 33 00 10 00 40 20 00 00 00 00 00 00 00 | 3 @
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 56327 2024-03-03 23:56:37 - [Commands] Sending to C-4 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 33 30 3A 20 | 2024:12:30:
|
||||
0010 | 30 30 3A 31 38 3A 30 38 2E 30 30 30 00 01 00 00 | 00:18:08.000
|
||||
I 56327 2024-03-03 23:56:37 - [Commands] Sending to C-4 (version=GC_V3 command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
|
||||
@@ -59,6 +59,9 @@ I 49484 2023-05-26 16:35:10 - [Commands] Received from C-2 (version=PC command=9
|
||||
I 49484 2023-05-26 16:35:10 - [Commands] Sending to C-2 (version=PC command=04 flag=00)
|
||||
0000 | 0C 00 04 00 00 00 01 00 11 11 11 11 |
|
||||
I 49484 2023-05-26 16:35:10 - [Commands] Sending to C-2 (version=PC command=07 flag=05)
|
||||
0000 | 20 00 B1 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 33 3A 30 39 2E 30 30 30 00 01 00 00 | 23:43:09.000
|
||||
I 49484 2023-05-26 16:35:10 - [Commands] Sending to C-2 (version=PC command=07 flag=05)
|
||||
0000 | 0C 01 07 05 11 00 00 11 FF FF FF FF 04 00 41 00 | A
|
||||
0010 | 6C 00 65 00 78 00 61 00 6E 00 64 00 72 00 69 00 | l e x a n d r i
|
||||
0020 | 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | a
|
||||
@@ -1143,6 +1146,9 @@ I 49484 2023-05-26 16:37:03 - [Commands] Received from C-4 (version=PC command=9
|
||||
I 49484 2023-05-26 16:37:03 - [Commands] Sending to C-4 (version=PC command=04 flag=00)
|
||||
0000 | 0C 00 04 00 00 00 01 00 11 11 11 11 |
|
||||
I 49484 2023-05-26 16:37:03 - [Commands] Sending to C-4 (version=PC command=07 flag=05)
|
||||
0000 | 20 00 B1 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 33 3A 30 39 2E 30 30 30 00 01 00 00 | 23:43:09.000
|
||||
I 49484 2023-05-26 16:37:03 - [Commands] Sending to C-4 (version=PC command=07 flag=05)
|
||||
0000 | 0C 01 07 05 11 00 00 11 FF FF FF FF 04 00 41 00 | A
|
||||
0010 | 6C 00 65 00 78 00 61 00 6E 00 64 00 72 00 69 00 | l e x a n d r i
|
||||
0020 | 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | a
|
||||
|
||||
@@ -59,6 +59,9 @@ I 97037 2023-12-29 15:55:52 - [Commands] Received from C-2 (version=PC_V2 comman
|
||||
I 97037 2023-12-29 15:55:52 - [Commands] Sending to C-2 (version=PC_V2 command=04 flag=00)
|
||||
0000 | 0C 00 04 00 00 00 01 00 1E B1 05 17 |
|
||||
I 97037 2023-12-29 15:55:52 - [Commands] Sending to C-2 (version=PC_V2 command=07 flag=05)
|
||||
0000 | 20 00 B1 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 33 3A 30 39 2E 30 30 30 00 01 00 00 | 23:43:09.000
|
||||
I 97037 2023-12-29 15:55:52 - [Commands] Sending to C-2 (version=PC_V2 command=07 flag=05)
|
||||
0000 | 0C 01 07 05 11 00 00 11 FF FF FF FF 04 00 41 00 | A
|
||||
0010 | 6C 00 65 00 78 00 61 00 6E 00 64 00 72 00 69 00 | l e x a n d r i
|
||||
0020 | 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | a
|
||||
@@ -546,6 +549,9 @@ I 97037 2023-12-29 15:56:51 - [Commands] Received from C-4 (version=DC_V1 comman
|
||||
0100 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |
|
||||
0110 | 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:56:51 - [Commands] Sending to C-4 (version=DC_V1 command=07 flag=05)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 34 34 3A 32 33 2E 30 30 30 00 01 00 00 | 23:44:23.000
|
||||
I 97037 2023-12-29 15:56:51 - [Commands] Sending to C-4 (version=DC_V1 command=07 flag=05)
|
||||
0000 | 07 04 90 00 11 00 00 11 FF FF FF FF 04 00 41 6C | Al
|
||||
0010 | 65 78 61 6E 64 72 69 61 00 00 00 00 00 00 00 00 | exandria
|
||||
0020 | 11 00 00 11 11 22 22 11 04 0F 47 6F 20 74 6F 20 | "" Go to
|
||||
@@ -839,9 +845,9 @@ I 97037 2023-12-29 15:57:03 - [Commands] Sending to C-5 (88888888) (version=DC_V
|
||||
I 97037 2023-12-29 15:57:05 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=09 flag=00)
|
||||
0000 | 09 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 97037 2023-12-29 15:57:05 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=11 flag=00)
|
||||
0000 | 11 00 24 00 00 00 00 00 00 00 00 00 54 61 6C 69 | $ Tali
|
||||
0010 | 0A 20 20 52 41 63 61 73 65 61 6C 20 4C 76 31 38 | RAcaseal Lv18
|
||||
0020 | 35 20 45 00 | 5 E
|
||||
0000 | 11 00 30 00 00 00 00 00 00 00 00 00 54 61 6C 69 | 0 Tali
|
||||
0010 | 20 09 43 32 50 43 09 43 37 0A 20 20 52 41 63 61 | C2PC C7 RAca
|
||||
0020 | 73 65 61 6C 20 4C 76 31 38 35 20 45 00 00 00 00 | seal Lv185 E
|
||||
I 97037 2023-12-29 15:57:06 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=10 flag=00)
|
||||
0000 | 10 00 0C 00 44 00 00 44 15 00 00 00 | D D
|
||||
I 97037 2023-12-29 15:57:06 - [C-5] Assigned inventory item IDs
|
||||
@@ -3522,7 +3528,7 @@ I 97037 2023-12-29 15:57:51 - [Game:15] Creating item CC000000 at 01:479.973,568
|
||||
I 97037 2023-12-29 15:57:51 - [Lobby:00000015:FloorItems:01] Added floor item CC000000 at 479.973, 568.098 with drop number 0 visible to 001
|
||||
I 97037 2023-12-29 15:57:51 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:57:51 - [Commands] Sending to C-3 (Tali) (version=PC_V2 command=60 flag=00)
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 00 7B 00 9B FC EF 43 | 0 ` _ { C
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 02 7B 00 9B FC EF 43 | 0 ` _ { C
|
||||
0010 | 42 06 0E 44 00 00 00 00 04 00 00 00 00 00 00 00 | B D
|
||||
0020 | 00 00 00 00 00 00 00 CC 16 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:57:51 - [Game:15] Creating item from box 007B (area 01)
|
||||
@@ -3620,7 +3626,7 @@ I 97037 2023-12-29 15:57:54 - [Game:15] Creating item CC000001 at 01:485.634,586
|
||||
I 97037 2023-12-29 15:57:54 - [Lobby:00000015:FloorItems:01] Added floor item CC000001 at 485.634, 586.012 with drop number 1 visible to 001
|
||||
I 97037 2023-12-29 15:57:54 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:57:54 - [Commands] Sending to C-3 (Tali) (version=PC_V2 command=60 flag=00)
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 00 7A 00 1C D1 F2 43 | 0 ` _ z C
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 02 7A 00 1C D1 F2 43 | 0 ` _ z C
|
||||
0010 | C2 80 12 44 00 00 00 00 01 01 00 00 00 00 01 00 | D
|
||||
0020 | 00 00 00 00 01 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:57:54 - [Game:15] Creating item from box 007A (area 01)
|
||||
@@ -3632,7 +3638,7 @@ I 97037 2023-12-29 15:57:54 - [Game:15] Creating item CC000002 at 01:485.634,586
|
||||
I 97037 2023-12-29 15:57:54 - [Lobby:00000015:FloorItems:01] Added floor item CC000002 at 485.634, 586.012 with drop number 2 visible to 002
|
||||
I 97037 2023-12-29 15:57:54 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:57:54 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 00 7A 00 1C D1 F2 43 | ` 0 _ z C
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 02 7A 00 1C D1 F2 43 | ` 0 _ z C
|
||||
0010 | C2 80 12 44 00 00 00 00 03 00 00 00 00 01 00 00 | D
|
||||
0020 | 00 00 00 00 02 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:57:54 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
@@ -4250,7 +4256,7 @@ I 97037 2023-12-29 15:58:41 - [Game:15] Creating item CC000003 at 01:538.379,394
|
||||
I 97037 2023-12-29 15:58:41 - [Lobby:00000015:FloorItems:01] Added floor item CC000003 at 538.379, 394.393 with drop number 4 visible to 001
|
||||
I 97037 2023-12-29 15:58:41 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:58:41 - [Commands] Sending to C-3 (Tali) (version=PC_V2 command=60 flag=00)
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 00 7D 00 3E 98 06 44 | 0 ` _ } > D
|
||||
0000 | 30 00 60 00 5F 0B 00 00 01 02 7D 00 3E 98 06 44 | 0 ` _ } > D
|
||||
0010 | 5E 32 C5 43 00 00 00 00 03 00 00 00 00 01 00 00 | ^2 C
|
||||
0020 | 00 00 00 00 03 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:58:41 - [Game:15] Creating item from box 007D (area 01)
|
||||
@@ -4262,7 +4268,7 @@ I 97037 2023-12-29 15:58:41 - [Game:15] Creating item CC000004 at 01:538.379,394
|
||||
I 97037 2023-12-29 15:58:41 - [Lobby:00000015:FloorItems:01] Added floor item CC000004 at 538.379, 394.393 with drop number 5 visible to 002
|
||||
I 97037 2023-12-29 15:58:41 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:58:41 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 00 7D 00 3E 98 06 44 | ` 0 _ } > D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 02 7D 00 3E 98 06 44 | ` 0 _ } > D
|
||||
0010 | 5E 32 C5 43 00 00 00 00 01 02 00 00 00 00 01 00 | ^2 C
|
||||
0020 | 04 00 00 00 04 00 00 CC 00 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:58:41 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
@@ -4330,7 +4336,7 @@ I 97037 2023-12-29 15:58:43 - [Game:15] Creating item CC000005 at 01:556.451,393
|
||||
I 97037 2023-12-29 15:58:43 - [Lobby:00000015:FloorItems:01] Added floor item CC000005 at 556.451, 393.417 with drop number 6 visible to 002
|
||||
I 97037 2023-12-29 15:58:43 - [Lobby:00000015:FloorItems:01] Evicted 0 items
|
||||
I 97037 2023-12-29 15:58:43 - [Commands] Sending to C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 00 7C 00 D5 1C 0B 44 | ` 0 _ | D
|
||||
0000 | 60 00 30 00 5F 0B 00 00 01 02 7C 00 D5 1C 0B 44 | ` 0 _ | D
|
||||
0010 | 69 B5 C4 43 00 00 00 00 04 00 00 00 00 00 00 00 | i C
|
||||
0020 | 00 00 00 00 05 00 00 CC 0F 00 00 00 00 00 00 00 |
|
||||
I 97037 2023-12-29 15:58:43 - [Commands] Received from C-5 (88888888) (version=DC_V1 command=60 flag=00)
|
||||
|
||||
@@ -55,6 +55,9 @@ I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=04 f
|
||||
0010 | 00 00 4F 34 00 91 00 40 20 00 00 00 00 00 00 00 | 4 B`
|
||||
0020 | 00 00 00 00 00 00 FF FF 80 FF FF FF |
|
||||
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=D5 flag=00)
|
||||
0000 | B1 00 20 00 32 30 32 34 3A 31 32 3A 32 39 3A 20 | 2024:12:29:
|
||||
0010 | 32 33 3A 31 30 3A 32 36 2E 30 30 30 00 01 00 00 | 23:10:26.000
|
||||
I 16496 2023-11-08 01:54:08 - [Commands] Sending to C-1 (version=XB command=D5 flag=00)
|
||||
0000 | D5 00 2C 00 59 6F 75 20 61 72 65 20 63 6F 6E 6E | , You are conn
|
||||
0010 | 65 63 74 65 64 20 74 6F 20 09 43 36 41 6C 65 78 | ected to C6Alex
|
||||
0020 | 61 6E 64 72 69 61 09 43 37 2E 00 00 | andria C7.
|
||||
|
||||
+1
-3
@@ -169,10 +169,8 @@
|
||||
"AllowUnregisteredUsers": true,
|
||||
"UseTemporaryAccountsForPrototypes": true,
|
||||
"AllowPCNTE": true,
|
||||
"AllowDCPCGames": true,
|
||||
"AllowGCXBGames": true,
|
||||
"EnableChatCommands": true,
|
||||
|
||||
"CompatibilityGroups": [0x0000, 0x0000, 0x0004, 0x0008, 0x00B0, 0x00B0, 0x0040, 0x00B0, 0x0100, 0x1200, 0x0400, 0x0800, 0x1200, 0x2000],
|
||||
"InformationMenuContents": [
|
||||
["Text colors", "$C7Display color values", "These values can be used to color text in\nsome situations, with escape codes like %sC6.\n\n$C0Color 0$C7 - Black\n$C1Color 1$C7 - Blue\n$C2Color 2$C7 - Green\n$C3Color 3$C7 - Cyan\n$C4Color 4$C7 - Red\n$C5Color 5$C7 - Purple\n$C6Color 6$C7 - Yellow\n$C7Color 7$C7 - White\n$C8Color 8$C7 - Pink\n$C9Color 9$C7 - Violet\n$CGColor G$C7 - Orange Pulse"],
|
||||
["Lobby commands", "$C7Display commands\nfor use in the\nlobby", "Lobby commands: you must be a moderator to use\nthese commands.\n\n%sallevent <event>: Change the server's event\n%sevent <event>: Change this lobby's event\n%stype <type>: Change this lobby's type\n%sann <message>: Announce a message to all players\n%sax <message>: Send a message to the server"],
|
||||
|
||||
Reference in New Issue
Block a user