|
2 | 2 | // Use of this source code is governed by the MIT license, a copy of which can |
3 | 3 | // be found in the LICENSE file. |
4 | 4 |
|
5 | | -import { CommandBuilder } from 'components/command_manager/command_builder.js'; |
| 5 | +import { CommandBuilder } from 'components/commands/command_builder.js'; |
6 | 6 | import { Feature } from 'components/feature_manager/feature.js'; |
7 | 7 |
|
8 | | -import InteriorList from 'features/debug/interiors.js'; |
9 | | - |
10 | | -// Utility function to return |value| in |len| digits, left-padded with zeros when necessary. |
11 | | -function leftPad(value, len = 2) { |
12 | | - return ('0' + value).slice(-2); |
13 | | -} |
14 | | - |
15 | 8 | // The debug feature offers useful tools for administrators to debug the server or the Las Venturas |
16 | 9 | // Playground gamemode itself. It's driven by a number of in-game comments. |
17 | | -class Debug extends Feature { |
18 | | - constructor() { |
19 | | - super(); |
20 | | - |
21 | | - // /serverfps |
22 | | - server.deprecatedCommandManager.buildCommand('serverfps') |
23 | | - .restrict(Player.LEVEL_ADMINISTRATOR) |
24 | | - .build(this.__proto__.serverFrameCounter.bind(this)); |
25 | | - |
26 | | - // /trace [seconds] |
27 | | - server.deprecatedCommandManager.buildCommand('trace') |
28 | | - .restrict(Player.LEVEL_MANAGEMENT) |
29 | | - .parameters([{ name: 'seconds', type: CommandBuilder.NUMBER_PARAMETER }]) |
30 | | - .build(this.__proto__.captureTrace.bind(this)); |
31 | | - |
32 | | - // /sound [id] |
33 | | - server.deprecatedCommandManager.buildCommand('sound') |
34 | | - .restrict(Player.LEVEL_MANAGEMENT) |
35 | | - .parameters([{ name: 'sound', type: CommandBuilder.NUMBER_PARAMETER }]) |
36 | | - .build(this.__proto__.playSound.bind(this)); |
37 | | - |
38 | | - // /int [id] |
39 | | - server.deprecatedCommandManager.buildCommand('int') |
40 | | - .restrict(Player.LEVEL_ADMINISTRATOR) |
41 | | - .parameters([ { name: 'id', type: CommandBuilder.NUMBER_PARAMETER } ]) |
42 | | - .build(this.__proto__.int.bind(this)); |
43 | | - |
44 | | - // /cam |
45 | | - server.deprecatedCommandManager.buildCommand('cam') |
46 | | - .restrict(Player.LEVEL_ADMINISTRATOR) |
47 | | - .build(this.__proto__.cam.bind(this)); |
48 | | - |
49 | | - // /eval |
50 | | - server.deprecatedCommandManager.buildCommand('eval') |
51 | | - .restrict(Player.LEVEL_MANAGEMENT) |
52 | | - .parameters([ { name: 'command', type: CommandBuilder.SENTENCE_PARAMETER } ]) |
53 | | - .build(this.__proto__.eval.bind(this)); |
54 | | - |
55 | | - // /idlers |
56 | | - server.deprecatedCommandManager.buildCommand('idlers') |
57 | | - .restrict(Player.LEVEL_ADMINISTRATOR) |
58 | | - .build(this.__proto__.idlers.bind(this)); |
59 | | - |
60 | | - // /pattach |
61 | | - server.deprecatedCommandManager.buildCommand('pattach') |
62 | | - .restrict(Player.LEVEL_MANAGEMENT) |
63 | | - .parameters([ { name: 'player', type: CommandBuilder.PLAYER_PARAMETER }, |
64 | | - { name: 'model', type: CommandBuilder.NUMBER_PARAMETER }, |
65 | | - { name: 'x', type: CommandBuilder.NUMBER_PARAMETER }, |
66 | | - { name: 'y', type: CommandBuilder.NUMBER_PARAMETER }, |
67 | | - { name: 'z', type: CommandBuilder.NUMBER_PARAMETER } ]) |
68 | | - .build(this.__proto__.attach.bind(this)); |
69 | | - |
70 | | - // /pdetach |
71 | | - server.deprecatedCommandManager.buildCommand('pdetach') |
72 | | - .restrict(Player.LEVEL_MANAGEMENT) |
73 | | - .parameters([ { name: 'player', type: CommandBuilder.PLAYER_PARAMETER } ]) |
74 | | - .build(this.__proto__.detach.bind(this)); |
75 | | - |
76 | | - this.attachedObjects_ = new Map(); |
77 | | - |
78 | | - server.playerManager.addObserver(this); |
79 | | - } |
80 | | - |
81 | | - // Displays the number of FPS the server was able to handle since the last call to this command. |
82 | | - serverFrameCounter(player) { |
83 | | - let stats = global.frameCounter(), |
84 | | - message = Message.format(Message.DEBUG_FRAME_COUNTER, stats.fps, stats.duration / 1000); |
85 | | - |
86 | | - player.sendMessage(message) |
87 | | - } |
88 | | - |
89 | | - // Captures a trace for |seconds| (which must be in range of [0, 300]) and stores it to the |
90 | | - // trace.log.[DMYHIS] file in the server's root directory. |
91 | | - captureTrace(player, seconds) { |
92 | | - if (typeof seconds !== 'number' || seconds < 0 || seconds > 300) { |
93 | | - player.sendMessage(Message.DEBUG_TRACE_INVALID_TIME); |
94 | | - return; |
| 10 | +export default class Debug extends Feature { |
| 11 | + constructor() { |
| 12 | + super(); |
| 13 | + |
| 14 | + // /serverfps |
| 15 | + server.commandManager.buildCommand('serverfps') |
| 16 | + .description(`Displays the current performance of the server.`) |
| 17 | + .restrict(Player.LEVEL_ADMINISTRATOR) |
| 18 | + .build(Debug.prototype.onServerFrameCounterCommand.bind(this)); |
| 19 | + |
| 20 | + // /eval |
| 21 | + server.commandManager.buildCommand('eval') |
| 22 | + .description(`Evaluates JavaScript code on the server.`) |
| 23 | + .restrict(Player.LEVEL_MANAGEMENT) |
| 24 | + .parameters([ { name: 'command', type: CommandBuilder.kTypeText } ]) |
| 25 | + .build(Debug.prototype.onEvaluateCommand.bind(this)); |
| 26 | + |
| 27 | + // /idlers |
| 28 | + server.commandManager.buildCommand('idlers') |
| 29 | + .description('Displays a list of players who are currently idle.') |
| 30 | + .restrict(Player.LEVEL_ADMINISTRATOR) |
| 31 | + .build(Debug.prototype.idlers.bind(this)); |
95 | 32 | } |
96 | 33 |
|
97 | | - let date = new Date(), |
98 | | - filename = 'trace.log.'; |
99 | | - |
100 | | - filename += date.getUTCFullYear() + leftPad(date.getUTCMonth() + 1) + leftPad(date.getUTCDate()); |
101 | | - filename += leftPad(date.getUTCHours()) + leftPad(date.getUTCMinutes()) + |
102 | | - leftPad(date.getUTCSeconds()); |
103 | | - |
104 | | - global.startTrace(); |
105 | | - wait(seconds * 1000).then(() => { |
106 | | - global.stopTrace(filename); |
107 | | - |
108 | | - if (player.isConnected()) |
109 | | - player.sendMessage(Message.DEBUG_TRACE_FINISHED); |
110 | | - }); |
111 | | - |
112 | | - player.sendMessage(Message.DEBUG_TRACE_STARTED); |
113 | | - } |
| 34 | + // Displays the number of FPS the server was able to handle since the last call to this command. |
| 35 | + onServerFrameCounterCommand(player) { |
| 36 | + const statistics = global.frameCounter(); |
114 | 37 |
|
115 | | - // Plays |soundId| for all in-game players. |
116 | | - playSound(player, soundId) { |
117 | | - server.playerManager.forEach(p => p.playSound(soundId)); |
118 | | - } |
119 | | - |
120 | | - // Teleports the player to the interior identified by |id|. Only available to administrators. |
121 | | - int(player, id) { |
122 | | - if (id < 0 || id >= InteriorList.length) { |
123 | | - player.sendMessage(Message.COMMAND_USAGE, '/int [0-' + (InteriorList.length - 1) + ']'); |
124 | | - return; |
| 38 | + player.sendMessage(Message.DEBUG_FRAME_COUNTER, statistics.fps, statistics.duration / 1000); |
125 | 39 | } |
126 | 40 |
|
127 | | - const interiorInfo = InteriorList[id]; |
128 | | - |
129 | | - player.position = interiorInfo.position; |
130 | | - player.rotation = interiorInfo.rotation; |
131 | | - player.interiorId = interiorInfo.interior; |
132 | | - |
133 | | - player.sendMessage(Message.COMMAND_SUCCESS, 'You have been teleported to ' + interiorInfo.name); |
134 | | - } |
135 | | - |
136 | | - // Has the player spectate their current position. |
137 | | - cam(player) { |
138 | | - player.setSpectating(true); |
139 | | - |
140 | | - const position = player.position; |
141 | | - const camera = new Vector(position.x, position.y, position.z + 10); |
142 | | - |
143 | | - player.setCamera(camera, position); |
144 | | - |
145 | | - wait(5000).then(() => player.setSpectating(false)); |
146 | | - } |
| 41 | + // Evaluates the |command| on behalf of |player|. |
| 42 | + onEvaluateCommand(player, command) { |
| 43 | + console.log('[JavaScript] Evaluating: ' + command); |
147 | 44 |
|
148 | | - // Evaluates the |command| on behalf of |player|. |
149 | | - eval(player, command) { |
150 | | - console.log('[JavaScript] Evaluating: ' + command); |
| 45 | + // Utility functions that are often used with the `/eval` command. |
| 46 | + const cm = server.commandManager; |
| 47 | + const fm = server.featureManager; |
| 48 | + const p = playerId => server.playerManager.getById(playerId); |
| 49 | + const vid = playerId => pawnInvoke('GetPlayerVehicleID', 'i', playerId); |
| 50 | + const v = playerId => server.playerManager.getById(playerId).vehicle; |
151 | 51 |
|
152 | | - // Utility functions that are often used with the `/eval` command. |
153 | | - const cm = server.deprecatedCommandManager; |
154 | | - const fm = server.featureManager; |
155 | | - const p = playerId => server.playerManager.getById(playerId); |
156 | | - const vid = playerId => pawnInvoke('GetPlayerVehicleID', 'i', playerId); |
157 | | - const v = playerId => server.playerManager.getById(playerId).vehicle; |
| 52 | + try { |
| 53 | + const output = '' + JSON.stringify(eval(command), null, ' '); |
| 54 | + const lines = output.split('\n'); |
158 | 55 |
|
159 | | - try { |
160 | | - const output = '' + JSON.stringify(eval(command), null, ' '); |
161 | | - const lines = output.split('\n'); |
| 56 | + for (let i = 0; i < Math.min(8, lines.length); ++i) |
| 57 | + player.sendMessage('>> ' + lines[i]); |
162 | 58 |
|
163 | | - for (let i = 0; i < Math.min(8, lines.length); ++i) { |
164 | | - player.sendMessage('>> ' + lines[i]); |
165 | | - } |
| 59 | + if (lines.length > 8) |
| 60 | + player.sendMessage('>> Omitted ' + (lines.length - 8) + ' lines.'); |
166 | 61 |
|
167 | | - if (lines.length > 8) |
168 | | - player.sendMessage('>> Omitted ' + (lines.length - 8) + ' lines.'); |
169 | | - } catch (error) { |
170 | | - player.sendMessage('>> ' + error.name + ': ' + error.message); |
| 62 | + } catch (error) { |
| 63 | + player.sendMessage('>> ' + error.name + ': ' + error.message); |
| 64 | + } |
171 | 65 | } |
172 | | - } |
173 | 66 |
|
174 | | - // Lists the players who currently have minimized their game. |
175 | | - idlers(player) { |
176 | | - const idlers = []; |
| 67 | + // Lists the players who currently have minimized their game. |
| 68 | + idlers(player) { |
| 69 | + const idlers = []; |
177 | 70 |
|
178 | | - for (const player of server.playerManager) { |
179 | | - if (player.isMinimized()) |
180 | | - idlers.push(player.name); |
181 | | - } |
182 | | - |
183 | | - if (!idlers.length) |
184 | | - player.sendMessage('Nobody minimized their game.'); |
185 | | - else |
186 | | - player.sendMessage('Minimized: ' + idlers.sort().join(', ')); |
187 | | - } |
188 | | - |
189 | | - // Called when the |player| disconnects from the server. |
190 | | - onPlayerDisconnect(player) { |
191 | | - if (!this.attachedObjects_.has(player)) |
192 | | - return; |
193 | | - |
194 | | - for (const objectId of this.attachedObjects_.get(player)) |
195 | | - pawnInvoke('DestroyObject', 'i', objectId); |
196 | | - |
197 | | - this.attachedObjects_.delete(player); |
198 | | - } |
199 | | - |
200 | | - // Attaches the object with |modelId| to the |subject| at the given offset. |
201 | | - attach(player, subject, modelId, x, y, z) { |
202 | | - if (!this.attachedObjects_.has(subject)) |
203 | | - this.attachedObjects_.set(subject, new Set()); |
204 | | - |
205 | | - const objectId = pawnInvoke('CreateObject', 'ifffffff', modelId, 0, 0, 0, 0, 0, 0, 0); |
206 | | - this.attachedObjects_.get(subject).add(objectId); |
| 71 | + for (const player of server.playerManager) { |
| 72 | + if (player.isMinimized()) |
| 73 | + idlers.push(player.name); |
| 74 | + } |
207 | 75 |
|
208 | | - pawnInvoke('AttachObjectToPlayer', 'iiffffff', objectId, subject.id, x, y, z, 0, 0, 0); |
209 | | - |
210 | | - player.sendMessage('Done!'); |
211 | | - } |
212 | | - |
213 | | - // Removes all attached objects from the |subject|. |
214 | | - detach(player, subject) { |
215 | | - this.onPlayerDisconnect(subject); |
216 | | - |
217 | | - player.sendMessage('Done!'); |
218 | | - } |
219 | | - |
220 | | - dispose() { |
221 | | - server.playerManager.removeObserver(this); |
222 | | - |
223 | | - for (const [player, objects] of this.attachedObjects_) { |
224 | | - for (const objectId of objects) |
225 | | - pawnInvoke('DestroyObject', 'i', objectId); |
| 76 | + if (!idlers.length) |
| 77 | + player.sendMessage('Nobody minimized their game.'); |
| 78 | + else |
| 79 | + player.sendMessage('Minimized: ' + idlers.sort().join(', ')); |
226 | 80 | } |
227 | 81 |
|
228 | | - this.attachedObjects_.clear(); |
229 | | - this.attachedObjects_ = null; |
230 | | - |
231 | | - server.deprecatedCommandManager.removeCommand('serverfps') |
232 | | - server.deprecatedCommandManager.removeCommand('trace') |
233 | | - server.deprecatedCommandManager.removeCommand('sound') |
234 | | - server.deprecatedCommandManager.removeCommand('int') |
235 | | - server.deprecatedCommandManager.removeCommand('cam') |
236 | | - server.deprecatedCommandManager.removeCommand('eval') |
237 | | - server.deprecatedCommandManager.removeCommand('idlers') |
238 | | - server.deprecatedCommandManager.removeCommand('pattach') |
239 | | - server.deprecatedCommandManager.removeCommand('pdetach') |
240 | | - } |
| 82 | + dispose() { |
| 83 | + server.commandManager.removeCommand('serverfps'); |
| 84 | + server.commandManager.removeCommand('eval'); |
| 85 | + server.commandManager.removeCommand('idlers'); |
| 86 | + } |
241 | 87 | } |
242 | | - |
243 | | -export default Debug; |
0 commit comments