Skip to content

Commit

Permalink
rewrite map data model
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzziqersoftware committed Jan 2, 2025
1 parent 69f7bb3 commit 72ac20e
Show file tree
Hide file tree
Showing 95 changed files with 7,468 additions and 4,997 deletions.
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|-----------------|----------|----------|----------|
Expand Down Expand Up @@ -339,27 +339,32 @@ 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.

In the server drop modes, the item tables used to generate common items are in the `system/item-tables/ItemPT-*` files. (The V2 files are used for V1 as well.) The rare item tables are in the `rare-table-*.json` files. Unlike the original formats, it's possible to make each enemy drop multiple different rare items at different rates, though the default tables never do this.

## 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

Expand All @@ -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 |
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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`)
Expand Down
2 changes: 1 addition & 1 deletion src/BattleParamsIndex.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
47 changes: 27 additions & 20 deletions src/ChatCommands.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 6 additions & 2 deletions src/Client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Client.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 72ac20e

Please sign in to comment.