aboutsummaryrefslogtreecommitdiff
path: root/src/chat.h
blob: 0b98e4d3c0507118b6485a70754998ad36ba128b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#pragma once

#include <string>
#include <vector>
#include <list>

#include "irrlichttypes.h"
#include "util/enriched_string.h"
#include "settings.h"

// Chat console related classes

struct ChatLine
{
	// age in seconds
	f32 age = 0.0f;
	// name of sending player, or empty if sent by server
	EnrichedString name;
	// message text
	EnrichedString text;

	ChatLine(const std::wstring &a_name, const std::wstring &a_text):
		name(a_name),
		text(a_text)
	{
	}

	ChatLine(const EnrichedString &a_name, const EnrichedString &a_text):
		name(a_name),
		text(a_text)
	{
	}
};

struct ChatFormattedFragment
{
	// text string
	EnrichedString text;
	// starting column
	u32 column;
	// formatting
	//u8 bold:1;
};

struct ChatFormattedLine
{
	// Array of text fragments
	std::vector<ChatFormattedFragment> fragments;
	// true if first line of one formatted ChatLine
	bool first;
};

class ChatBuffer
{
public:
	ChatBuffer(u32 scrollback);
	~ChatBuffer() = default;

	// Append chat line
	// Removes oldest chat line if scrollback size is reached
	void addLine(const std::wstring &name, const std::wstring &text);

	// Remove all chat lines
	void clear();

	// Get number of lines currently in buffer.
	u32 getLineCount() const;
	// Get reference to i-th chat line.
	const ChatLine& getLine(u32 index) const;

	// Increase each chat line's age by dtime.
	void step(f32 dtime);
	// Delete oldest N chat lines.
	void deleteOldest(u32 count);
	// Delete lines older than maxAge.
	void deleteByAge(f32 maxAge);

	// Get number of rows, 0 if reformat has not been called yet.
	u32 getRows() const;
	// Update console size and reformat all formatted lines.
	void reformat(u32 cols, u32 rows);
	// Get formatted line for a given row (0 is top of screen).
	// Only valid after reformat has been called at least once
	const ChatFormattedLine& getFormattedLine(u32 row) const;
	// Scrolling in formatted buffer (relative)
	// positive rows == scroll up, negative rows == scroll down
	void scroll(s32 rows);
	// Scrolling in formatted buffer (absolute)
	void scrollAbsolute(s32 scroll);
	// Scroll to bottom of buffer (newest)
	void scrollBottom();
	// Scroll to top of buffer (oldest)
	void scrollTop();

	// Format a chat line for the given number of columns.
	// Appends the formatted lines to the destination array and
	// returns the number of formatted lines.
	u32 formatChatLine(const ChatLine& line, u32 cols,
			std::vector<ChatFormattedLine>& destination) const;

	void resize(u32 scrollback);
protected:
	s32 getTopScrollPos() const;
	s32 getBottomScrollPos() const;

private:
	// Scrollback size
	u32 m_scrollback;
	// Array of unformatted chat lines
	std::vector<ChatLine> m_unformatted;

	// Number of character columns in console
	u32 m_cols = 0;
	// Number of character rows in console
	u32 m_rows = 0;
	// Scroll position (console's top line index into m_formatted)
	s32 m_scroll = 0;
	// Array of formatted lines
	std::vector<ChatFormattedLine> m_formatted;
	// Empty formatted line, for error returns
	ChatFormattedLine m_empty_formatted_line;
};

class ChatPrompt
{
public:
	ChatPrompt(const std::wstring &prompt, u32 history_limit);
	~ChatPrompt() = default;

	// Input character or string
	void input(wchar_t ch);
	void input(const std::wstring &str);

	// Add a string to the history
	void addToHistory(const std::wstring &line);

	// Get current line
	std::wstring getLine() const { return m_line; }

	// Get section of line that is currently selected
	std::wstring getSelection() const { return m_line.substr(m_cursor, m_cursor_len); }

	// Clear the current line
	void clear();

	// Replace the current line with the given text
	std::wstring replace(const std::wstring &line);

	// Select previous command from history
	void historyPrev();
	// Select next command from history
	void historyNext();

	// Nick completion
	void nickCompletion(const std::list<std::string>& names, bool backwards);

	// Update console size and reformat the visible portion of the prompt
	void reformat(u32 cols);
	// Get visible portion of the prompt.
	std::wstring getVisiblePortion() const;
	// Get cursor position (relative to visible portion). -1 if invalid
	s32 getVisibleCursorPosition() const;
	// Get length of cursor selection
	s32 getCursorLength() const { return m_cursor_len; }

	// Cursor operations
	enum CursorOp {
		CURSOROP_MOVE,
		CURSOROP_SELECT,
		CURSOROP_DELETE
	};

	// Cursor operation direction
	enum CursorOpDir {
		CURSOROP_DIR_LEFT,
		CURSOROP_DIR_RIGHT
	};

	// Cursor operation scope
	enum CursorOpScope {
		CURSOROP_SCOPE_CHARACTER,
		CURSOROP_SCOPE_WORD,
		CURSOROP_SCOPE_LINE,
		CURSOROP_SCOPE_SELECTION
	};

	// Cursor operation
	// op specifies whether it's a move or delete operation
	// dir specifies whether the operation goes left or right
	// scope specifies how far the operation will reach (char/word/line)
	// Examples:
	//   cursorOperation(CURSOROP_MOVE, CURSOROP_DIR_RIGHT, CURSOROP_SCOPE_LINE)
	//     moves the cursor to the end of the line.
	//   cursorOperation(CURSOROP_DELETE, CURSOROP_DIR_LEFT, CURSOROP_SCOPE_WORD)
	//     deletes the word to the left of the cursor.
	void cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope scope);

protected:
	// set m_view to ensure that 0 <= m_view <= m_cursor < m_view + m_cols
	// if line can be fully shown, set m_view to zero
	// else, also ensure m_view <= m_line.size() + 1 - m_cols
	void clampView();

private:
	// Prompt prefix
	std::wstring m_prompt = L"";
	// Currently edited line
	std::wstring m_line = L"";
	// History buffer
	std::vector<std::wstring> m_history;
	// History index (0 <= m_history_index <= m_history.size())
	u32 m_history_index = 0;
	// Maximum number of history entries
	u32 m_history_limit;

	// Number of columns excluding columns reserved for the prompt
	s32 m_cols = 0;
	// Start of visible portion (index into m_line)
	s32 m_view = 0;
	// Cursor (index into m_line)
	s32 m_cursor = 0;
	// Cursor length (length of selected portion of line)
	s32 m_cursor_len = 0;

	// Last nick completion start (index into m_line)
	s32 m_nick_completion_start = 0;
	// Last nick completion start (index into m_line)
	s32 m_nick_completion_end = 0;
};

class ChatBackend
{
public:
	ChatBackend();
	~ChatBackend() = default;

	// Add chat message
	void addMessage(const std::wstring &name, std::wstring text);
	// Parse and add unparsed chat message
	void addUnparsedMessage(std::wstring line);

	// Get the console buffer
	ChatBuffer& getConsoleBuffer();
	// Get the recent messages buffer
	ChatBuffer& getRecentBuffer();
	// Concatenate all recent messages
	EnrichedString getRecentChat() const;
	// Get the console prompt
	ChatPrompt& getPrompt();

	// Reformat all buffers
	void reformat(u32 cols, u32 rows);

	// Clear all recent messages
	void clearRecentChat();

	// Age recent messages
	void step(float dtime);

	// Scrolling
	void scroll(s32 rows);
	void scrollPageDown();
	void scrollPageUp();

	// Resize recent buffer based on settings
	void applySettings();

private:
	ChatBuffer m_console_buffer;
	ChatBuffer m_recent_buffer;
	ChatPrompt m_prompt;
};
Environment *env, bool *pos_exists) const; }; struct ServerPlayingSound { ServerSoundParams params; SimpleSoundSpec spec; std::unordered_set<session_t> clients; // peer ids }; struct MinimapMode { MinimapType type = MINIMAP_TYPE_OFF; std::string label; u16 size = 0; std::string texture; u16 scale = 1; }; // structure for everything getClientInfo returns, for convenience struct ClientInfo { ClientState state; Address addr; u32 uptime; u8 ser_vers; u16 prot_vers; u8 major, minor, patch; std::string vers_string, lang_code; }; class Server : public con::PeerHandler, public MapEventReceiver, public IGameDef { public: /* NOTE: Every public method should be thread-safe */ Server( const std::string &path_world, const SubgameSpec &gamespec, bool simple_singleplayer_mode, Address bind_addr, bool dedicated, ChatInterface *iface = nullptr, std::string *on_shutdown_errmsg = nullptr ); ~Server(); DISABLE_CLASS_COPY(Server); void start(); void stop(); // This is mainly a way to pass the time to the server. // Actual processing is done in an another thread. void step(float dtime); // This is run by ServerThread and does the actual processing void AsyncRunStep(bool initial_step=false); void Receive(); PlayerSAO* StageTwoClientInit(session_t peer_id); /* * Command Handlers */ void handleCommand(NetworkPacket* pkt); void handleCommand_Null(NetworkPacket* pkt) {}; void handleCommand_Deprecated(NetworkPacket* pkt); void handleCommand_Init(NetworkPacket* pkt); void handleCommand_Init2(NetworkPacket* pkt); void handleCommand_ModChannelJoin(NetworkPacket *pkt); void handleCommand_ModChannelLeave(NetworkPacket *pkt); void handleCommand_ModChannelMsg(NetworkPacket *pkt); void handleCommand_RequestMedia(NetworkPacket* pkt); void handleCommand_ClientReady(NetworkPacket* pkt); void handleCommand_GotBlocks(NetworkPacket* pkt); void handleCommand_PlayerPos(NetworkPacket* pkt); void handleCommand_DeletedBlocks(NetworkPacket* pkt); void handleCommand_InventoryAction(NetworkPacket* pkt); void handleCommand_ChatMessage(NetworkPacket* pkt); void handleCommand_Damage(NetworkPacket* pkt); void handleCommand_PlayerItem(NetworkPacket* pkt); void handleCommand_Respawn(NetworkPacket* pkt); void handleCommand_Interact(NetworkPacket* pkt); void handleCommand_RemovedSounds(NetworkPacket* pkt); void handleCommand_NodeMetaFields(NetworkPacket* pkt); void handleCommand_InventoryFields(NetworkPacket* pkt); void handleCommand_FirstSrp(NetworkPacket* pkt); void handleCommand_SrpBytesA(NetworkPacket* pkt); void handleCommand_SrpBytesM(NetworkPacket* pkt); void handleCommand_HaveMedia(NetworkPacket *pkt); void ProcessData(NetworkPacket *pkt); void Send(NetworkPacket *pkt); void Send(session_t peer_id, NetworkPacket *pkt); // Helper for handleCommand_PlayerPos and handleCommand_Interact void process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, NetworkPacket *pkt); // Both setter and getter need no envlock, // can be called freely from threads void setTimeOfDay(u32 time); /* Shall be called with the environment locked. This is accessed by the map, which is inside the environment, so it shouldn't be a problem. */ void onMapEditEvent(const MapEditEvent &event); // Connection must be locked when called std::string getStatusString(); inline double getUptime() const { return m_uptime_counter->get(); } // read shutdown state inline bool isShutdownRequested() const { return m_shutdown_state.is_requested; } // request server to shutdown void requestShutdown(const std::string &msg, bool reconnect, float delay = 0.0f); // Returns -1 if failed, sound handle on success // Envlock s32 playSound(const SimpleSoundSpec &spec, const ServerSoundParams &params, bool ephemeral=false); void stopSound(s32 handle); void fadeSound(s32 handle, float step, float gain); // Envlock std::set<std::string> getPlayerEffectivePrivs(const std::string &name); bool checkPriv(const std::string &name, const std::string &priv); void reportPrivsModified(const std::string &name=""); // ""=all void reportInventoryFormspecModified(const std::string &name); void reportFormspecPrependModified(const std::string &name); void setIpBanned(const std::string &ip, const std::string &name); void unsetIpBanned(const std::string &ip_or_name); std::string getBanDescription(const std::string &ip_or_name); void notifyPlayer(const char *name, const std::wstring &msg); void notifyPlayers(const std::wstring &msg); void spawnParticle(const std::string &playername, const ParticleParameters &p); u32 addParticleSpawner(const ParticleSpawnerParameters &p, ServerActiveObject *attached, const std::string &playername); void deleteParticleSpawner(const std::string &playername, u32 id); bool dynamicAddMedia(std::string filepath, u32 token, const std::string &to_player, bool ephemeral); ServerInventoryManager *getInventoryMgr() const { return m_inventory_mgr.get(); } void sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id); // Envlock and conlock should be locked when using scriptapi ServerScripting *getScriptIface(){ return m_script; } // actions: time-reversed list // Return value: success/failure bool rollbackRevertActions(const std::list<RollbackAction> &actions, std::list<std::string> *log); // IGameDef interface // Under envlock virtual IItemDefManager* getItemDefManager(); virtual const NodeDefManager* getNodeDefManager(); virtual ICraftDefManager* getCraftDefManager(); virtual u16 allocateUnknownNodeId(const std::string &name); IRollbackManager *getRollbackManager() { return m_rollback; } virtual EmergeManager *getEmergeManager() { return m_emerge; } IWritableItemDefManager* getWritableItemDefManager(); NodeDefManager* getWritableNodeDefManager(); IWritableCraftDefManager* getWritableCraftDefManager(); virtual const std::vector<ModSpec> &getMods() const; virtual const ModSpec* getModSpec(const std::string &modname) const; void getModNames(std::vector<std::string> &modlist); std::string getBuiltinLuaPath(); virtual std::string getWorldPath() const { return m_path_world; } virtual std::string getModStoragePath() const; inline bool isSingleplayer() { return m_simple_singleplayer_mode; } inline void setAsyncFatalError(const std::string &error) { m_async_fatal_error.set(error); } inline void setAsyncFatalError(const LuaError &e) { setAsyncFatalError(std::string("Lua: ") + e.what()); } bool showFormspec(const char *name, const std::string &formspec, const std::string &formname); Map & getMap() { return m_env->getMap(); } ServerEnvironment & getEnv() { return *m_env; } v3f findSpawnPos(); u32 hudAdd(RemotePlayer *player, HudElement *element); bool hudRemove(RemotePlayer *player, u32 id); bool hudChange(RemotePlayer *player, u32 id, HudElementStat stat, void *value); bool hudSetFlags(RemotePlayer *player, u32 flags, u32 mask); bool hudSetHotbarItemcount(RemotePlayer *player, s32 hotbar_itemcount); void hudSetHotbarImage(RemotePlayer *player, const std::string &name); void hudSetHotbarSelectedImage(RemotePlayer *player, const std::string &name); Address getPeerAddress(session_t peer_id); void setLocalPlayerAnimations(RemotePlayer *player, v2s32 animation_frames[4], f32 frame_speed); void setPlayerEyeOffset(RemotePlayer *player, const v3f &first, const v3f &third); void setSky(RemotePlayer *player, const SkyboxParams &params); void setSun(RemotePlayer *player, const SunParams &params); void setMoon(RemotePlayer *player, const MoonParams &params); void setStars(RemotePlayer *player, const StarParams &params); void setClouds(RemotePlayer *player, const CloudParams &params); void overrideDayNightRatio(RemotePlayer *player, bool do_override, float brightness); /* con::PeerHandler implementation. */ void peerAdded(con::Peer *peer); void deletingPeer(con::Peer *peer, bool timeout); void DenySudoAccess(session_t peer_id); void DenyAccessVerCompliant(session_t peer_id, u16 proto_ver, AccessDeniedCode reason, const std::string &str_reason = "", bool reconnect = false); void DenyAccess(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason = ""); void acceptAuth(session_t peer_id, bool forSudoMode); void DenyAccess_Legacy(session_t peer_id, const std::wstring &reason); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); bool getClientInfo(session_t peer_id, ClientInfo &ret); void printToConsoleOnly(const std::string &text); void SendPlayerHPOrDie(PlayerSAO *player, const PlayerHPChangeReason &reason); void SendPlayerBreath(PlayerSAO *sao); void SendInventory(PlayerSAO *playerSAO, bool incremental); void SendMovePlayer(session_t peer_id); void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerFov(session_t peer_id); void SendMinimapModes(session_t peer_id, std::vector<MinimapMode> &modes, size_t wanted_mode); void sendDetachedInventories(session_t peer_id, bool incremental); virtual bool registerModStorage(ModMetadata *storage); virtual void unregisterModStorage(const std::string &name); bool joinModChannel(const std::string &channel); bool leaveModChannel(const std::string &channel); bool sendModChannelMessage(const std::string &channel, const std::string &message); ModChannel *getModChannel(const std::string &channel); // Send block to specific player only bool SendBlock(session_t peer_id, const v3s16 &blockpos); // Get or load translations for a language Translations *getTranslationLanguage(const std::string &lang_code); // Bind address Address m_bind_addr; // Environment mutex (envlock) std::mutex m_env_mutex; private: friend class EmergeThread; friend class RemoteClient; friend class TestServerShutdownState; struct ShutdownState { friend class TestServerShutdownState; public: bool is_requested = false; bool should_reconnect = false; std::string message; void reset(); void trigger(float delay, const std::string &msg, bool reconnect); void tick(float dtime, Server *server); std::wstring getShutdownTimerMessage() const; bool isTimerRunning() const { return m_timer > 0.0f; } private: float m_timer = 0.0f; }; struct PendingDynamicMediaCallback { std::string filename; // only set if media entry and file is to be deleted float expiry_timer; std::unordered_set<session_t> waiting_players; }; void init(); void SendMovement(session_t peer_id); void SendHP(session_t peer_id, u16 hp); void SendBreath(session_t peer_id, u16 breath); void SendAccessDenied(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason, bool reconnect = false); void SendAccessDenied_Legacy(session_t peer_id, const std::wstring &reason); void SendDeathscreen(session_t peer_id, bool set_camera_point_target, v3f camera_point_target); void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version); void SendNodeDef(session_t peer_id, const NodeDefManager *nodedef, u16 protocol_version); /* mark blocks not sent for all clients */ void SetBlocksNotSent(std::map<v3s16, MapBlock *>& block); virtual void SendChatMessage(session_t peer_id, const ChatMessage &message); void SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed); void SendPlayerHP(session_t peer_id); void SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4], f32 animation_speed); void SendEyeOffset(session_t peer_id, v3f first, v3f third); void SendPlayerPrivileges(session_t peer_id); void SendPlayerInventoryFormspec(session_t peer_id); void SendPlayerFormspecPrepend(session_t peer_id); void SendShowFormspecMessage(session_t peer_id, const std::string &formspec, const std::string &formname); void SendHUDAdd(session_t peer_id, u32 id, HudElement *form); void SendHUDRemove(session_t peer_id, u32 id); void SendHUDChange(session_t peer_id, u32 id, HudElementStat stat, void *value); void SendHUDSetFlags(session_t peer_id, u32 flags, u32 mask); void SendHUDSetParam(session_t peer_id, u16 param, const std::string &value); void SendSetSky(session_t peer_id, const SkyboxParams &params); void SendSetSun(session_t peer_id, const SunParams &params); void SendSetMoon(session_t peer_id, const MoonParams &params); void SendSetStars(session_t peer_id, const StarParams &params); void SendCloudParams(session_t peer_id, const CloudParams &params); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); /* Send a node removal/addition event to all clients except ignore_id. Additionally, if far_players!=NULL, players further away than far_d_nodes are ignored and their peer_ids are added to far_players */ // Envlock and conlock should be locked when calling these void sendRemoveNode(v3s16 p, std::unordered_set<u16> *far_players = nullptr, float far_d_nodes = 100); void sendAddNode(v3s16 p, MapNode n, std::unordered_set<u16> *far_players = nullptr, float far_d_nodes = 100, bool remove_metadata = true); void sendMetadataChanged(const std::list<v3s16> &meta_updates, float far_d_nodes = 100); // Environment and Connection must be locked when called void SendBlockNoLock(session_t peer_id, MapBlock *block, u8 ver, u16 net_proto_version); // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); bool addMediaFile(const std::string &filename, const std::string &filepath, std::string *filedata = nullptr, std::string *digest = nullptr); void fillMediaCache(); void sendMediaAnnouncement(session_t peer_id, const std::string &lang_code); void sendRequestedMedia(session_t peer_id, const std::vector<std::string> &tosend); void stepPendingDynMediaCallbacks(float dtime); // Adds a ParticleSpawner on peer with peer_id (PEER_ID_INEXISTENT == all) void SendAddParticleSpawner(session_t peer_id, u16 protocol_version, const ParticleSpawnerParameters &p, u16 attached_id, u32 id); void SendDeleteParticleSpawner(session_t peer_id, u32 id); // Spawns particle on peer with peer_id (PEER_ID_INEXISTENT == all) void SendSpawnParticle(session_t peer_id, u16 protocol_version, const ParticleParameters &p); void SendActiveObjectRemoveAdd(RemoteClient *client, PlayerSAO *playersao); void SendActiveObjectMessages(session_t peer_id, const std::string &datas, bool reliable = true); void SendCSMRestrictionFlags(session_t peer_id);