diff --git a/data/language/english_uk.txt b/data/language/english_uk.txt index 1111e14da7..03e41f9d47 100644 --- a/data/language/english_uk.txt +++ b/data/language/english_uk.txt @@ -3981,6 +3981,69 @@ STR_5639 :{SMALLFONT}{BLACK}Show list of players STR_5640 :{SMALLFONT}{BLACK}Manage groups STR_5641 :Default Group: STR_5642 :Group: +STR_5643 :Add Group +STR_5644 :Remove Group +STR_5645 :Chat +STR_5646 :Set Ride Appearance +STR_5647 :Pause/Unpause +STR_5648 :Place Track +STR_5649 :Remove Track +STR_5650 :Create Ride +STR_5651 :Remove Ride +STR_5652 :Set Ride Status +STR_5653 :Set Ride Vehicles +STR_5654 :Set Ride Name +STR_5655 :Set Ride Setting +STR_5656 :Place Ride Entrance/Exit +STR_5657 :Remove Ride Entrance/Exit +STR_5658 :Remove Scenery +STR_5659 :Place Scenery +STR_5660 :Place Path +STR_5661 :Place Path From Track +STR_5662 :Remove Path +STR_5663 :Change Surface Style +STR_5664 :Set Ride Price +STR_5665 :Set Peep Name +STR_5666 :Raise Land +STR_5667 :Lower Land +STR_5668 :Smooth Land +STR_5669 :Raise Water +STR_5670 :Lower Water +STR_5671 :Set Brakes Speed +STR_5672 :Hire New Staff +STR_5673 :Set Staff Patrol +STR_5674 :Fire Staff +STR_5675 :Set Staff Order +STR_5676 :Set Park Name +STR_5677 :Open/Close Park +STR_5678 :Buy Land Rights +STR_5679 :Place Park Entrance +STR_5680 :Remove Park Entrance +STR_5681 :Set Maze Track +STR_5682 :Set Park Entrance Fee +STR_5683 :Set Staff Colour +STR_5684 :Place Fence +STR_5685 :Remove Fence +STR_5686 :Place Large Scenery +STR_5687 :Set Current Loan +STR_5688 :Set Research Funding +STR_5689 :Place Track Design +STR_5690 :Start Marketing Campaign +STR_5691 :Place Maze Design +STR_5692 :Place Banner +STR_5693 :Remove Banner +STR_5694 :Set Scenery Colour +STR_5695 :Set Fence Colour +STR_5696 :Set Large Scenery Colour +STR_5697 :Set Banner Colour +STR_5698 :Set Land Ownership +STR_5699 :Clear Scenery +STR_5700 :Set Banner Name +STR_5701 :Set Sign Name +STR_5702 :Set Banner Style +STR_5703 :Set Sign Style +STR_5704 :Set Player Group +STR_5705 :Modify Groups ############# # Scenarios # diff --git a/src/game.c b/src/game.c index 2e60f07092..7d4241d3a8 100644 --- a/src/game.c +++ b/src/game.c @@ -418,8 +418,6 @@ static int game_check_affordability(int cost) return MONEY32_UNDEFINED; } -static GAME_COMMAND_POINTER* new_game_command_table[63]; - /** * * rct2: 0x006677F2 @@ -1177,7 +1175,7 @@ void game_load_or_quit_no_save_prompt() } } -static GAME_COMMAND_POINTER* new_game_command_table[63] = { +GAME_COMMAND_POINTER* new_game_command_table[64] = { game_command_set_ride_appearance, game_command_set_land_height, game_pause_toggle, @@ -1240,5 +1238,6 @@ static GAME_COMMAND_POINTER* new_game_command_table[63] = { game_command_set_sign_name, game_command_set_banner_style, game_command_set_sign_style, - game_command_set_player_group + game_command_set_player_group, + game_command_modify_groups }; diff --git a/src/game.h b/src/game.h index fa646dafe5..abd795fe0c 100644 --- a/src/game.h +++ b/src/game.h @@ -88,7 +88,8 @@ enum GAME_COMMAND { GAME_COMMAND_SET_SIGN_NAME, GAME_COMMAND_SET_BANNER_STYLE, GAME_COMMAND_SET_SIGN_STYLE, - GAME_COMMAND_SET_PLAYER_GROUP + GAME_COMMAND_SET_PLAYER_GROUP, + GAME_COMMAND_MODIFY_GROUPS }; enum { @@ -113,6 +114,8 @@ extern GAME_COMMAND_CALLBACK_POINTER* game_command_callback; int game_command_callback_get_index(GAME_COMMAND_CALLBACK_POINTER* callback); GAME_COMMAND_CALLBACK_POINTER* game_command_callback_get_callback(int index); +extern GAME_COMMAND_POINTER* new_game_command_table[64]; + extern int gGameSpeed; extern float gDayNightCycle; extern bool gInUpdateCode; diff --git a/src/localisation/string_ids.h b/src/localisation/string_ids.h index 24c9871d78..620c414ce3 100644 --- a/src/localisation/string_ids.h +++ b/src/localisation/string_ids.h @@ -2268,6 +2268,70 @@ enum { STR_GROUPS_TIP = 5640, STR_DEFAULT_GROUP = 5641, STR_GROUP = 5642, + STR_ADD_GROUP = 5643, + STR_REMOVE_GROUP = 5644, + + STR_ACTION_CHAT = 5645, + STR_ACTION_SET_RIDE_APPEARANCE = 5646, + STR_ACTION_TOGGLE_PAUSE = 5647, + STR_ACTION_PLACE_TRACK = 5648, + STR_ACTION_REMOVE_TRACK = 5649, + STR_ACTION_CREATE_RIDE = 5650, + STR_ACTION_REMOVE_RIDE = 5651, + STR_ACTION_SET_RIDE_STATUS = 5652, + STR_ACTION_SET_RIDE_VEHICLES = 5653, + STR_ACTION_SET_RIDE_NAME = 5654, + STR_ACTION_SET_RIDE_SETTING = 5655, + STR_ACTION_PLACE_RIDE_ENTRANCE_EXIT = 5656, + STR_ACTION_REMOVE_RIDE_ENTRANCE_EXIT = 5657, + STR_ACTION_REMOVE_SCENERY = 5658, + STR_ACTION_PLACE_SCENERY = 5659, + STR_ACTION_PLACE_PATH = 5660, + STR_ACTION_PLACE_PATH_FROM_TRACK = 5661, + STR_ACTION_REMOVE_PATH = 5662, + STR_ACTION_CHANGE_SURFACE_STYLE = 5663, + STR_ACTION_SET_RIDE_PRICE = 5664, + STR_ACTION_SET_PEEP_NAME = 5665, + STR_ACTION_RAISE_LAND = 5666, + STR_ACTION_LOWER_LAND = 5667, + STR_ACTION_SMOOTH_LAND = 5668, + STR_ACTION_RAISE_WATER = 5669, + STR_ACTION_LOWER_WATER = 5670, + STR_ACTION_SET_BRAKES_SPEED = 5671, + STR_ACTION_HIRE_NEW_STAFF = 5672, + STR_ACTION_SET_STAFF_PATROL = 5673, + STR_ACTION_FIRE_STAFF = 5674, + STR_ACTION_SET_STAFF_ORDER = 5675, + STR_ACTION_SET_PARK_NAME = 5676, + STR_ACTION_OPEN_CLOSE_PARK = 5677, + STR_ACTION_BUY_LAND_RIGHTS = 5678, + STR_ACTION_PLACE_PARK_ENTRANCE = 5679, + STR_ACTION_REMOVE_PARK_ENTRANCE = 5680, + STR_ACTION_SET_MAZE_TRACK = 5681, + STR_ACTION_SET_PARK_ENTRANCE_FEE = 5682, + STR_ACTION_SET_STAFF_COLOUR = 5683, + STR_ACTION_PLACE_FENCE = 5684, + STR_ACTION_REMOVE_FENCE = 5685, + STR_ACTION_PLACE_LARGE_SCENERY = 5686, + STR_ACTION_SET_CURRENT_LOAN = 5687, + STR_ACTION_SET_RESEARCH_FUNDING = 5688, + STR_ACTION_PLACE_TRACK_DESIGN = 5689, + STR_ACTION_START_MARKETING_CAMPAIGN = 5690, + STR_ACTION_PLACE_MAZE_DESIGN = 5691, + STR_ACTION_PLACE_BANNER = 5692, + STR_ACTION_REMOVE_BANNER = 5693, + STR_ACTION_SET_SCENERY_COLOUR = 5694, + STR_ACTION_SET_FENCE_COLOUR = 5695, + STR_ACTION_SET_LARGE_SCENERY_COLOUR = 5696, + STR_ACTION_SET_BANNER_COLOUR = 5697, + STR_ACTION_SET_LAND_OWNERSHIP = 5698, + STR_ACTION_CLEAR_SCENERY = 5699, + STR_ACTION_SET_BANNER_NAME = 5700, + STR_ACTION_SET_SIGN_NAME = 5701, + STR_ACTION_SET_BANNER_STYLE = 5702, + STR_ACTION_SET_SIGN_STYLE = 5703, + STR_ACTION_SET_PLAYER_GROUP = 5704, + STR_ACTION_MODIFY_GROUPS = 5705, // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working STR_COUNT = 32768 diff --git a/src/network/network.cpp b/src/network/network.cpp index 2308b3a94d..8fcf2fb136 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -53,6 +53,7 @@ extern "C" { #pragma comment(lib, "Ws2_32.lib") Network gNetwork; +NetworkActions gNetworkActions; enum { NETWORK_READPACKET_SUCCESS, @@ -73,6 +74,7 @@ enum { NETWORK_COMMAND_SETDISCONNECTMSG, NETWORK_COMMAND_GAMEINFO, NETWORK_COMMAND_SHOWERROR, + NETWORK_COMMAND_GROUPLIST, NETWORK_COMMAND_MAX, NETWORK_COMMAND_INVALID = -1 }; @@ -180,15 +182,34 @@ bool NetworkPacket::CommandRequiresAuth() } } -NetworkPlayer::NetworkPlayer(const char* name) +NetworkPlayer::NetworkPlayer() { - safe_strcpy((char*)NetworkPlayer::name, name, sizeof(NetworkPlayer::name)); - NetworkPlayer::name[sizeof(NetworkPlayer::name) - 1] = 0; + name[0] = 0; ping = 0; flags = 0; money_spent = MONEY(0, 0); commands_ran = 0; group = 0; + reserved = 0; +} + +void NetworkPlayer::Read(NetworkPacket& packet) +{ + const char* name = packet.ReadString(); + SetName(name); + packet >> id >> flags >> group >> reserved; +} + +void NetworkPlayer::Write(NetworkPacket& packet) +{ + packet.WriteString((const char*)name); + packet << id << flags << group << reserved; +} + +void NetworkPlayer::SetName(const char* name) +{ + safe_strcpy((char*)NetworkPlayer::name, name, sizeof(NetworkPlayer::name)); + NetworkPlayer::name[sizeof(NetworkPlayer::name) - 1] = 0; } void NetworkPlayer::AddMoneySpent(money32 cost) @@ -198,25 +219,102 @@ void NetworkPlayer::AddMoneySpent(money32 cost) window_invalidate_by_number(WC_PLAYER, id); } +int NetworkActions::FindGameCommand(int command) +{ + auto it = std::find_if(actions.begin(), actions.end(), [&command](NetworkAction const& action) { return action.game_command == command; }); + if (it != actions.end()) { + return it - actions.begin(); + } + return -1; +} + NetworkGroup::NetworkGroup() { - permissions = 0; name_string_id = STR_NONE; } NetworkGroup::~NetworkGroup() { - user_string_free(name_string_id); + FreeNameStringId(); } -bool NetworkGroup::CanRun(int command) +void NetworkGroup::Read(NetworkPacket& packet) { - if (permissions) { - return true; + packet >> id; + SetName(packet.ReadString()); + for (size_t i = 0; i < actions_allowed.size(); i++) { + packet >> actions_allowed[i]; + } +} + +void NetworkGroup::Write(NetworkPacket& packet) +{ + packet << id; + packet.WriteString(GetName().c_str()); + for (size_t i = 0; i < actions_allowed.size(); i++) { + packet << actions_allowed[i]; + } +} + +void NetworkGroup::FreeNameStringId() +{ + if (name_string_id != STR_NONE) { + user_string_free(name_string_id); + name_string_id = STR_NONE; + } +} + +void NetworkGroup::ToggleActionPermission(size_t index) +{ + size_t byte = index / 8; + size_t bit = index % 8; + if (byte >= actions_allowed.size()) { + return; + } + actions_allowed[byte] ^= (1 << bit); +} + +bool NetworkGroup::CanPerformAction(size_t index) +{ + size_t byte = index / 8; + size_t bit = index % 8; + if (byte >= actions_allowed.size()) { + return false; + } + return (actions_allowed[byte] & (1 << bit)) ? true : false; +} + +bool NetworkGroup::CanPerformGameCommand(uint32 command) +{ + if ((size_t)command >= Util::CountOf(new_game_command_table)) { + return false; + } + int action = gNetworkActions.FindGameCommand(command); + if (action != -1) { + return CanPerformAction(action); } return false; } +std::string& NetworkGroup::GetName() +{ + return name; +} + +void NetworkGroup::SetName(std::string name) +{ + FreeNameStringId(); + NetworkGroup::name = name; +} + +rct_string_id NetworkGroup::GetNameStringId() +{ + if (name_string_id == STR_NONE) { + name_string_id = user_string_allocate(128, name.c_str()); + } + return name_string_id; +} + NetworkConnection::NetworkConnection() { authstatus = NETWORK_AUTH_NONE; @@ -462,17 +560,17 @@ bool Network::Init() #endif // Hardcoded permission groups + std::unique_ptr admin(new NetworkGroup()); // change to make_unique in c++14 + admin->SetName("Admin"); + admin->actions_allowed.fill(0xFF); + admin->id = 0; + group_list.push_back(std::move(admin)); std::unique_ptr spectator(new NetworkGroup()); // change to make_unique in c++14 - spectator->name = "Spectator"; - spectator->permissions = 0; - spectator->id = 0; + spectator->SetName("Spectator"); + spectator->ToggleActionPermission(0); + spectator->id = 1; group_list.push_back(std::move(spectator)); - std::unique_ptr fullaccess(new NetworkGroup()); // change to make_unique in c++14 - fullaccess->name = "Full Access"; - fullaccess->permissions = 1; - fullaccess->id = 1; - group_list.push_back(std::move(fullaccess)); - SetDefaultGroup(0); + SetDefaultGroup(1); status = NETWORK_STATUS_READY; return true; @@ -573,9 +671,10 @@ bool Network::BeginServer(unsigned short port, const char* address) return false; } - NetworkPlayer* player = AddPlayer(gConfigNetwork.player_name); + NetworkPlayer* player = AddPlayer(); + player->SetName(gConfigNetwork.player_name); player->flags |= NETWORK_PLAYER_FLAG_ISSERVER; - player->group = 1; + player->group = 0; player_id = player->id; printf("Ready for clients...\n"); @@ -1022,6 +1121,30 @@ void Network::AdvertiseHeartbeat() #endif } +NetworkGroup* Network::AddGroup() +{ + NetworkGroup* addedgroup = nullptr; + int newid = -1; + // Find first unused group id + for (int id = 0; id < 255; id++) { + if (std::find_if(group_list.begin(), group_list.end(), [&id](std::unique_ptr const& group) { return group->id == id; }) == group_list.end()) { + newid = id; + break; + } + } + if (newid != -1) { + std::unique_ptr group(new NetworkGroup); // change to make_unique in c++14 + group->id = newid; + group->SetName("Group #" + std::to_string(newid)); + addedgroup = group.get(); + group_list.push_back(std::move(group)); + if (GetMode() == NETWORK_MODE_SERVER) { + Server_Send_GROUPLIST(); + } + } + return addedgroup; +} + uint8 Network::GetDefaultGroup() { return default_group; @@ -1073,14 +1196,14 @@ void Network::Server_Send_MAP(NetworkConnection* connection) size_t chunksize = 1000; size_t out_size; unsigned char *compressed = util_zlib_deflate(&buffer[0], size, &out_size); - unsigned char *header = (unsigned char *)strdup("open2_sv6_zlib"); + unsigned char *header = (unsigned char *)_strdup("open2_sv6_zlib"); size_t header_len = strlen((char *)header) + 1; // account for null terminator header = (unsigned char *)realloc(header, header_len + out_size); memcpy(&header[header_len], compressed, out_size); out_size += header_len; free(compressed); log_verbose("Sending map of size %u bytes, compressed to %u bytes", size, out_size); - for (int i = 0; i < out_size; i += chunksize) { + for (size_t i = 0; i < out_size; i += chunksize) { int datasize = (std::min)(chunksize, out_size - i); std::unique_ptr packet = std::move(NetworkPacket::Allocate()); *packet << (uint32)NETWORK_COMMAND_MAP << (uint32)out_size << (uint32)i; @@ -1138,8 +1261,7 @@ void Network::Server_Send_PLAYERLIST() std::unique_ptr packet = std::move(NetworkPacket::Allocate()); *packet << (uint32)NETWORK_COMMAND_PLAYERLIST << (uint32)player_list.size(); for (unsigned int i = 0; i < player_list.size(); i++) { - packet->WriteString((const char*)player_list[i]->name); - *packet << player_list[i]->id << player_list[i]->flags << player_list[i]->group << player_list[i]->reserved; + player_list[i]->Write(*packet); } SendPacketToClients(*packet); } @@ -1214,6 +1336,13 @@ void Network::Server_Send_SHOWERROR(NetworkConnection& connection, rct_string_id connection.QueuePacket(std::move(packet)); } +void Network::Server_Send_GROUPLIST() +{ + std::unique_ptr packet = std::move(NetworkPacket::Allocate()); + *packet << (uint32)NETWORK_COMMAND_GROUPLIST; + SendPacketToClients(*packet); +} + bool Network::ProcessConnection(NetworkConnection& connection) { int packetStatus; @@ -1321,7 +1450,7 @@ void Network::RemoveClient(std::unique_ptr& connection) Server_Send_PLAYERLIST(); } -NetworkPlayer* Network::AddPlayer(const char* name) +NetworkPlayer* Network::AddPlayer() { NetworkPlayer* addedplayer = nullptr; int newid = -1; @@ -1337,14 +1466,11 @@ NetworkPlayer* Network::AddPlayer(const char* name) newid = 0; } if (newid != -1) { - std::unique_ptr player(new NetworkPlayer(name)); // change to make_unique in c++14 + std::unique_ptr player(new NetworkPlayer); // change to make_unique in c++14 player->id = newid; player->group = GetDefaultGroup(); addedplayer = player.get(); player_list.push_back(std::move(player)); - if (GetMode() == NETWORK_MODE_SERVER) { - Server_Send_PLAYERLIST(); - } } return addedplayer; } @@ -1412,17 +1538,19 @@ int Network::Server_Handle_AUTH(NetworkConnection& connection, NetworkPacket& pa connection.authstatus = NETWORK_AUTH_FULL; } else { connection.authstatus = NETWORK_AUTH_OK; - NetworkPlayer* player = AddPlayer(name); + NetworkPlayer* player = AddPlayer(); connection.player = player; if (player) { + player->SetName(name); char text[256]; char* lineCh = text; lineCh = utf8_write_codepoint(lineCh, FORMAT_OUTLINE); lineCh = utf8_write_codepoint(lineCh, FORMAT_GREEN); sprintf(lineCh, "%s has joined the game", player->name); chat_history_add(text); - gNetwork.Server_Send_CHAT(text); Server_Send_MAP(&connection); + gNetwork.Server_Send_CHAT(text); + Server_Send_PLAYERLIST(); } } Server_Send_AUTH(connection); @@ -1541,7 +1669,7 @@ int Network::Server_Handle_GAMECMD(NetworkConnection& connection, NetworkPacket& // Check if player's group permission allows command to run NetworkGroup* group = GetGroupByID(connection.player->group); - if (group && !group->CanRun(commandCommand)) { + if (group && !group->CanPerformGameCommand(commandCommand)) { Server_Send_SHOWERROR(connection, STR_CANT_DO_THIS, STR_PERMISSION_DENIED); return 0; } @@ -1582,20 +1710,13 @@ int Network::Client_Handle_PLAYERLIST(NetworkConnection& connection, NetworkPack packet >> size; std::vector ids; for (unsigned int i = 0; i < size; i++) { - const char* name = packet.ReadString(); - uint8 id; - uint8 flags; - uint8 group; - uint16 reserved; - packet >> id >> flags >> group >> reserved; - ids.push_back(id); - if (!GetPlayerByID(id)) { - NetworkPlayer* player = AddPlayer(name); + NetworkPlayer tempplayer; + tempplayer.Read(packet); + ids.push_back(tempplayer.id); + if (!GetPlayerByID(tempplayer.id)) { + NetworkPlayer* player = AddPlayer(); if (player) { - player->id = id; - player->flags = flags; - player->group = group; - player->reserved = reserved; + *player = tempplayer; if (player->flags & NETWORK_PLAYER_FLAG_ISSERVER) { server_connection.player = player; } @@ -1810,15 +1931,12 @@ int network_get_num_groups() const char* network_get_group_name(unsigned int index) { - return gNetwork.group_list[index]->name.c_str(); + return gNetwork.group_list[index]->GetName().c_str(); } rct_string_id network_get_group_name_string_id(unsigned int index) { - if (gNetwork.group_list[index]->name_string_id == STR_NONE) { - gNetwork.group_list[index]->name_string_id = user_string_allocate(128, gNetwork.group_list[index]->name.c_str()); - } - return gNetwork.group_list[index]->name_string_id; + return gNetwork.group_list[index]->GetNameStringId(); } void game_command_set_player_group(int* eax, int* ebx, int* ecx, int* edx, int* esi, int* edi, int* ebp) @@ -1846,6 +1964,90 @@ void game_command_set_player_group(int* eax, int* ebx, int* ecx, int* edx, int* *ebx = 0; } +void game_command_modify_groups(int *eax, int *ebx, int *ecx, int *edx, int *esi, int *edi, int *ebp) +{ + uint8 action = (uint8)*eax; + uint8 groupid = (uint8)(*eax >> 8); + uint8 nameChunkIndex = (uint8)(*eax >> 16); + + rct_string_id newUserStringId; + char oldName[128]; + static char newName[128]; + + switch (action) + { + case 0:{ // add group + NetworkGroup* newgroup = gNetwork.AddGroup(); + if (!newgroup) { + *ebx = MONEY32_UNDEFINED; + return; + } + }break; + case 1:{ // remove group + + }break; + case 2:{ // toggle permission + if (groupid == 0) { // cant change admin group permissions + RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TITLE, uint16) = STR_CANT_DO_THIS; + *ebx = MONEY32_UNDEFINED; + return; + } + if (*ebx & GAME_COMMAND_FLAG_APPLY) { + int index = *ecx; + NetworkGroup* group = gNetwork.GetGroupByID(groupid); + if (group) { + group->ToggleActionPermission(index); + } + } + }break; + case 3:{ // set group name + size_t nameChunkOffset = nameChunkIndex - 1; + if (nameChunkOffset < 0) + nameChunkOffset = 2; + nameChunkOffset *= 12; + nameChunkOffset = min(nameChunkOffset, Util::CountOf(newName) - 12); + RCT2_GLOBAL(newName + nameChunkOffset + 0, uint32) = *edx; + RCT2_GLOBAL(newName + nameChunkOffset + 4, uint32) = *ebp; + RCT2_GLOBAL(newName + nameChunkOffset + 8, uint32) = *edi; + + if (nameChunkIndex != 0) { + *ebx = 0; + return; + } + + if (strcmp(oldName, newName) == 0) { + *ebx = 0; + return; + } + + if (newName[0] == 0) { + RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = STR_INVALID_RIDE_ATTRACTION_NAME; + *ebx = MONEY32_UNDEFINED; + return; + } + + newUserStringId = user_string_allocate(4, newName); + if (newUserStringId == 0) { + RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = STR_INVALID_NAME_FOR_PARK; + *ebx = MONEY32_UNDEFINED; + return; + } + + if (*ebx & GAME_COMMAND_FLAG_APPLY) { + // Free the old ride name + user_string_free(RCT2_GLOBAL(RCT2_ADDRESS_PARK_NAME, rct_string_id)); + RCT2_GLOBAL(RCT2_ADDRESS_PARK_NAME, rct_string_id) = newUserStringId; + + gfx_invalidate_screen(); + } else { + user_string_free(newUserStringId); + } + }break; + } + + *ebx = 0; +} + uint8 network_get_default_group() { return gNetwork.GetDefaultGroup(); @@ -1856,6 +2058,21 @@ void network_set_default_group(uint8 id) gNetwork.SetDefaultGroup(id); } +int network_get_num_actions() +{ + return gNetworkActions.actions.size(); +} + +rct_string_id network_get_action_name_string_id(unsigned int index) +{ + return gNetworkActions.actions[index].name; +} + +int network_can_perform_action(unsigned int groupindex, unsigned int index) +{ + return gNetwork.group_list[groupindex]->CanPerformAction(index); +} + void network_send_map() { gNetwork.Server_Send_MAP(); @@ -1930,6 +2147,9 @@ rct_string_id network_get_group_name_string_id(unsigned int index) { return -1; void game_command_set_player_group(int* eax, int* ebx, int* ecx, int* edx, int* esi, int* edi, int* ebp) { } uint8 network_get_default_group() { return 0; } void network_set_default_group(uint8 id) { } +int network_get_num_actions() { return 0; } +rct_string_id network_get_action_name_string_id(unsigned int index) { return -1; } +int network_can_perform_action(unsigned int groupindex, unsigned int index) { return 0; } void network_send_chat(const char* text) {} void network_send_password(const char* password) {} void network_close() {} diff --git a/src/network/network.h b/src/network/network.h index e5f9ba529e..be26ecb363 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -56,7 +56,9 @@ enum { extern "C" { #endif // __cplusplus #include "../common.h" +#include "../game.h" #include "../platform/platform.h" +#include "../localisation/string_ids.h" #ifdef __cplusplus } #endif // __cplusplus @@ -98,6 +100,7 @@ extern "C" { #ifdef __cplusplus +#include #include #include #include @@ -144,7 +147,10 @@ public: class NetworkPlayer { public: - NetworkPlayer(const char* name); + NetworkPlayer(); + void Read(NetworkPacket& packet); + void Write(NetworkPacket& packet); + void SetName(const char* name); void AddMoneySpent(money32 cost); uint8 id; uint8 name[32 + 1]; @@ -156,15 +162,104 @@ public: unsigned int commands_ran; }; +class NetworkAction +{ +public: + rct_string_id name; + int game_command; +}; + +class NetworkActions +{ +public: + int FindGameCommand(int command); + const std::vector actions = { + {STR_ACTION_CHAT, -1}, + {STR_ACTION_SET_RIDE_APPEARANCE, GAME_COMMAND_SET_RIDE_APPEARANCE}, + //{STR_NONE, GAME_COMMAND_SET_LAND_HEIGHT}, + {STR_ACTION_TOGGLE_PAUSE, GAME_COMMAND_TOGGLE_PAUSE}, + {STR_ACTION_PLACE_TRACK, GAME_COMMAND_PLACE_TRACK}, + {STR_ACTION_REMOVE_TRACK, GAME_COMMAND_REMOVE_TRACK}, + //{STR_NONE, GAME_COMMAND_LOAD_OR_QUIT}, + {STR_ACTION_CREATE_RIDE, GAME_COMMAND_CREATE_RIDE}, + {STR_ACTION_REMOVE_RIDE, GAME_COMMAND_DEMOLISH_RIDE}, + {STR_ACTION_SET_RIDE_STATUS, GAME_COMMAND_SET_RIDE_STATUS}, + {STR_ACTION_SET_RIDE_VEHICLES, GAME_COMMAND_SET_RIDE_VEHICLES}, + {STR_ACTION_SET_RIDE_NAME, GAME_COMMAND_SET_RIDE_NAME}, + {STR_ACTION_SET_RIDE_SETTING, GAME_COMMAND_SET_RIDE_SETTING}, + {STR_ACTION_PLACE_RIDE_ENTRANCE_EXIT, GAME_COMMAND_PLACE_RIDE_ENTRANCE_OR_EXIT}, + {STR_ACTION_REMOVE_RIDE_ENTRANCE_EXIT, GAME_COMMAND_REMOVE_RIDE_ENTRANCE_OR_EXIT}, + {STR_ACTION_REMOVE_SCENERY, GAME_COMMAND_REMOVE_SCENERY}, + {STR_ACTION_PLACE_SCENERY, GAME_COMMAND_PLACE_SCENERY}, + //{STR_NONE, GAME_COMMAND_SET_WATER_HEIGHT}, + {STR_ACTION_PLACE_PATH, GAME_COMMAND_PLACE_PATH}, + {STR_ACTION_PLACE_PATH_FROM_TRACK, GAME_COMMAND_PLACE_PATH_FROM_TRACK}, + {STR_ACTION_REMOVE_PATH, GAME_COMMAND_REMOVE_PATH}, + {STR_ACTION_CHANGE_SURFACE_STYLE, GAME_COMMAND_CHANGE_SURFACE_STYLE}, + {STR_ACTION_SET_RIDE_PRICE, GAME_COMMAND_SET_RIDE_PRICE}, + {STR_ACTION_SET_PEEP_NAME, GAME_COMMAND_SET_PEEP_NAME}, + {STR_ACTION_RAISE_LAND, GAME_COMMAND_RAISE_LAND}, + {STR_ACTION_LOWER_LAND, GAME_COMMAND_LOWER_LAND}, + {STR_ACTION_SMOOTH_LAND, GAME_COMMAND_EDIT_LAND_SMOOTH}, + {STR_ACTION_RAISE_WATER, GAME_COMMAND_RAISE_WATER}, + {STR_ACTION_LOWER_WATER, GAME_COMMAND_LOWER_WATER}, + {STR_ACTION_SET_BRAKES_SPEED, GAME_COMMAND_SET_BRAKES_SPEED}, + {STR_ACTION_HIRE_NEW_STAFF, GAME_COMMAND_HIRE_NEW_STAFF_MEMBER}, + {STR_ACTION_SET_STAFF_PATROL, GAME_COMMAND_SET_STAFF_PATROL}, + {STR_ACTION_FIRE_STAFF, GAME_COMMAND_FIRE_STAFF_MEMBER}, + {STR_ACTION_SET_STAFF_ORDER, GAME_COMMAND_SET_STAFF_ORDER}, + {STR_ACTION_SET_PARK_NAME, GAME_COMMAND_SET_PARK_NAME}, + {STR_ACTION_OPEN_CLOSE_PARK, GAME_COMMAND_SET_PARK_OPEN}, + {STR_ACTION_BUY_LAND_RIGHTS, GAME_COMMAND_BUY_LAND_RIGHTS}, + {STR_ACTION_PLACE_PARK_ENTRANCE, GAME_COMMAND_PLACE_PARK_ENTRANCE}, + {STR_ACTION_REMOVE_PARK_ENTRANCE, GAME_COMMAND_REMOVE_PARK_ENTRANCE}, + {STR_ACTION_SET_MAZE_TRACK, GAME_COMMAND_SET_MAZE_TRACK}, + {STR_ACTION_SET_PARK_ENTRANCE_FEE, GAME_COMMAND_SET_PARK_ENTRANCE_FEE}, + {STR_ACTION_SET_STAFF_COLOUR, GAME_COMMAND_SET_STAFF_COLOUR}, + {STR_ACTION_PLACE_FENCE, GAME_COMMAND_PLACE_FENCE}, + {STR_ACTION_REMOVE_FENCE, GAME_COMMAND_REMOVE_FENCE}, + {STR_ACTION_PLACE_LARGE_SCENERY, GAME_COMMAND_PLACE_LARGE_SCENERY}, + {STR_ACTION_SET_CURRENT_LOAN, GAME_COMMAND_SET_CURRENT_LOAN}, + {STR_ACTION_SET_RESEARCH_FUNDING, GAME_COMMAND_SET_RESEARCH_FUNDING}, + {STR_ACTION_PLACE_TRACK_DESIGN, GAME_COMMAND_PLACE_TRACK_DESIGN}, + {STR_ACTION_START_MARKETING_CAMPAIGN, GAME_COMMAND_START_MARKETING_CAMPAIGN}, + {STR_ACTION_PLACE_MAZE_DESIGN, GAME_COMMAND_PLACE_MAZE_DESIGN}, + {STR_ACTION_PLACE_BANNER, GAME_COMMAND_PLACE_BANNER}, + {STR_ACTION_REMOVE_BANNER, GAME_COMMAND_REMOVE_BANNER}, + {STR_ACTION_SET_SCENERY_COLOUR, GAME_COMMAND_SET_SCENERY_COLOUR}, + {STR_ACTION_SET_FENCE_COLOUR, GAME_COMMAND_SET_FENCE_COLOUR}, + {STR_ACTION_SET_LARGE_SCENERY_COLOUR, GAME_COMMAND_SET_LARGE_SCENERY_COLOUR}, + {STR_ACTION_SET_BANNER_COLOUR, GAME_COMMAND_SET_BANNER_COLOUR}, + {STR_ACTION_SET_LAND_OWNERSHIP, GAME_COMMAND_SET_LAND_OWNERSHIP}, + {STR_ACTION_CLEAR_SCENERY, GAME_COMMAND_CLEAR_SCENERY}, + {STR_ACTION_SET_BANNER_NAME, GAME_COMMAND_SET_BANNER_NAME}, + {STR_ACTION_SET_SIGN_NAME, GAME_COMMAND_SET_SIGN_NAME}, + {STR_ACTION_SET_BANNER_STYLE, GAME_COMMAND_SET_BANNER_STYLE}, + {STR_ACTION_SET_SIGN_STYLE, GAME_COMMAND_SET_SIGN_STYLE}, + {STR_ACTION_SET_PLAYER_GROUP, GAME_COMMAND_SET_PLAYER_GROUP}, + {STR_ACTION_MODIFY_GROUPS, GAME_COMMAND_MODIFY_GROUPS} + }; +}; + class NetworkGroup { public: NetworkGroup(); ~NetworkGroup(); - bool CanRun(int command); - std::string name; - uint32 permissions; + void Read(NetworkPacket& packet); + void Write(NetworkPacket& packet); + void FreeNameStringId(); + void ToggleActionPermission(size_t index); + bool CanPerformAction(size_t index); + bool CanPerformGameCommand(uint32 command); + std::string& GetName(); + void SetName(std::string name); + rct_string_id GetNameStringId(); + std::array actions_allowed = {0}; uint8 id; + +private: + std::string name; rct_string_id name_string_id; }; @@ -249,6 +344,7 @@ public: void ShutdownClient(); void AdvertiseRegister(); void AdvertiseHeartbeat(); + NetworkGroup* AddGroup(); uint8 GetDefaultGroup(); void SetDefaultGroup(uint8 id); @@ -267,6 +363,7 @@ public: void Server_Send_SETDISCONNECTMSG(NetworkConnection& connection, const char* msg); void Server_Send_GAMEINFO(NetworkConnection& connection); void Server_Send_SHOWERROR(NetworkConnection& connection, rct_string_id title, rct_string_id message); + void Server_Send_GROUPLIST(); std::vector> player_list; std::vector> group_list; @@ -277,7 +374,7 @@ private: void ProcessGameCommandQueue(); void AddClient(SOCKET socket); void RemoveClient(std::unique_ptr& connection); - NetworkPlayer* AddPlayer(const char* name); + NetworkPlayer* AddPlayer(); void PrintError(); const char* GetMasterServerUrl(); std::string GenerateAdvertiseKey(); @@ -378,8 +475,12 @@ int network_get_num_groups(); const char* network_get_group_name(unsigned int index); rct_string_id network_get_group_name_string_id(unsigned int index); void game_command_set_player_group(int* eax, int* ebx, int* ecx, int* edx, int* esi, int* edi, int* ebp); +void game_command_modify_groups(int *eax, int *ebx, int *ecx, int *edx, int *esi, int *edi, int *ebp); uint8 network_get_default_group(); void network_set_default_group(uint8 id); +int network_get_num_actions(); +rct_string_id network_get_action_name_string_id(unsigned int index); +int network_can_perform_action(unsigned int groupindex, unsigned int index); void network_send_map(); void network_send_chat(const char* text); diff --git a/src/windows/multiplayer.c b/src/windows/multiplayer.c index 5c7b6c64f4..2a68e95b66 100644 --- a/src/windows/multiplayer.c +++ b/src/windows/multiplayer.c @@ -44,6 +44,11 @@ enum WINDOW_MULTIPLAYER_WIDGET_IDX { WIDX_DEFAULT_GROUP = 6, WIDX_DEFAULT_GROUP_DROPDOWN, + WIDX_ADD_GROUP, + WIDX_REMOVE_GROUP, + WIDX_SELECTED_GROUP, + WIDX_SELECTED_GROUP_DROPDOWN, + WIDX_PERMISSIONS_LIST, }; static rct_widget window_multiplayer_players_widgets[] = { @@ -66,6 +71,11 @@ static rct_widget window_multiplayer_groups_widgets[] = { { WWT_TAB, 1, 3, 33, 17, 43, 0x02000144E, STR_GROUPS_TIP }, // tab { WWT_DROPDOWN, 1, 141, 315, 46, 57, 0x0FFFFFFFF, STR_NONE }, // default group { WWT_DROPDOWN_BUTTON, 1, 305, 315, 47, 56, 876, STR_NONE }, // + { WWT_DROPDOWN_BUTTON, 1, 44, 134, 65, 76, STR_ADD_GROUP, STR_NONE }, // add group button + { WWT_DROPDOWN_BUTTON, 1, 178, 268, 65, 76, STR_REMOVE_GROUP, STR_NONE }, // remove group button + { WWT_DROPDOWN, 1, 72, 246, 80, 91, 0x0FFFFFFFF, STR_NONE }, // selected group + { WWT_DROPDOWN_BUTTON, 1, 236, 246, 81, 90, 876, STR_NONE }, // + { WWT_SCROLL, 1, 3, 316, 94, 300, 2, STR_NONE }, // permissions list { WIDGETS_END } }; @@ -76,9 +86,11 @@ static rct_widget *window_multiplayer_page_widgets[] = { const uint64 window_multiplayer_page_enabled_widgets[] = { (1 << WIDX_CLOSE) | (1 << WIDX_TAB1) | (1 << WIDX_TAB2), - (1 << WIDX_CLOSE) | (1 << WIDX_TAB1) | (1 << WIDX_TAB2) | (1 << WIDX_DEFAULT_GROUP) | (1 << WIDX_DEFAULT_GROUP_DROPDOWN) + (1 << WIDX_CLOSE) | (1 << WIDX_TAB1) | (1 << WIDX_TAB2) | (1 << WIDX_DEFAULT_GROUP) | (1 << WIDX_DEFAULT_GROUP_DROPDOWN) | (1 << WIDX_ADD_GROUP) | (1 << WIDX_REMOVE_GROUP) | (1 << WIDX_SELECTED_GROUP) | (1 << WIDX_SELECTED_GROUP_DROPDOWN) }; +static uint8 _selectedGroup = 0; + static void window_multiplayer_players_mouseup(rct_window *w, int widgetIndex); static void window_multiplayer_players_resize(rct_window *w); static void window_multiplayer_players_mousedown(int widgetIndex, rct_window* w, rct_widget* widget); @@ -95,8 +107,12 @@ static void window_multiplayer_groups_resize(rct_window *w); static void window_multiplayer_groups_mousedown(int widgetIndex, rct_window* w, rct_widget* widget); static void window_multiplayer_groups_dropdown(rct_window *w, int widgetIndex, int dropdownIndex); static void window_multiplayer_groups_update(rct_window *w); +static void window_multiplayer_groups_scrollgetsize(rct_window *w, int scrollIndex, int *width, int *height); +static void window_multiplayer_groups_scrollmousedown(rct_window *w, int scrollIndex, int x, int y); +static void window_multiplayer_groups_scrollmouseover(rct_window *w, int scrollIndex, int x, int y); static void window_multiplayer_groups_invalidate(rct_window *w); static void window_multiplayer_groups_paint(rct_window *w, rct_drawpixelinfo *dpi); +static void window_multiplayer_groups_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int scrollIndex); static rct_window_event_list window_multiplayer_players_events = { NULL, @@ -145,10 +161,10 @@ static rct_window_event_list window_multiplayer_groups_events = { NULL, NULL, NULL, + window_multiplayer_groups_scrollgetsize, + window_multiplayer_groups_scrollmousedown, NULL, - NULL, - NULL, - NULL, + window_multiplayer_groups_scrollmouseover, NULL, NULL, NULL, @@ -157,7 +173,7 @@ static rct_window_event_list window_multiplayer_groups_events = { NULL, window_multiplayer_groups_invalidate, window_multiplayer_groups_paint, - NULL, + window_multiplayer_groups_scrollpaint }; static rct_window_event_list *window_multiplayer_page_events[] = { @@ -168,7 +184,6 @@ static rct_window_event_list *window_multiplayer_page_events[] = { const int window_multiplayer_animation_divisor[] = { 4, 4 }; const int window_multiplayer_animation_frames[] = { 8, 16 }; -static void window_multiplayer_players_refresh_list(rct_window *w); static void window_multiplayer_draw_tab_images(rct_window *w, rct_drawpixelinfo *dpi); static void window_multiplayer_set_page(rct_window* w, int page); @@ -228,7 +243,7 @@ static void window_multiplayer_set_pressed_tab(rct_window *w) w->pressed_widgets |= 1LL << (WIDX_TAB1 + w->page); } -static void window_multiplayer_groups_show_defaultgroup_dropdown(rct_window *w, rct_widget *widget) +static void window_multiplayer_groups_show_group_dropdown(rct_window *w, rct_widget *widget) { rct_widget *dropdownWidget; int numItems, i; @@ -251,8 +266,12 @@ static void window_multiplayer_groups_show_defaultgroup_dropdown(rct_window *w, gDropdownItemsFormat[i] = 1142; gDropdownItemsArgs[i] = network_get_group_name_string_id(i); } - - dropdown_set_checked(network_get_group_index(network_get_default_group()), true); + if (widget == &window_multiplayer_groups_widgets[WIDX_DEFAULT_GROUP_DROPDOWN]) { + dropdown_set_checked(network_get_group_index(network_get_default_group()), true); + } else + if (widget == &window_multiplayer_groups_widgets[WIDX_SELECTED_GROUP_DROPDOWN]) { + dropdown_set_checked(network_get_group_index(_selectedGroup), true); + } } static void window_multiplayer_players_mouseup(rct_window *w, int widgetIndex) @@ -268,7 +287,11 @@ static void window_multiplayer_players_resize(rct_window *w) { window_set_resize(w, 320, 124, 500, 450); - window_multiplayer_players_refresh_list(w); + w->no_list_items = network_get_num_players(); + w->list_item_positions[0] = 0; + + w->selected_list_item = -1; + window_invalidate(w); } static void window_multiplayer_players_mousedown(int widgetIndex, rct_window* w, rct_widget* widget) @@ -371,8 +394,6 @@ static void window_multiplayer_players_scrollpaint(rct_window *w, rct_drawpixeli { int y; - gfx_fill_rect(dpi, dpi->x, dpi->y, dpi->x + dpi->width - 1, dpi->y + dpi->height - 1, ColourMapA[w->colours[1]].mid_light); - y = 0; for (int i = 0; i < network_get_num_players(); i++) { if (y > dpi->y + dpi->height) { @@ -439,7 +460,13 @@ static void window_multiplayer_groups_mouseup(rct_window *w, int widgetIndex) static void window_multiplayer_groups_resize(rct_window *w) { - window_set_resize(w, 320, 61, 320, 61); + window_set_resize(w, 320, 200, 320, 500); + + w->no_list_items = network_get_num_actions(); + w->list_item_positions[0] = 0; + + w->selected_list_item = -1; + window_invalidate(w); } static void window_multiplayer_groups_mousedown(int widgetIndex, rct_window* w, rct_widget* widget) @@ -452,7 +479,10 @@ static void window_multiplayer_groups_mousedown(int widgetIndex, rct_window* w, } break; case WIDX_DEFAULT_GROUP_DROPDOWN: - window_multiplayer_groups_show_defaultgroup_dropdown(w, widget); + window_multiplayer_groups_show_group_dropdown(w, widget); + break; + case WIDX_SELECTED_GROUP_DROPDOWN: + window_multiplayer_groups_show_group_dropdown(w, widget); break; } } @@ -462,9 +492,18 @@ static void window_multiplayer_groups_dropdown(rct_window *w, int widgetIndex, i if (dropdownIndex == -1) { return; } - if (network_get_mode() == NETWORK_MODE_SERVER) { - network_set_default_group(network_get_group_id(dropdownIndex)); + + switch(widgetIndex){ + case WIDX_DEFAULT_GROUP_DROPDOWN: + if (network_get_mode() == NETWORK_MODE_SERVER) { + network_set_default_group(network_get_group_id(dropdownIndex)); + } + break; + case WIDX_SELECTED_GROUP_DROPDOWN: + _selectedGroup = network_get_group_id(dropdownIndex); + break; } + window_invalidate(w); } @@ -474,10 +513,61 @@ static void window_multiplayer_groups_update(rct_window *w) widget_invalidate(w, WIDX_TAB1 + w->page); } +static void window_multiplayer_groups_scrollgetsize(rct_window *w, int scrollIndex, int *width, int *height) +{ + int i; + + if (w->selected_list_item != -1) { + w->selected_list_item = -1; + window_invalidate(w); + } + + *height = network_get_num_actions() * 10; + i = *height - window_multiplayer_groups_widgets[WIDX_LIST].bottom + window_multiplayer_groups_widgets[WIDX_LIST].top + 21; + if (i < 0) + i = 0; + if (i < w->scrolls[0].v_top) { + w->scrolls[0].v_top = i; + window_invalidate(w); + } +} + +static void window_multiplayer_groups_scrollmousedown(rct_window *w, int scrollIndex, int x, int y) +{ + int index; + + index = y / 10; + if (index >= w->no_list_items) + return; + + w->selected_list_item = index; + window_invalidate(w); + + rct_widget *listWidget = &w->widgets[WIDX_LIST]; + int ddx = w->x + listWidget->left + x - w->scrolls[0].h_left; + int ddy = w->y + listWidget->top + y - w->scrolls[0].v_top; + + game_do_command(2 | (_selectedGroup << 8), GAME_COMMAND_FLAG_APPLY, index, 0, GAME_COMMAND_MODIFY_GROUPS, 0, 0); +} + +static void window_multiplayer_groups_scrollmouseover(rct_window *w, int scrollIndex, int x, int y) +{ + int index; + + index = y / 10; + if (index >= w->no_list_items) + return; + + w->selected_list_item = index; + window_invalidate(w); +} + static void window_multiplayer_groups_invalidate(rct_window *w) { window_multiplayer_set_pressed_tab(w); window_multiplayer_anchor_border_widgets(w); + window_multiplayer_groups_widgets[WIDX_PERMISSIONS_LIST].right = w->width - 4; + window_multiplayer_groups_widgets[WIDX_PERMISSIONS_LIST].bottom = w->height - 0x0F; window_align_tabs(w, WIDX_TAB1, WIDX_TAB2); } @@ -500,16 +590,73 @@ static void window_multiplayer_groups_paint(rct_window *w, rct_drawpixelinfo *dp ); } - gfx_draw_string_left(dpi, STR_DEFAULT_GROUP, NULL, w->colours[2], w->x + 6, 58 - 12 + w->y + 1); + int x = w->x + window_multiplayer_groups_widgets[WIDX_CONTENT_PANEL].left + 4; + int y = w->y + window_multiplayer_groups_widgets[WIDX_CONTENT_PANEL].top + 4; + + gfx_draw_string_left(dpi, STR_DEFAULT_GROUP, NULL, w->colours[2], x, y); + + y += 20; + + gfx_fill_rect_inset(dpi, x, y - 6, x + 310, y - 5, w->colours[1], 32); + + widget = &window_multiplayer_groups_widgets[WIDX_SELECTED_GROUP]; + group = network_get_group_index(_selectedGroup); + if (group != -1) { + RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS, uint16) = network_get_group_name_string_id(group); + gfx_draw_string_centred( + dpi, + 1193, + w->x + (widget->left + widget->right - 11) / 2, + w->y + widget->top, + 0, + (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS + ); + } } -static void window_multiplayer_players_refresh_list(rct_window *w) +static void window_multiplayer_groups_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int scrollIndex) { - w->no_list_items = network_get_num_players(); - w->list_item_positions[0] = 0; + int y = 0; - w->selected_list_item = -1; - window_invalidate(w); + gfx_fill_rect(dpi, dpi->x, dpi->y, dpi->x + dpi->width - 1, dpi->y + dpi->height - 1, ColourMapA[w->colours[1]].mid_light); + + for (int i = 0; i < network_get_num_actions(); i++) { + if (i == w->selected_list_item) { + gfx_fill_rect(dpi, 0, y, 800, y + 9, 0x02000031); + } + if (y > dpi->y + dpi->height) { + break; + } + + if (y + 11 >= dpi->y) { + char buffer[300]; + char* lineCh; + int groupindex = network_get_group_index(_selectedGroup); + if (groupindex != -1){ + if (network_can_perform_action(groupindex, i)) { + lineCh = buffer; + lineCh = utf8_write_codepoint(lineCh, FORMAT_WINDOW_COLOUR_2); + safe_strcpy(lineCh, "x", sizeof(buffer) - (lineCh - buffer)); + gfx_draw_string(dpi, buffer, 0, 0, y - 1); + } + } + + // Draw action name + RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS, uint16) = network_get_action_name_string_id(i); + gfx_draw_string_left(dpi, 1193, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, 0, 10, y - 1); + + /* + char buffer[300]; + char* lineCh; + lineCh = buffer; + int group = i; + lineCh = utf8_write_codepoint(lineCh, FORMAT_WINDOW_COLOUR_2); + safe_strcpy(lineCh, network_get_action_name(group), sizeof(buffer) - (lineCh - buffer)); + gfx_clip_string(buffer, 80); + gfx_draw_string(dpi, buffer, 0, 0, y - 1);*/ + } + y += 10; + } } static void window_multiplayer_draw_tab_image(rct_window *w, rct_drawpixelinfo *dpi, int page, int spriteIndex)