mirror of https://github.com/OpenRCT2/OpenRCT2.git
Remove Twitch from the rest of the code
This commit is contained in:
parent
aaece7a435
commit
9a9ab47856
|
@ -47,7 +47,7 @@ option(USE_MMAP "Use mmap to try loading rct2's data segment into memory.")
|
|||
|
||||
option(DISABLE_DISCORD_RPC "Disable Discord-RPC support." OFF)
|
||||
option(DISABLE_GOOGLE_BENCHMARK "Disable Google Benchmarks support." OFF)
|
||||
option(DISABLE_HTTP_TWITCH "Disable HTTP and Twitch support.")
|
||||
option(DISABLE_HTTP "Disable HTTP support.")
|
||||
option(DISABLE_NETWORK "Disable multiplayer functionality. Mainly for testing.")
|
||||
option(DISABLE_TTF "Disable support for TTF provided by freetype2.")
|
||||
option(ENABLE_LIGHTFX "Enable lighting effects." ON)
|
||||
|
@ -114,9 +114,8 @@ endif ()
|
|||
if (DISABLE_NETWORK)
|
||||
add_definitions(-DDISABLE_NETWORK)
|
||||
endif ()
|
||||
if (DISABLE_HTTP_TWITCH)
|
||||
if (DISABLE_HTTP)
|
||||
add_definitions(-DDISABLE_HTTP)
|
||||
add_definitions(-DDISABLE_TWITCH)
|
||||
endif ()
|
||||
if (DISABLE_TTF)
|
||||
add_definitions(-DNO_TTF)
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
01C6F0C822FD51FC0057E2F7 /* T6Exporter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 01C6F0C522FD51FC0057E2F7 /* T6Exporter.cpp */; };
|
||||
01C6F0C922FD51FC0057E2F7 /* T6Importer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 01C6F0C622FD51FC0057E2F7 /* T6Importer.cpp */; };
|
||||
01DDFE6522FD608500221318 /* Window_internal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 01DDFE6422FD608500221318 /* Window_internal.cpp */; };
|
||||
2A1F4FE0221FF4B0003CA045 /* Twitch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C840F1EC4E7CC00FA49E2 /* Twitch.cpp */; };
|
||||
2A1F4FE1221FF4B0003CA045 /* Audio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F76C83571EC4E7CC00FA49E2 /* Audio.cpp */; };
|
||||
2A1F4FE2221FF4B0003CA045 /* macos.mm in Sources */ = {isa = PBXBuildFile; fileRef = F76C845D1EC4E7CC00FA49E2 /* macos.mm */; };
|
||||
2A5354E922099C4F00A5440F /* Network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2A5354E822099C4F00A5440F /* Network.cpp */; };
|
||||
|
@ -1729,8 +1728,6 @@
|
|||
F76C840C1EC4E7CC00FA49E2 /* ServerList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ServerList.h; sourceTree = "<group>"; };
|
||||
F76C840D1EC4E7CC00FA49E2 /* Socket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Socket.cpp; sourceTree = "<group>"; };
|
||||
F76C840E1EC4E7CC00FA49E2 /* Socket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Socket.h; sourceTree = "<group>"; };
|
||||
F76C840F1EC4E7CC00FA49E2 /* Twitch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Twitch.cpp; sourceTree = "<group>"; };
|
||||
F76C84101EC4E7CC00FA49E2 /* Twitch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Twitch.h; sourceTree = "<group>"; };
|
||||
F76C84121EC4E7CC00FA49E2 /* BannerObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BannerObject.cpp; sourceTree = "<group>"; };
|
||||
F76C84131EC4E7CC00FA49E2 /* BannerObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BannerObject.h; sourceTree = "<group>"; };
|
||||
F76C84141EC4E7CC00FA49E2 /* EntranceObject.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = EntranceObject.cpp; sourceTree = "<group>"; };
|
||||
|
|
|
@ -50,7 +50,7 @@ The following people are not part of the development team, but have been contrib
|
|||
* (atmaxinger) - User configuration
|
||||
* (anyc) - Housecleaning, cross-platform fixes
|
||||
* Michael Steenbeek (Gymnasiast) - Cheats, RCT1 ride style, misc.
|
||||
* Miso Zmiric (mzmiric5) - Twitch integration, misc.
|
||||
* Miso Zmiric (mzmiric5) - Old Twitch integration, misc.
|
||||
* Inseok Lee (dlunch) - Load save files from command line
|
||||
* Jørn Lomax (jvlomax) - Configuration parser
|
||||
* Alexander Overvoorde (Overv) - OpenGL improvements, Steam overlay detection, various bugfixes.
|
||||
|
|
|
@ -2521,19 +2521,6 @@ STR_5160 :{POP16}{MONTH} {PUSH16}{PUSH16}{STRINGID}, Year {POP16}{COMMA16}
|
|||
STR_5161 :Date Format:
|
||||
STR_5162 :Day/Month/Year
|
||||
STR_5163 :Month/Day/Year
|
||||
STR_5164 :Twitch Channel name
|
||||
STR_5165 :Name guests after followers
|
||||
STR_5166 :{SMALLFONT}{BLACK}Name guests after channel's{NEWLINE}Twitch followers
|
||||
STR_5167 :Track follower guests
|
||||
STR_5168 :{SMALLFONT}{BLACK}Enable tracking information for guests named after channel's Twitch followers
|
||||
STR_5169 :Name guests after people in Twitch chat
|
||||
STR_5170 :{SMALLFONT}{BLACK}Name guests after people in Twitch chat
|
||||
STR_5171 :Track chat guests
|
||||
STR_5172 :{SMALLFONT}{BLACK}Enable tracking information for guests named after Twitch chat participants
|
||||
STR_5173 :Pull Twitch chat as news
|
||||
STR_5174 :{SMALLFONT}{BLACK}Use Twitch chat messages preceded by !news for in game notifications
|
||||
STR_5175 :Input the name of your Twitch channel
|
||||
STR_5176 :Enable Twitch integration
|
||||
STR_5177 :Screen mode:
|
||||
STR_5178 :{SMALLFONT}{BLACK}Show financial cheats
|
||||
STR_5179 :{SMALLFONT}{BLACK}Show guest cheats
|
||||
|
@ -2628,7 +2615,6 @@ STR_5267 :{SMALLFONT}{BLACK}Culture and Units
|
|||
STR_5268 :{SMALLFONT}{BLACK}Audio
|
||||
STR_5269 :{SMALLFONT}{BLACK}Controls and interface
|
||||
STR_5270 :{SMALLFONT}{BLACK}Miscellaneous
|
||||
STR_5271 :{SMALLFONT}{BLACK}Twitch
|
||||
STR_5272 :{SMALLFONT}{BLACK}Small Scenery
|
||||
STR_5273 :{SMALLFONT}{BLACK}Large Scenery
|
||||
STR_5274 :{SMALLFONT}{BLACK}Footpath
|
||||
|
@ -3165,7 +3151,6 @@ STR_5848 :{SMALLFONT}{BLACK}Create and manage custom title sequences
|
|||
STR_5849 :{SMALLFONT}{BLACK}Automatically place{NEWLINE}newly hired staff members
|
||||
STR_5850 :<not used anymore>
|
||||
STR_5851 :{SMALLFONT}{BLACK}Sets the default inspection interval{NEWLINE}on newly built rides
|
||||
STR_5852 :{SMALLFONT}{BLACK}Set the name of the TwitchTV channel that will be used for Twitch integration
|
||||
STR_5853 :{SMALLFONT}{BLACK}Toggle sound effects on/off
|
||||
STR_5854 :{SMALLFONT}{BLACK}Toggle ride music on/off
|
||||
STR_5855 :{SMALLFONT}{BLACK}Set regular fullscreen, borderless fullscreen{NEWLINE}or windowed display
|
||||
|
@ -3564,9 +3549,6 @@ STR_6248 :{WINDOW_COLOUR_1}Do you want to refurbish {STRINGID} for {CURRENCY}
|
|||
STR_6249 :{WINDOW_COLOUR_1}Do you want to refurbish {STRINGID}?
|
||||
STR_6250 :{WINDOW_COLOUR_1}Are you sure you want to completely demolish {STRINGID} and gain {CURRENCY}?
|
||||
STR_6251 :Ride is not empty yet
|
||||
STR_6252 :Twitch API URL
|
||||
STR_6253 :{SMALLFONT}{BLACK}Provide URL of Twitch integration API. Required in order to enable Twitch integration.
|
||||
STR_6254 :URL of Twitch integration API:
|
||||
STR_6255 :URL is not valid
|
||||
STR_6256 :Rendering effects
|
||||
STR_6257 :Glassy (translucent)
|
||||
|
|
|
@ -139,7 +139,7 @@ include_directories("${CMAKE_BINARY_DIR}/libs/include/SDL2")
|
|||
# now build app's shared lib
|
||||
include_directories(./ndk_helper
|
||||
${ANDROID_NDK}/sources/android/cpufeatures)
|
||||
add_definitions(-DDISABLE_HTTP -DDISABLE_DISCORD_RPC -DDISABLE_HTTP_TWITCH -DDISABLE_NETWORK -DDISABLE_TWITCH -DDISABLE_OPENGL -DGL_GLEXT_PROTOTYPES -D__STDC_LIMIT_MACROS -DNO_TTF -DSDL_MAIN_HANDLED)
|
||||
add_definitions(-DDISABLE_HTTP -DDISABLE_DISCORD_RPC -DDISABLE_HTTP -DDISABLE_NETWORK -DDISABLE_OPENGL -DGL_GLEXT_PROTOTYPES -D__STDC_LIMIT_MACROS -DNO_TTF -DSDL_MAIN_HANDLED)
|
||||
|
||||
# Fix SpeexDSP compilation
|
||||
add_definitions(-DHAVE_STDINT_H)
|
||||
|
|
|
@ -50,7 +50,6 @@
|
|||
#include <openrct2/interface/Chat.h>
|
||||
#include <openrct2/interface/InteractiveConsole.h>
|
||||
#include <openrct2/interface/Screenshot.h>
|
||||
#include <openrct2/network/Twitch.h>
|
||||
#include <openrct2/network/network.h>
|
||||
#include <openrct2/paint/VirtualFloor.h>
|
||||
#include <openrct2/peep/Staff.h>
|
||||
|
@ -122,8 +121,6 @@ enum FILE_MENU_DDIDX {
|
|||
// separator
|
||||
DDIDX_QUIT_TO_MENU = 10,
|
||||
DDIDX_EXIT_OPENRCT2 = 11,
|
||||
// separator
|
||||
DDIDX_ENABLE_TWITCH = 13
|
||||
};
|
||||
|
||||
enum TOP_TOOLBAR_VIEW_MENU_DDIDX {
|
||||
|
@ -329,7 +326,6 @@ static money32 selection_raise_land(uint8_t flags);
|
|||
|
||||
static ClearAction GetClearAction();
|
||||
|
||||
static bool _menuDropdownIncludesTwitch;
|
||||
static bool _landToolBlocked;
|
||||
static uint8_t _unkF64F0E;
|
||||
static int16_t _unkF64F0A;
|
||||
|
@ -449,7 +445,6 @@ static void window_top_toolbar_mousedown(rct_window* w, rct_widgetindex widgetIn
|
|||
switch (widgetIndex)
|
||||
{
|
||||
case WIDX_FILE_MENU:
|
||||
_menuDropdownIncludesTwitch = false;
|
||||
if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))
|
||||
{
|
||||
gDropdownItemsFormat[0] = STR_ABOUT;
|
||||
|
@ -494,28 +489,10 @@ static void window_top_toolbar_mousedown(rct_window* w, rct_widgetindex widgetIn
|
|||
gDropdownItemsFormat[10] = STR_QUIT_TO_MENU;
|
||||
gDropdownItemsFormat[11] = STR_EXIT_OPENRCT2;
|
||||
numItems = 12;
|
||||
|
||||
#ifndef DISABLE_TWITCH
|
||||
if (gConfigTwitch.channel != nullptr && gConfigTwitch.channel[0] != 0)
|
||||
{
|
||||
_menuDropdownIncludesTwitch = true;
|
||||
gDropdownItemsFormat[12] = STR_EMPTY;
|
||||
gDropdownItemsFormat[DDIDX_ENABLE_TWITCH] = STR_TOGGLE_OPTION;
|
||||
gDropdownItemsArgs[DDIDX_ENABLE_TWITCH] = STR_TWITCH_ENABLE;
|
||||
numItems = 14;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
window_dropdown_show_text(
|
||||
{ w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->bottom - widget->top + 1,
|
||||
w->colours[0] | 0x80, DROPDOWN_FLAG_STAY_OPEN, numItems);
|
||||
|
||||
#ifndef DISABLE_TWITCH
|
||||
if (_menuDropdownIncludesTwitch && gTwitchEnable)
|
||||
{
|
||||
dropdown_set_checked(DDIDX_ENABLE_TWITCH, true);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case WIDX_CHEATS:
|
||||
top_toolbar_init_cheats_menu(w, widget);
|
||||
|
@ -627,11 +604,6 @@ static void window_top_toolbar_dropdown(rct_window* w, rct_widgetindex widgetInd
|
|||
case DDIDX_EXIT_OPENRCT2:
|
||||
context_quit();
|
||||
break;
|
||||
#ifndef DISABLE_TWITCH
|
||||
case DDIDX_ENABLE_TWITCH:
|
||||
gTwitchEnable = !gTwitchEnable;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case WIDX_CHEATS:
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
#include "localisation/Localisation.h"
|
||||
#include "localisation/LocalisationService.h"
|
||||
#include "network/DiscordService.h"
|
||||
#include "network/Twitch.h"
|
||||
#include "network/network.h"
|
||||
#include "object/ObjectManager.h"
|
||||
#include "object/ObjectRepository.h"
|
||||
|
@ -1030,7 +1029,6 @@ namespace OpenRCT2
|
|||
}
|
||||
#endif
|
||||
|
||||
Twitch::Update();
|
||||
chat_update();
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
_scriptEngine.Update();
|
||||
|
|
|
@ -477,34 +477,6 @@ namespace Config
|
|||
writer->WriteBoolean("guest_died", model->guest_died);
|
||||
}
|
||||
|
||||
static void ReadTwitch(IIniReader* reader)
|
||||
{
|
||||
if (reader->ReadSection("twitch"))
|
||||
{
|
||||
auto model = &gConfigTwitch;
|
||||
model->api_url = reader->GetCString("api_url", nullptr);
|
||||
model->channel = reader->GetCString("channel", nullptr);
|
||||
model->enable_follower_peep_names = reader->GetBoolean("follower_peep_names", true);
|
||||
model->enable_follower_peep_tracking = reader->GetBoolean("follower_peep_tracking", false);
|
||||
model->enable_chat_peep_names = reader->GetBoolean("chat_peep_names", true);
|
||||
model->enable_chat_peep_tracking = reader->GetBoolean("chat_peep_tracking", true);
|
||||
model->enable_news = reader->GetBoolean("news", false);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteTwitch(IIniWriter* writer)
|
||||
{
|
||||
auto model = &gConfigTwitch;
|
||||
writer->WriteSection("twitch");
|
||||
writer->WriteString("api_url", model->api_url);
|
||||
writer->WriteString("channel", model->channel);
|
||||
writer->WriteBoolean("follower_peep_names", model->enable_follower_peep_names);
|
||||
writer->WriteBoolean("follower_peep_tracking", model->enable_follower_peep_tracking);
|
||||
writer->WriteBoolean("chat_peep_names", model->enable_chat_peep_names);
|
||||
writer->WriteBoolean("chat_peep_tracking", model->enable_chat_peep_tracking);
|
||||
writer->WriteBoolean("news", model->enable_news);
|
||||
}
|
||||
|
||||
static void ReadFont(IIniReader* reader)
|
||||
{
|
||||
if (reader->ReadSection("font"))
|
||||
|
@ -573,7 +545,6 @@ namespace Config
|
|||
ReadSound(reader.get());
|
||||
ReadNetwork(reader.get());
|
||||
ReadNotifications(reader.get());
|
||||
ReadTwitch(reader.get());
|
||||
ReadFont(reader.get());
|
||||
ReadPlugin(reader.get());
|
||||
return true;
|
||||
|
@ -595,7 +566,6 @@ namespace Config
|
|||
ReadSound(reader.get());
|
||||
ReadNetwork(reader.get());
|
||||
ReadNotifications(reader.get());
|
||||
ReadTwitch(reader.get());
|
||||
ReadFont(reader.get());
|
||||
ReadPlugin(reader.get());
|
||||
return true;
|
||||
|
@ -620,7 +590,6 @@ namespace Config
|
|||
WriteSound(writer.get());
|
||||
WriteNetwork(writer.get());
|
||||
WriteNotifications(writer.get());
|
||||
WriteTwitch(writer.get());
|
||||
WriteFont(writer.get());
|
||||
WritePlugin(writer.get());
|
||||
return true;
|
||||
|
@ -734,7 +703,6 @@ namespace Config
|
|||
GeneralConfiguration gConfigGeneral;
|
||||
InterfaceConfiguration gConfigInterface;
|
||||
SoundConfiguration gConfigSound;
|
||||
TwitchConfiguration gConfigTwitch;
|
||||
NetworkConfiguration gConfigNetwork;
|
||||
NotificationConfiguration gConfigNotifications;
|
||||
FontConfiguration gConfigFonts;
|
||||
|
@ -780,8 +748,6 @@ void config_release()
|
|||
SafeFree(gConfigInterface.current_theme_preset);
|
||||
SafeFree(gConfigInterface.current_title_sequence_preset);
|
||||
SafeFree(gConfigSound.device);
|
||||
SafeFree(gConfigTwitch.api_url);
|
||||
SafeFree(gConfigTwitch.channel);
|
||||
SafeFree(gConfigFonts.file_name);
|
||||
SafeFree(gConfigFonts.font_name);
|
||||
}
|
||||
|
|
|
@ -128,17 +128,6 @@ struct SoundConfiguration
|
|||
bool audio_focus;
|
||||
};
|
||||
|
||||
struct TwitchConfiguration
|
||||
{
|
||||
utf8* channel;
|
||||
utf8* api_url;
|
||||
bool enable_follower_peep_names;
|
||||
bool enable_follower_peep_tracking;
|
||||
bool enable_chat_peep_names;
|
||||
bool enable_chat_peep_tracking;
|
||||
bool enable_news;
|
||||
};
|
||||
|
||||
struct NetworkConfiguration
|
||||
{
|
||||
std::string player_name;
|
||||
|
@ -238,7 +227,6 @@ enum MEASUREMENT_FORMAT
|
|||
extern GeneralConfiguration gConfigGeneral;
|
||||
extern InterfaceConfiguration gConfigInterface;
|
||||
extern SoundConfiguration gConfigSound;
|
||||
extern TwitchConfiguration gConfigTwitch;
|
||||
extern NetworkConfiguration gConfigNetwork;
|
||||
extern NotificationConfiguration gConfigNotifications;
|
||||
extern FontConfiguration gConfigFonts;
|
||||
|
|
|
@ -1035,18 +1035,6 @@ static int32_t cc_set(InteractiveConsole& console, const arguments_t& argv)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int32_t cc_twitch([[maybe_unused]] InteractiveConsole& console, [[maybe_unused]] const arguments_t& argv)
|
||||
{
|
||||
#ifdef DISABLE_TWITCH
|
||||
console.WriteLineError("OpenRCT2 build not compiled with Twitch integration.");
|
||||
#else
|
||||
// TODO: Add some twitch commands
|
||||
// Display a message to the player for now
|
||||
console.WriteLine("To be implemented");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t cc_load_object(InteractiveConsole& console, const arguments_t& argv)
|
||||
{
|
||||
if (!argv.empty())
|
||||
|
@ -1781,7 +1769,6 @@ static constexpr const console_command console_command_table[] = {
|
|||
{ "show_limits", cc_show_limits, "Shows the map data counts and limits.", "show_limits" },
|
||||
{ "staff", cc_staff, "Staff management.", "staff <subcommand>" },
|
||||
{ "terminate", cc_terminate, "Calls std::terminate(), for testing purposes only.", "terminate" },
|
||||
{ "twitch", cc_twitch, "Twitch API", "twitch" },
|
||||
{ "variables", cc_variables, "Lists all the variables that can be used with get and sometimes set.", "variables" },
|
||||
{ "windows", cc_windows, "Lists all the windows that can be opened.", "windows" },
|
||||
{ "replay_startrecord", cc_replay_startrecord, "Starts recording a new replay.", "replay_startrecord <name> [max_ticks]"},
|
||||
|
|
|
@ -2649,19 +2649,6 @@ enum
|
|||
STR_DATE_FORMAT = 5161,
|
||||
STR_DATE_FORMAT_DAY_MONTH_YEAR = 5162,
|
||||
STR_DATE_FORMAT_MONTH_DAY_YEAR = 5163,
|
||||
STR_TWITCH_NAME = 5164,
|
||||
STR_TWITCH_PEEP_FOLLOWERS = 5165,
|
||||
STR_TWITCH_PEEP_FOLLOWERS_TIP = 5166,
|
||||
STR_TWITCH_FOLLOWERS_TRACK = 5167,
|
||||
STR_TWITCH_FOLLOWERS_TRACK_TIP = 5168,
|
||||
STR_TWITCH_PEEP_CHAT = 5169,
|
||||
STR_TWITCH_PEEP_CHAT_TIP = 5170,
|
||||
STR_TWITCH_CHAT_TRACK = 5171,
|
||||
STR_TWITCH_CHAT_TRACK_TIP = 5172,
|
||||
STR_TWITCH_CHAT_NEWS = 5173,
|
||||
STR_TWITCH_CHAT_NEWS_TIP = 5174,
|
||||
STR_TWITCH_NAME_DESC = 5175,
|
||||
STR_TWITCH_ENABLE = 5176,
|
||||
STR_FULLSCREEN_MODE = 5177,
|
||||
STR_FINANCIAL_CHEATS_TIP = 5178,
|
||||
STR_GUEST_CHEATS_TIP = 5179,
|
||||
|
@ -2756,7 +2743,6 @@ enum
|
|||
STR_OPTIONS_AUDIO_TIP = 5268,
|
||||
STR_OPTIONS_CONTROLS_AND_INTERFACE_TIP = 5269,
|
||||
STR_OPTIONS_MISCELLANEOUS_TIP = 5270,
|
||||
STR_OPTIONS_TWITCH_TIP = 5271,
|
||||
STR_CLEAR_SCENERY_REMOVE_SMALL_SCENERY_TIP = 5272,
|
||||
STR_CLEAR_SCENERY_REMOVE_LARGE_SCENERY_TIP = 5273,
|
||||
STR_CLEAR_SCENERY_REMOVE_FOOTPATHS_TIP = 5274,
|
||||
|
@ -3292,7 +3278,6 @@ enum
|
|||
STR_AUTO_STAFF_PLACEMENT_TIP = 5849,
|
||||
STR_HANDYMEN_MOW_BY_DEFAULT_TIP = 5850,
|
||||
STR_DEFAULT_INSPECTION_INTERVAL_TIP = 5851,
|
||||
STR_TWITCH_NAME_TIP = 5852,
|
||||
STR_SOUND_EFFECTS_TIP = 5853,
|
||||
STR_RIDE_MUSIC_TIP = 5854,
|
||||
STR_FULLSCREEN_MODE_TIP = 5855,
|
||||
|
@ -3756,9 +3741,6 @@ enum
|
|||
STR_DEMOLISH_RIDE_ID_MONEY = 6250,
|
||||
STR_RIDE_NOT_YET_EMPTY = 6251,
|
||||
|
||||
STR_TWITCH_API_URL = 6252,
|
||||
STR_TWITCH_API_URL_TIP = 6253,
|
||||
STR_TWITCH_API_URL_DESC = 6254,
|
||||
STR_INVALID_URL = 6255,
|
||||
|
||||
STR_EFFECTS_GROUP = 6256,
|
||||
|
|
|
@ -1,570 +0,0 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2019 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifdef DISABLE_TWITCH
|
||||
|
||||
# include "Twitch.h"
|
||||
|
||||
namespace Twitch
|
||||
{
|
||||
void Update()
|
||||
{
|
||||
}
|
||||
} // namespace Twitch
|
||||
|
||||
#else
|
||||
|
||||
# ifdef DISABLE_HTTP
|
||||
# error HTTP must be enabled to use the TWITCH functionality.
|
||||
# endif
|
||||
|
||||
# include "../Context.h"
|
||||
# include "../Game.h"
|
||||
# include "../OpenRCT2.h"
|
||||
# include "../actions/GuestSetFlagsAction.hpp"
|
||||
# include "../config/Config.h"
|
||||
# include "../core/Http.h"
|
||||
# include "../core/Json.hpp"
|
||||
# include "../core/String.hpp"
|
||||
# include "../drawing/Drawing.h"
|
||||
# include "../interface/InteractiveConsole.h"
|
||||
# include "../localisation/Localisation.h"
|
||||
# include "../management/NewsItem.h"
|
||||
# include "../peep/Peep.h"
|
||||
# include "../platform/platform.h"
|
||||
# include "../util/Util.h"
|
||||
# include "../world/Sprite.h"
|
||||
# include "Twitch.h"
|
||||
|
||||
# include <jansson.h>
|
||||
# include <memory>
|
||||
# include <vector>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
|
||||
bool gTwitchEnable = false;
|
||||
|
||||
namespace Twitch
|
||||
{
|
||||
enum
|
||||
{
|
||||
TWITCH_STATE_JOINING,
|
||||
TWITCH_STATE_JOINED,
|
||||
TWITCH_STATE_WAITING,
|
||||
TWITCH_STATE_GET_FOLLOWERS,
|
||||
TWITCH_STATE_GET_MESSAGES,
|
||||
TWITCH_STATE_LEAVING,
|
||||
TWITCH_STATE_LEFT
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
TWITCH_STATUS_OK = 200
|
||||
};
|
||||
|
||||
struct AudienceMember
|
||||
{
|
||||
const char* Name;
|
||||
bool IsFollower;
|
||||
bool IsInChat;
|
||||
bool IsMod;
|
||||
bool Exists;
|
||||
bool ShouldTrack;
|
||||
|
||||
static AudienceMember FromJson(json_t* json)
|
||||
{
|
||||
AudienceMember member = {};
|
||||
|
||||
if (!json_is_object(json))
|
||||
return member;
|
||||
json_t* name = json_object_get(json, "name");
|
||||
json_t* isFollower = json_object_get(json, "isFollower");
|
||||
json_t* isInChat = json_object_get(json, "inChat");
|
||||
json_t* isMod = json_object_get(json, "isMod");
|
||||
|
||||
member.Name = json_string_value(name);
|
||||
member.IsFollower = json_is_true(isFollower);
|
||||
member.IsInChat = json_is_true(isInChat);
|
||||
member.IsMod = json_is_true(isMod);
|
||||
member.Exists = false;
|
||||
member.ShouldTrack = false;
|
||||
return member;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The time between HTTP requests.
|
||||
* TODO Ideally, the chat message pulse should be more frequent than the followers / chat members so that news messages etc.
|
||||
* have a lower latency.
|
||||
*/
|
||||
constexpr uint32_t PulseTime = 10 * 1000;
|
||||
|
||||
static int32_t _twitchState = TWITCH_STATE_LEFT;
|
||||
static bool _twitchIdle = true;
|
||||
static uint32_t _twitchLastPulseTick = 0;
|
||||
static int32_t _twitchLastPulseOperation = 1;
|
||||
static Http::Response _twitchJsonResponse;
|
||||
|
||||
static void Join();
|
||||
static void Leave();
|
||||
static void GetFollowers();
|
||||
static void GetMessages();
|
||||
static void ParseFollowers();
|
||||
static void ParseMessages();
|
||||
static bool ShouldTrackMember(const AudienceMember* member);
|
||||
static bool ShouldMemberBeGuest(const AudienceMember* member);
|
||||
static void ManageGuestNames(std::vector<AudienceMember>& members);
|
||||
static void ParseChatMessage(const char* message);
|
||||
static void DoChatMessageNews(const char* message);
|
||||
|
||||
static bool IsTwitchEnabled()
|
||||
{
|
||||
if (!gTwitchEnable)
|
||||
return false;
|
||||
if (gScreenFlags & (~SCREEN_FLAGS_PLAYING))
|
||||
return false;
|
||||
if (String::IsNullOrEmpty(gConfigTwitch.channel))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!_twitchIdle)
|
||||
return;
|
||||
if (IsTwitchEnabled())
|
||||
{
|
||||
if (game_is_paused())
|
||||
return;
|
||||
|
||||
switch (_twitchState)
|
||||
{
|
||||
case TWITCH_STATE_LEFT:
|
||||
{
|
||||
uint32_t currentTime = platform_get_ticks();
|
||||
uint32_t timeSinceLastPulse = currentTime - _twitchLastPulseTick;
|
||||
if (_twitchLastPulseTick == 0 || timeSinceLastPulse > PulseTime)
|
||||
{
|
||||
_twitchLastPulseTick = currentTime;
|
||||
Join();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TWITCH_STATE_JOINED:
|
||||
{
|
||||
uint32_t currentTime = platform_get_ticks();
|
||||
uint32_t timeSinceLastPulse = currentTime - _twitchLastPulseTick;
|
||||
if (_twitchLastPulseTick == 0 || timeSinceLastPulse > PulseTime)
|
||||
{
|
||||
_twitchLastPulseTick = currentTime;
|
||||
_twitchLastPulseOperation = (_twitchLastPulseOperation + 1) % 2;
|
||||
switch (_twitchLastPulseOperation + TWITCH_STATE_GET_FOLLOWERS)
|
||||
{
|
||||
case TWITCH_STATE_GET_FOLLOWERS:
|
||||
GetFollowers();
|
||||
break;
|
||||
case TWITCH_STATE_GET_MESSAGES:
|
||||
if (gConfigTwitch.enable_news)
|
||||
{
|
||||
GetMessages();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TWITCH_STATE_GET_FOLLOWERS:
|
||||
ParseFollowers();
|
||||
break;
|
||||
case TWITCH_STATE_GET_MESSAGES:
|
||||
ParseMessages();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_twitchState != TWITCH_STATE_LEFT)
|
||||
{
|
||||
Leave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /leave/:join
|
||||
*/
|
||||
static void Join()
|
||||
{
|
||||
char url[256];
|
||||
|
||||
if (gConfigTwitch.api_url == nullptr || strlen(gConfigTwitch.api_url) == 0)
|
||||
{
|
||||
auto context = GetContext();
|
||||
context->WriteLine("API URL is empty! skipping request...");
|
||||
return;
|
||||
}
|
||||
snprintf(url, sizeof(url), "%s/join/%s", gConfigTwitch.api_url, gConfigTwitch.channel);
|
||||
|
||||
_twitchState = TWITCH_STATE_JOINING;
|
||||
_twitchIdle = false;
|
||||
|
||||
Http::Request request;
|
||||
request.url = url;
|
||||
request.method = Http::Method::GET;
|
||||
|
||||
Http::DoAsync(request, [](Http::Response res) {
|
||||
std::shared_ptr<void> _(nullptr, [&](...) { _twitchIdle = true; });
|
||||
|
||||
if (res.status != Http::Status::OK)
|
||||
{
|
||||
_twitchState = TWITCH_STATE_LEFT;
|
||||
GetContext()->WriteLine("Unable to connect to twitch channel.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = Json::FromString(res.body);
|
||||
json_t* jsonStatus = json_object_get(root, "status");
|
||||
if (json_is_number(jsonStatus) && json_integer_value(jsonStatus) == TWITCH_STATUS_OK)
|
||||
{
|
||||
_twitchState = TWITCH_STATE_JOINED;
|
||||
}
|
||||
else
|
||||
{
|
||||
_twitchState = TWITCH_STATE_LEFT;
|
||||
}
|
||||
|
||||
_twitchLastPulseTick = 0;
|
||||
GetContext()->WriteLine("Connected to twitch channel.");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /leave/:channel
|
||||
*/
|
||||
static void Leave()
|
||||
{
|
||||
GetContext()->WriteLine("Left twitch channel.");
|
||||
_twitchJsonResponse = {};
|
||||
_twitchState = TWITCH_STATE_LEFT;
|
||||
_twitchLastPulseTick = 0;
|
||||
gTwitchEnable = false;
|
||||
|
||||
// TODO reset all peeps with twitch flag
|
||||
|
||||
// HTTP request no longer used as it could be abused
|
||||
// char url[256];
|
||||
// snprintf(url, sizeof(url), "%sleave/%s", TwitchExtendedBaseUrl, gConfigTwitch.channel);
|
||||
// _twitchState = TWITCH_STATE_LEAVING;
|
||||
// _twitchIdle = false;
|
||||
// http_request_json_async(url, [](http_json_response * jsonResponse) -> void
|
||||
// {
|
||||
// http_request_json_dispose(jsonResponse);
|
||||
// _twitchState = TWITCH_STATE_LEFT;
|
||||
// _twitchIdle = true;
|
||||
//
|
||||
// GetContext()->WriteLine("Left twitch channel.");
|
||||
// });
|
||||
}
|
||||
|
||||
static void Get(const std::string& requestFormat, int operation)
|
||||
{
|
||||
char url[256];
|
||||
|
||||
if (gConfigTwitch.api_url == nullptr || strlen(gConfigTwitch.api_url) == 0)
|
||||
{
|
||||
auto context = GetContext();
|
||||
context->WriteLine("API URL is empty! skipping request...");
|
||||
return;
|
||||
}
|
||||
snprintf(url, sizeof(url), requestFormat.c_str(), gConfigTwitch.api_url, gConfigTwitch.channel);
|
||||
|
||||
_twitchState = TWITCH_STATE_WAITING;
|
||||
_twitchIdle = false;
|
||||
|
||||
Http::DoAsync({ url }, [operation](Http::Response res) {
|
||||
std::shared_ptr<void> _(nullptr, [&](...) { _twitchIdle = true; });
|
||||
|
||||
if (res.status != Http::Status::OK)
|
||||
{
|
||||
_twitchState = TWITCH_STATE_JOINED;
|
||||
return;
|
||||
}
|
||||
|
||||
_twitchJsonResponse = res;
|
||||
_twitchState = operation;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /channel/:channel/audience
|
||||
*/
|
||||
static void GetFollowers()
|
||||
{
|
||||
Get("%s/channel/%s/audience", TWITCH_STATE_GET_FOLLOWERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /channel/:channel/messages
|
||||
*/
|
||||
static void GetMessages()
|
||||
{
|
||||
Get("%s/channel/%s/messages", TWITCH_STATE_GET_MESSAGES);
|
||||
}
|
||||
|
||||
static void ParseFollowers()
|
||||
{
|
||||
json_t* root = Json::FromString(_twitchJsonResponse.body);
|
||||
if (json_is_array(root))
|
||||
{
|
||||
std::vector<AudienceMember> members;
|
||||
|
||||
size_t audienceCount = json_array_size(root);
|
||||
for (size_t i = 0; i < audienceCount; i++)
|
||||
{
|
||||
json_t* jsonAudienceMember = json_array_get(root, i);
|
||||
auto member = AudienceMember::FromJson(jsonAudienceMember);
|
||||
if (!String::IsNullOrEmpty(member.Name))
|
||||
{
|
||||
member.ShouldTrack = ShouldTrackMember(&member);
|
||||
if (ShouldMemberBeGuest(&member))
|
||||
{
|
||||
members.push_back(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ManageGuestNames(members);
|
||||
}
|
||||
|
||||
_twitchJsonResponse = {};
|
||||
_twitchState = TWITCH_STATE_JOINED;
|
||||
|
||||
gfx_invalidate_screen();
|
||||
}
|
||||
|
||||
static void ParseMessages()
|
||||
{
|
||||
json_t* root = Json::FromString(_twitchJsonResponse.body);
|
||||
if (json_is_array(root))
|
||||
{
|
||||
size_t messageCount = json_array_size(root);
|
||||
for (size_t i = 0; i < messageCount; i++)
|
||||
{
|
||||
json_t* jsonMessage = json_array_get(root, i);
|
||||
if (!json_is_object(jsonMessage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
json_t* jsonText = json_object_get(jsonMessage, "message");
|
||||
const char* text = json_string_value(jsonText);
|
||||
ParseChatMessage(text);
|
||||
}
|
||||
}
|
||||
|
||||
_twitchJsonResponse = {};
|
||||
_twitchState = TWITCH_STATE_JOINED;
|
||||
}
|
||||
|
||||
static bool ShouldTrackMember(const AudienceMember* member)
|
||||
{
|
||||
if (member->IsInChat && gConfigTwitch.enable_chat_peep_tracking)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (member->IsFollower && gConfigTwitch.enable_follower_peep_tracking)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ShouldMemberBeGuest(const AudienceMember* member)
|
||||
{
|
||||
if (gConfigTwitch.enable_chat_peep_names && member->IsInChat)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (gConfigTwitch.enable_follower_peep_names && member->IsFollower)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ManageGuestNames(std::vector<AudienceMember>& members)
|
||||
{
|
||||
// Check what followers are already in the park
|
||||
uint16_t spriteIndex;
|
||||
Peep* peep;
|
||||
FOR_ALL_GUESTS (spriteIndex, peep)
|
||||
{
|
||||
if (peep->name != nullptr)
|
||||
{
|
||||
uint8_t args[32]{};
|
||||
char buffer[256]{};
|
||||
peep->FormatNameTo(args);
|
||||
format_string(buffer, sizeof(buffer), STR_STRINGID, args);
|
||||
|
||||
AudienceMember* member = nullptr;
|
||||
for (AudienceMember& m : members)
|
||||
{
|
||||
if (String::Equals(buffer, m.Name, true))
|
||||
{
|
||||
member = &m;
|
||||
m.Exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (peep->peep_flags & PEEP_FLAGS_TWITCH)
|
||||
{
|
||||
if (member == nullptr)
|
||||
{
|
||||
// Member no longer peep name worthy
|
||||
uint32_t flags = peep->peep_flags & ~(PEEP_FLAGS_TRACKING | PEEP_FLAGS_TWITCH);
|
||||
|
||||
auto guestSetFlagsAction = GuestSetFlagsAction(peep->sprite_index, flags);
|
||||
GameActions::Execute(&guestSetFlagsAction);
|
||||
|
||||
// TODO set peep name back to number / real name
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t flags = peep->peep_flags;
|
||||
if (member->ShouldTrack)
|
||||
{
|
||||
flags |= (PEEP_FLAGS_TRACKING);
|
||||
}
|
||||
else if (!member->ShouldTrack)
|
||||
{
|
||||
flags &= ~(PEEP_FLAGS_TRACKING);
|
||||
}
|
||||
if (flags != peep->peep_flags)
|
||||
{
|
||||
auto guestSetFlagsAction = GuestSetFlagsAction(peep->sprite_index, flags);
|
||||
GameActions::Execute(&guestSetFlagsAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (member != nullptr && !(peep->peep_flags & PEEP_FLAGS_LEAVING_PARK))
|
||||
{
|
||||
// Peep with same name already exists but not twitch
|
||||
uint32_t flags = peep->peep_flags | PEEP_FLAGS_TWITCH;
|
||||
if (member->ShouldTrack)
|
||||
{
|
||||
flags |= PEEP_FLAGS_TRACKING;
|
||||
}
|
||||
auto guestSetFlagsAction = GuestSetFlagsAction(peep->sprite_index, flags);
|
||||
GameActions::Execute(&guestSetFlagsAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename non-named peeps to followers that aren't currently in the park.
|
||||
if (!members.empty())
|
||||
{
|
||||
size_t memberIndex = SIZE_MAX;
|
||||
FOR_ALL_GUESTS (spriteIndex, peep)
|
||||
{
|
||||
size_t originalMemberIndex = memberIndex;
|
||||
for (size_t i = memberIndex + 1; i < members.size(); i++)
|
||||
{
|
||||
if (!members[i].Exists)
|
||||
{
|
||||
memberIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (originalMemberIndex == memberIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
AudienceMember* member = &members[memberIndex];
|
||||
if (peep->name == nullptr && !(peep->peep_flags & PEEP_FLAGS_LEAVING_PARK))
|
||||
{
|
||||
// Rename peep and add flags
|
||||
auto memLen = std::strlen(member->Name) + 1;
|
||||
peep->name = static_cast<char*>(std::malloc(memLen));
|
||||
if (peep->name != nullptr)
|
||||
{
|
||||
std::memcpy(peep->name, member->Name, memLen);
|
||||
|
||||
uint32_t flags = peep->peep_flags | PEEP_FLAGS_TWITCH;
|
||||
if (member->ShouldTrack)
|
||||
{
|
||||
flags |= PEEP_FLAGS_TRACKING;
|
||||
}
|
||||
|
||||
auto guestSetFlagsAction = GuestSetFlagsAction(peep->sprite_index, flags);
|
||||
GameActions::Execute(&guestSetFlagsAction);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Peep still yet to be found for member
|
||||
memberIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char* strskipwhitespace(const char* str)
|
||||
{
|
||||
while (*str == ' ' || *str == '\t')
|
||||
{
|
||||
str++;
|
||||
}
|
||||
return const_cast<char*>(str);
|
||||
}
|
||||
|
||||
static void ParseChatMessage(const char* message)
|
||||
{
|
||||
message = strskipwhitespace(message);
|
||||
if (!String::StartsWith(message, "!"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip '!'
|
||||
message++;
|
||||
|
||||
// Check that command is "news"
|
||||
const char *ch, *cmd;
|
||||
for (ch = message, cmd = "news"; *cmd != '\0'; ++ch, ++cmd)
|
||||
{
|
||||
if (*ch != *cmd)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isspace(*ch))
|
||||
return;
|
||||
|
||||
ch = strskipwhitespace(ch);
|
||||
DoChatMessageNews(ch);
|
||||
}
|
||||
|
||||
static void DoChatMessageNews(const char* message)
|
||||
{
|
||||
if (gConfigTwitch.enable_news)
|
||||
{
|
||||
utf8 buffer[256];
|
||||
buffer[0] = static_cast<utf8>(static_cast<uint8_t>(FORMAT_TOPAZ));
|
||||
safe_strcpy(buffer + 1, message, sizeof(buffer) - 1);
|
||||
|
||||
utf8_remove_formatting(buffer, false);
|
||||
|
||||
// TODO Create a new news item type for twitch which has twitch icon
|
||||
news_item_add_to_queue_raw(NEWS_ITEM_BLANK, buffer, 0);
|
||||
}
|
||||
}
|
||||
} // namespace Twitch
|
||||
|
||||
#endif
|
|
@ -1,22 +0,0 @@
|
|||
/*****************************************************************************
|
||||
* Copyright (c) 2014-2019 OpenRCT2 developers
|
||||
*
|
||||
* For a complete list of all authors, please refer to contributors.md
|
||||
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
||||
*
|
||||
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef _TWITCH_H_
|
||||
#define _TWITCH_H_
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
extern bool gTwitchEnable;
|
||||
|
||||
namespace Twitch
|
||||
{
|
||||
void Update();
|
||||
}
|
||||
|
||||
#endif
|
|
@ -382,7 +382,7 @@ enum PeepFlags : uint32_t
|
|||
PEEP_FLAGS_INTAMIN_DEPRECATED = (1 << 27), // Used to make the peep think "I'm so excited - It's an Intamin ride!" while
|
||||
// riding on a Intamin ride.
|
||||
PEEP_FLAGS_HERE_WE_ARE = (1 << 28), // Makes the peep think "...and here we are on X!" while riding a ride
|
||||
PEEP_FLAGS_TWITCH = (1u << 31), // Added for twitch integration
|
||||
PEEP_FLAGS_TWITCH_DEPRECATED = (1u << 31), // Formerly used for twitch integration
|
||||
};
|
||||
|
||||
enum PeepNextFlags
|
||||
|
|
|
@ -745,7 +745,6 @@ enum
|
|||
SPR_G2_FASTFORWARD = SPR_G2_BEGIN + 2,
|
||||
SPR_G2_SPEED_ARROW = SPR_G2_BEGIN + 3,
|
||||
SPR_G2_HYPER_ARROW = SPR_G2_BEGIN + 4,
|
||||
SPR_G2_TAB_TWITCH = SPR_G2_BEGIN + 5,
|
||||
SPR_G2_TAB_LAND = SPR_G2_BEGIN + 6,
|
||||
|
||||
SPR_G2_PLACEHOLDER = SPR_G2_BEGIN + 7,
|
||||
|
|
Loading…
Reference in New Issue