clockwerk-opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections.Generic; |
||
30 | using System.IO; |
||
31 | using System.Reflection; |
||
32 | using System.Net; |
||
33 | |||
34 | using log4net; |
||
35 | using Nini.Config; |
||
36 | |||
37 | using OpenMetaverse; |
||
38 | using Mono.Addins; |
||
39 | |||
40 | using OpenSim.Data; |
||
41 | using OpenSim.Framework; |
||
42 | using OpenSim.Framework.Console; |
||
43 | using OpenSim.Region.CoreModules.Framework.InterfaceCommander; |
||
44 | using OpenSim.Region.CoreModules.World.Terrain.FileLoaders; |
||
45 | using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes; |
||
46 | using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; |
||
47 | using OpenSim.Region.Framework.Interfaces; |
||
48 | using OpenSim.Region.Framework.Scenes; |
||
49 | |||
50 | namespace OpenSim.Region.CoreModules.World.Terrain |
||
51 | { |
||
52 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "TerrainModule")] |
||
53 | public class TerrainModule : INonSharedRegionModule, ICommandableModule, ITerrainModule |
||
54 | { |
||
55 | #region StandardTerrainEffects enum |
||
56 | |||
57 | /// <summary> |
||
58 | /// A standard set of terrain brushes and effects recognised by viewers |
||
59 | /// </summary> |
||
60 | public enum StandardTerrainEffects : byte |
||
61 | { |
||
62 | Flatten = 0, |
||
63 | Raise = 1, |
||
64 | Lower = 2, |
||
65 | Smooth = 3, |
||
66 | Noise = 4, |
||
67 | Revert = 5, |
||
68 | |||
69 | // Extended brushes |
||
70 | Erode = 255, |
||
71 | Weather = 254, |
||
72 | Olsen = 253 |
||
73 | } |
||
74 | |||
75 | #endregion |
||
76 | |||
77 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
78 | |||
79 | #pragma warning disable 414 |
||
80 | private static readonly string LogHeader = "[TERRAIN MODULE]"; |
||
81 | #pragma warning restore 414 |
||
82 | |||
83 | private readonly Commander m_commander = new Commander("terrain"); |
||
84 | |||
85 | private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects = |
||
86 | new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>(); |
||
87 | |||
88 | private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>(); |
||
89 | |||
90 | private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects = |
||
91 | new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>(); |
||
92 | |||
93 | private ITerrainChannel m_channel; |
||
94 | private Dictionary<string, ITerrainEffect> m_plugineffects; |
||
95 | private ITerrainChannel m_revert; |
||
96 | private Scene m_scene; |
||
97 | private volatile bool m_tainted; |
||
98 | private readonly Stack<LandUndoState> m_undo = new Stack<LandUndoState>(5); |
||
99 | |||
100 | private String m_InitialTerrain = "pinhead-island"; |
||
101 | |||
102 | // If true, send terrain patch updates to clients based on their view distance |
||
103 | private bool m_sendTerrainUpdatesByViewDistance = true; |
||
104 | |||
105 | // Class to keep the per client collection of terrain patches that must be sent. |
||
106 | // A patch is set to 'true' meaning it should be sent to the client. Once the |
||
107 | // patch packet is queued to the client, the bit for that patch is set to 'false'. |
||
108 | private class PatchUpdates |
||
109 | { |
||
110 | private bool[,] updated; // for each patch, whether it needs to be sent to this client |
||
111 | private int updateCount; // number of patches that need to be sent |
||
112 | public ScenePresence Presence; // a reference to the client to send to |
||
113 | public PatchUpdates(TerrainData terrData, ScenePresence pPresence) |
||
114 | { |
||
115 | updated = new bool[terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize]; |
||
116 | updateCount = 0; |
||
117 | Presence = pPresence; |
||
118 | // Initially, send all patches to the client |
||
119 | SetAll(true); |
||
120 | } |
||
121 | // Returns 'true' if there are any patches marked for sending |
||
122 | public bool HasUpdates() |
||
123 | { |
||
124 | return (updateCount > 0); |
||
125 | } |
||
126 | public void SetByXY(int x, int y, bool state) |
||
127 | { |
||
128 | this.SetByPatch(x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, state); |
||
129 | } |
||
130 | public bool GetByPatch(int patchX, int patchY) |
||
131 | { |
||
132 | return updated[patchX, patchY]; |
||
133 | } |
||
134 | public void SetByPatch(int patchX, int patchY, bool state) |
||
135 | { |
||
136 | bool prevState = updated[patchX, patchY]; |
||
137 | if (!prevState && state) |
||
138 | updateCount++; |
||
139 | if (prevState && !state) |
||
140 | updateCount--; |
||
141 | updated[patchX, patchY] = state; |
||
142 | } |
||
143 | public void SetAll(bool state) |
||
144 | { |
||
145 | updateCount = 0; |
||
146 | for (int xx = 0; xx < updated.GetLength(0); xx++) |
||
147 | for (int yy = 0; yy < updated.GetLength(1); yy++) |
||
148 | updated[xx, yy] = state; |
||
149 | if (state) |
||
150 | updateCount = updated.GetLength(0) * updated.GetLength(1); |
||
151 | } |
||
152 | // Logically OR's the terrain data's patch taint map into this client's update map. |
||
153 | public void SetAll(TerrainData terrData) |
||
154 | { |
||
155 | if (updated.GetLength(0) != (terrData.SizeX / Constants.TerrainPatchSize) |
||
156 | || updated.GetLength(1) != (terrData.SizeY / Constants.TerrainPatchSize)) |
||
157 | { |
||
158 | throw new Exception( |
||
159 | String.Format("{0} PatchUpdates.SetAll: patch array not same size as terrain. arr=<{1},{2}>, terr=<{3},{4}>", |
||
160 | LogHeader, updated.GetLength(0), updated.GetLength(1), |
||
161 | terrData.SizeX / Constants.TerrainPatchSize, terrData.SizeY / Constants.TerrainPatchSize) |
||
162 | ); |
||
163 | } |
||
164 | for (int xx = 0; xx < terrData.SizeX; xx += Constants.TerrainPatchSize) |
||
165 | { |
||
166 | for (int yy = 0; yy < terrData.SizeY; yy += Constants.TerrainPatchSize) |
||
167 | { |
||
168 | // Only set tainted. The patch bit may be set if the patch was to be sent later. |
||
169 | if (terrData.IsTaintedAt(xx, yy, false)) |
||
170 | { |
||
171 | this.SetByXY(xx, yy, true); |
||
172 | } |
||
173 | } |
||
174 | } |
||
175 | } |
||
176 | } |
||
177 | |||
178 | // The flags of which terrain patches to send for each of the ScenePresence's |
||
179 | private Dictionary<UUID, PatchUpdates> m_perClientPatchUpdates = new Dictionary<UUID, PatchUpdates>(); |
||
180 | |||
181 | /// <summary> |
||
182 | /// Human readable list of terrain file extensions that are supported. |
||
183 | /// </summary> |
||
184 | private string m_supportedFileExtensions = ""; |
||
185 | |||
186 | //For terrain save-tile file extensions |
||
187 | private string m_supportFileExtensionsForTileSave = ""; |
||
188 | |||
189 | #region ICommandableModule Members |
||
190 | |||
191 | public ICommander CommandInterface |
||
192 | { |
||
193 | get { return m_commander; } |
||
194 | } |
||
195 | |||
196 | #endregion |
||
197 | |||
198 | #region INonSharedRegionModule Members |
||
199 | |||
200 | /// <summary> |
||
201 | /// Creates and initialises a terrain module for a region |
||
202 | /// </summary> |
||
203 | /// <param name="scene">Region initialising</param> |
||
204 | /// <param name="config">Config for the region</param> |
||
205 | public void Initialise(IConfigSource config) |
||
206 | { |
||
207 | IConfig terrainConfig = config.Configs["Terrain"]; |
||
208 | if (terrainConfig != null) |
||
209 | { |
||
210 | m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain); |
||
211 | m_sendTerrainUpdatesByViewDistance = terrainConfig.GetBoolean("SendTerrainUpdatesByViewDistance", m_sendTerrainUpdatesByViewDistance); |
||
212 | } |
||
213 | } |
||
214 | |||
215 | public void AddRegion(Scene scene) |
||
216 | { |
||
217 | m_scene = scene; |
||
218 | |||
219 | // Install terrain module in the simulator |
||
220 | lock (m_scene) |
||
221 | { |
||
222 | if (m_scene.Heightmap == null) |
||
223 | { |
||
224 | m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX, |
||
225 | (int)m_scene.RegionInfo.RegionSizeY, |
||
226 | (int)m_scene.RegionInfo.RegionSizeZ); |
||
227 | m_scene.Heightmap = m_channel; |
||
228 | UpdateRevertMap(); |
||
229 | } |
||
230 | else |
||
231 | { |
||
232 | m_channel = m_scene.Heightmap; |
||
233 | UpdateRevertMap(); |
||
234 | } |
||
235 | |||
236 | m_scene.RegisterModuleInterface<ITerrainModule>(this); |
||
237 | m_scene.EventManager.OnNewClient += EventManager_OnNewClient; |
||
238 | m_scene.EventManager.OnClientClosed += EventManager_OnClientClosed; |
||
239 | m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; |
||
240 | m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick; |
||
241 | m_scene.EventManager.OnFrame += EventManager_OnFrame; |
||
242 | } |
||
243 | |||
244 | InstallDefaultEffects(); |
||
245 | LoadPlugins(); |
||
246 | |||
247 | // Generate user-readable extensions list |
||
248 | string supportedFilesSeparator = ""; |
||
249 | string supportedFilesSeparatorForTileSave = ""; |
||
250 | |||
251 | m_supportFileExtensionsForTileSave = ""; |
||
252 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
253 | { |
||
254 | m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")"; |
||
255 | supportedFilesSeparator = ", "; |
||
256 | |||
257 | //For terrain save-tile file extensions |
||
258 | if (loader.Value.SupportsTileSave() == true) |
||
259 | { |
||
260 | m_supportFileExtensionsForTileSave += supportedFilesSeparatorForTileSave + loader.Key + " (" + loader.Value + ")"; |
||
261 | supportedFilesSeparatorForTileSave = ", "; |
||
262 | } |
||
263 | } |
||
264 | } |
||
265 | |||
266 | public void RegionLoaded(Scene scene) |
||
267 | { |
||
268 | //Do this here to give file loaders time to initialize and |
||
269 | //register their supported file extensions and file formats. |
||
270 | InstallInterfaces(); |
||
271 | } |
||
272 | |||
273 | public void RemoveRegion(Scene scene) |
||
274 | { |
||
275 | lock (m_scene) |
||
276 | { |
||
277 | // remove the commands |
||
278 | m_scene.UnregisterModuleCommander(m_commander.Name); |
||
279 | // remove the event-handlers |
||
280 | m_scene.EventManager.OnFrame -= EventManager_OnFrame; |
||
281 | m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick; |
||
282 | m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole; |
||
283 | m_scene.EventManager.OnClientClosed -= EventManager_OnClientClosed; |
||
284 | m_scene.EventManager.OnNewClient -= EventManager_OnNewClient; |
||
285 | // remove the interface |
||
286 | m_scene.UnregisterModuleInterface<ITerrainModule>(this); |
||
287 | } |
||
288 | } |
||
289 | |||
290 | public void Close() |
||
291 | { |
||
292 | } |
||
293 | |||
294 | public Type ReplaceableInterface |
||
295 | { |
||
296 | get { return null; } |
||
297 | } |
||
298 | |||
299 | public string Name |
||
300 | { |
||
301 | get { return "TerrainModule"; } |
||
302 | } |
||
303 | |||
304 | #endregion |
||
305 | |||
306 | #region ITerrainModule Members |
||
307 | |||
308 | public void UndoTerrain(ITerrainChannel channel) |
||
309 | { |
||
310 | m_channel = channel; |
||
311 | } |
||
312 | |||
313 | /// <summary> |
||
314 | /// Loads a terrain file from disk and installs it in the scene. |
||
315 | /// </summary> |
||
316 | /// <param name="filename">Filename to terrain file. Type is determined by extension.</param> |
||
317 | public void LoadFromFile(string filename) |
||
318 | { |
||
319 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
320 | { |
||
321 | if (filename.EndsWith(loader.Key)) |
||
322 | { |
||
323 | lock (m_scene) |
||
324 | { |
||
325 | try |
||
326 | { |
||
327 | ITerrainChannel channel = loader.Value.LoadFile(filename); |
||
328 | if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY) |
||
329 | { |
||
330 | // TerrainChannel expects a RegionSize x RegionSize map, currently |
||
331 | throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}", |
||
332 | m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY)); |
||
333 | } |
||
334 | m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height); |
||
335 | m_scene.Heightmap = channel; |
||
336 | m_channel = channel; |
||
337 | UpdateRevertMap(); |
||
338 | } |
||
339 | catch (NotImplementedException) |
||
340 | { |
||
341 | m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + |
||
342 | " parser does not support file loading. (May be save only)"); |
||
343 | throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); |
||
344 | } |
||
345 | catch (FileNotFoundException) |
||
346 | { |
||
347 | m_log.Error( |
||
348 | "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)"); |
||
349 | throw new TerrainException( |
||
350 | String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename)); |
||
351 | } |
||
352 | catch (ArgumentException e) |
||
353 | { |
||
354 | m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message); |
||
355 | throw new TerrainException( |
||
356 | String.Format("Unable to load heightmap: {0}", e.Message)); |
||
357 | } |
||
358 | } |
||
359 | m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); |
||
360 | return; |
||
361 | } |
||
362 | } |
||
363 | |||
364 | m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); |
||
365 | throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); |
||
366 | } |
||
367 | |||
368 | /// <summary> |
||
369 | /// Saves the current heightmap to a specified file. |
||
370 | /// </summary> |
||
371 | /// <param name="filename">The destination filename</param> |
||
372 | public void SaveToFile(string filename) |
||
373 | { |
||
374 | try |
||
375 | { |
||
376 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
377 | { |
||
378 | if (filename.EndsWith(loader.Key)) |
||
379 | { |
||
380 | loader.Value.SaveFile(filename, m_channel); |
||
381 | m_log.InfoFormat("[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename); |
||
382 | return; |
||
383 | } |
||
384 | } |
||
385 | } |
||
386 | catch (IOException ioe) |
||
387 | { |
||
388 | m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message)); |
||
389 | } |
||
390 | |||
391 | m_log.ErrorFormat( |
||
392 | "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}", |
||
393 | m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions); |
||
394 | } |
||
395 | |||
396 | /// <summary> |
||
397 | /// Loads a terrain file from the specified URI |
||
398 | /// </summary> |
||
399 | /// <param name="filename">The name of the terrain to load</param> |
||
400 | /// <param name="pathToTerrainHeightmap">The URI to the terrain height map</param> |
||
401 | public void LoadFromStream(string filename, Uri pathToTerrainHeightmap) |
||
402 | { |
||
403 | LoadFromStream(filename, URIFetch(pathToTerrainHeightmap)); |
||
404 | } |
||
405 | |||
406 | public void LoadFromStream(string filename, Stream stream) |
||
407 | { |
||
408 | LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream); |
||
409 | } |
||
410 | |||
411 | /// <summary> |
||
412 | /// Loads a terrain file from a stream and installs it in the scene. |
||
413 | /// </summary> |
||
414 | /// <param name="filename">Filename to terrain file. Type is determined by extension.</param> |
||
415 | /// <param name="stream"></param> |
||
416 | public void LoadFromStream(string filename, Vector3 displacement, |
||
417 | float radianRotation, Vector2 rotationDisplacement, Stream stream) |
||
418 | { |
||
419 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
420 | { |
||
421 | if (filename.EndsWith(loader.Key)) |
||
422 | { |
||
423 | lock (m_scene) |
||
424 | { |
||
425 | try |
||
426 | { |
||
427 | ITerrainChannel channel = loader.Value.LoadStream(stream); |
||
428 | m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement); |
||
429 | UpdateRevertMap(); |
||
430 | } |
||
431 | catch (NotImplementedException) |
||
432 | { |
||
433 | m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value + |
||
434 | " parser does not support file loading. (May be save only)"); |
||
435 | throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value)); |
||
436 | } |
||
437 | } |
||
438 | |||
439 | m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully"); |
||
440 | return; |
||
441 | } |
||
442 | } |
||
443 | m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format."); |
||
444 | throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename)); |
||
445 | } |
||
446 | |||
447 | private static Stream URIFetch(Uri uri) |
||
448 | { |
||
449 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); |
||
450 | |||
451 | // request.Credentials = credentials; |
||
452 | |||
453 | request.ContentLength = 0; |
||
454 | request.KeepAlive = false; |
||
455 | |||
456 | WebResponse response = request.GetResponse(); |
||
457 | Stream file = response.GetResponseStream(); |
||
458 | |||
459 | if (response.ContentLength == 0) |
||
460 | throw new Exception(String.Format("{0} returned an empty file", uri.ToString())); |
||
461 | |||
462 | // return new BufferedStream(file, (int) response.ContentLength); |
||
463 | return new BufferedStream(file, 1000000); |
||
464 | } |
||
465 | |||
466 | /// <summary> |
||
467 | /// Modify Land |
||
468 | /// </summary> |
||
469 | /// <param name="pos">Land-position (X,Y,0)</param> |
||
470 | /// <param name="size">The size of the brush (0=small, 1=medium, 2=large)</param> |
||
471 | /// <param name="action">0=LAND_LEVEL, 1=LAND_RAISE, 2=LAND_LOWER, 3=LAND_SMOOTH, 4=LAND_NOISE, 5=LAND_REVERT</param> |
||
472 | /// <param name="agentId">UUID of script-owner</param> |
||
473 | public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId) |
||
474 | { |
||
475 | float duration = 0.25f; |
||
476 | if (action == 0) |
||
477 | duration = 4.0f; |
||
478 | |||
479 | client_OnModifyTerrain(user, (float)pos.Z, duration, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId); |
||
480 | } |
||
481 | |||
482 | /// <summary> |
||
483 | /// Saves the current heightmap to a specified stream. |
||
484 | /// </summary> |
||
485 | /// <param name="filename">The destination filename. Used here only to identify the image type</param> |
||
486 | /// <param name="stream"></param> |
||
487 | public void SaveToStream(string filename, Stream stream) |
||
488 | { |
||
489 | try |
||
490 | { |
||
491 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
492 | { |
||
493 | if (filename.EndsWith(loader.Key)) |
||
494 | { |
||
495 | loader.Value.SaveStream(stream, m_channel); |
||
496 | return; |
||
497 | } |
||
498 | } |
||
499 | } |
||
500 | catch (NotImplementedException) |
||
501 | { |
||
502 | m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented."); |
||
503 | throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented")); |
||
504 | } |
||
505 | } |
||
506 | |||
507 | // Someone diddled terrain outside the normal code paths. Set the taintedness for all clients. |
||
508 | // ITerrainModule.TaintTerrain() |
||
509 | public void TaintTerrain () |
||
510 | { |
||
511 | lock (m_perClientPatchUpdates) |
||
512 | { |
||
513 | // Set the flags for all clients so the tainted patches will be sent out |
||
514 | foreach (PatchUpdates pups in m_perClientPatchUpdates.Values) |
||
515 | { |
||
516 | pups.SetAll(m_scene.Heightmap.GetTerrainData()); |
||
517 | } |
||
518 | } |
||
519 | } |
||
520 | |||
521 | // ITerrainModule.PushTerrain() |
||
522 | public void PushTerrain(IClientAPI pClient) |
||
523 | { |
||
524 | if (m_sendTerrainUpdatesByViewDistance) |
||
525 | { |
||
526 | ScenePresence presence = m_scene.GetScenePresence(pClient.AgentId); |
||
527 | if (presence != null) |
||
528 | { |
||
529 | lock (m_perClientPatchUpdates) |
||
530 | { |
||
531 | PatchUpdates pups; |
||
532 | if (!m_perClientPatchUpdates.TryGetValue(pClient.AgentId, out pups)) |
||
533 | { |
||
534 | // There is a ScenePresence without a send patch map. Create one. |
||
535 | pups = new PatchUpdates(m_scene.Heightmap.GetTerrainData(), presence); |
||
536 | m_perClientPatchUpdates.Add(presence.UUID, pups); |
||
537 | } |
||
538 | pups.SetAll(true); |
||
539 | } |
||
540 | } |
||
541 | } |
||
542 | else |
||
543 | { |
||
544 | // The traditional way is to call into the protocol stack to send them all. |
||
545 | pClient.SendLayerData(new float[10]); |
||
546 | } |
||
547 | } |
||
548 | |||
549 | #region Plugin Loading Methods |
||
550 | |||
551 | private void LoadPlugins() |
||
552 | { |
||
553 | m_plugineffects = new Dictionary<string, ITerrainEffect>(); |
||
554 | LoadPlugins(Assembly.GetCallingAssembly()); |
||
555 | string plugineffectsPath = "Terrain"; |
||
556 | |||
557 | // Load the files in the Terrain/ dir |
||
558 | if (!Directory.Exists(plugineffectsPath)) |
||
559 | return; |
||
560 | |||
561 | string[] files = Directory.GetFiles(plugineffectsPath); |
||
562 | foreach (string file in files) |
||
563 | { |
||
564 | m_log.Info("Loading effects in " + file); |
||
565 | try |
||
566 | { |
||
567 | Assembly library = Assembly.LoadFrom(file); |
||
568 | LoadPlugins(library); |
||
569 | } |
||
570 | catch (BadImageFormatException) |
||
571 | { |
||
572 | } |
||
573 | } |
||
574 | } |
||
575 | |||
576 | private void LoadPlugins(Assembly library) |
||
577 | { |
||
578 | foreach (Type pluginType in library.GetTypes()) |
||
579 | { |
||
580 | try |
||
581 | { |
||
582 | if (pluginType.IsAbstract || pluginType.IsNotPublic) |
||
583 | continue; |
||
584 | |||
585 | string typeName = pluginType.Name; |
||
586 | |||
587 | if (pluginType.GetInterface("ITerrainEffect", false) != null) |
||
588 | { |
||
589 | ITerrainEffect terEffect = (ITerrainEffect)Activator.CreateInstance(library.GetType(pluginType.ToString())); |
||
590 | |||
591 | InstallPlugin(typeName, terEffect); |
||
592 | } |
||
593 | else if (pluginType.GetInterface("ITerrainLoader", false) != null) |
||
594 | { |
||
595 | ITerrainLoader terLoader = (ITerrainLoader)Activator.CreateInstance(library.GetType(pluginType.ToString())); |
||
596 | m_loaders[terLoader.FileExtension] = terLoader; |
||
597 | m_log.Info("L ... " + typeName); |
||
598 | } |
||
599 | } |
||
600 | catch (AmbiguousMatchException) |
||
601 | { |
||
602 | } |
||
603 | } |
||
604 | } |
||
605 | |||
606 | public void InstallPlugin(string pluginName, ITerrainEffect effect) |
||
607 | { |
||
608 | lock (m_plugineffects) |
||
609 | { |
||
610 | if (!m_plugineffects.ContainsKey(pluginName)) |
||
611 | { |
||
612 | m_plugineffects.Add(pluginName, effect); |
||
613 | m_log.Info("E ... " + pluginName); |
||
614 | } |
||
615 | else |
||
616 | { |
||
617 | m_plugineffects[pluginName] = effect; |
||
618 | m_log.Info("E ... " + pluginName + " (Replaced)"); |
||
619 | } |
||
620 | } |
||
621 | } |
||
622 | |||
623 | #endregion |
||
624 | |||
625 | #endregion |
||
626 | |||
627 | /// <summary> |
||
628 | /// Installs into terrain module the standard suite of brushes |
||
629 | /// </summary> |
||
630 | private void InstallDefaultEffects() |
||
631 | { |
||
632 | // Draggable Paint Brush Effects |
||
633 | m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere(); |
||
634 | m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere(); |
||
635 | m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere(); |
||
636 | m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere(); |
||
637 | m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere(); |
||
638 | m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_revert); |
||
639 | m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere(); |
||
640 | m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere(); |
||
641 | m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere(); |
||
642 | |||
643 | // Area of effect selection effects |
||
644 | m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea(); |
||
645 | m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea(); |
||
646 | m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea(); |
||
647 | m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea(); |
||
648 | m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea(); |
||
649 | m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert); |
||
650 | |||
651 | // Filesystem load/save loaders |
||
652 | m_loaders[".r32"] = new RAW32(); |
||
653 | m_loaders[".f32"] = m_loaders[".r32"]; |
||
654 | m_loaders[".ter"] = new Terragen(); |
||
655 | m_loaders[".raw"] = new LLRAW(); |
||
656 | m_loaders[".jpg"] = new JPEG(); |
||
657 | m_loaders[".jpeg"] = m_loaders[".jpg"]; |
||
658 | m_loaders[".bmp"] = new BMP(); |
||
659 | m_loaders[".png"] = new PNG(); |
||
660 | m_loaders[".gif"] = new GIF(); |
||
661 | m_loaders[".tif"] = new TIFF(); |
||
662 | m_loaders[".tiff"] = m_loaders[".tif"]; |
||
663 | } |
||
664 | |||
665 | /// <summary> |
||
666 | /// Saves the current state of the region into the revert map buffer. |
||
667 | /// </summary> |
||
668 | public void UpdateRevertMap() |
||
669 | { |
||
670 | /* |
||
671 | int x; |
||
672 | for (x = 0; x < m_channel.Width; x++) |
||
673 | { |
||
674 | int y; |
||
675 | for (y = 0; y < m_channel.Height; y++) |
||
676 | { |
||
677 | m_revert[x, y] = m_channel[x, y]; |
||
678 | } |
||
679 | } |
||
680 | */ |
||
681 | m_revert = m_channel.MakeCopy(); |
||
682 | } |
||
683 | |||
684 | /// <summary> |
||
685 | /// Loads a tile from a larger terrain file and installs it into the region. |
||
686 | /// </summary> |
||
687 | /// <param name="filename">The terrain file to load</param> |
||
688 | /// <param name="fileWidth">The width of the file in units</param> |
||
689 | /// <param name="fileHeight">The height of the file in units</param> |
||
690 | /// <param name="fileStartX">Where to begin our slice</param> |
||
691 | /// <param name="fileStartY">Where to begin our slice</param> |
||
692 | public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY) |
||
693 | { |
||
694 | int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX; |
||
695 | int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY; |
||
696 | |||
697 | if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight) |
||
698 | { |
||
699 | // this region is included in the tile request |
||
700 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
701 | { |
||
702 | if (filename.EndsWith(loader.Key)) |
||
703 | { |
||
704 | lock (m_scene) |
||
705 | { |
||
706 | ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY, |
||
707 | fileWidth, fileHeight, |
||
708 | (int) m_scene.RegionInfo.RegionSizeX, |
||
709 | (int) m_scene.RegionInfo.RegionSizeY); |
||
710 | m_scene.Heightmap = channel; |
||
711 | m_channel = channel; |
||
712 | UpdateRevertMap(); |
||
713 | } |
||
714 | |||
715 | return; |
||
716 | } |
||
717 | } |
||
718 | } |
||
719 | } |
||
720 | |||
721 | /// <summary> |
||
722 | /// Save a number of map tiles to a single big image file. |
||
723 | /// </summary> |
||
724 | /// <remarks> |
||
725 | /// If the image file already exists then the tiles saved will replace those already in the file - other tiles |
||
726 | /// will be untouched. |
||
727 | /// </remarks> |
||
728 | /// <param name="filename">The terrain file to save</param> |
||
729 | /// <param name="fileWidth">The number of tiles to save along the X axis.</param> |
||
730 | /// <param name="fileHeight">The number of tiles to save along the Y axis.</param> |
||
731 | /// <param name="fileStartX">The map x co-ordinate at which to begin the save.</param> |
||
732 | /// <param name="fileStartY">The may y co-ordinate at which to begin the save.</param> |
||
733 | public void SaveToFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY) |
||
734 | { |
||
735 | int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX; |
||
736 | int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY; |
||
737 | |||
738 | if (offsetX < 0 || offsetX >= fileWidth || offsetY < 0 || offsetY >= fileHeight) |
||
739 | { |
||
740 | MainConsole.Instance.OutputFormat( |
||
741 | "ERROR: file width + minimum X tile and file height + minimum Y tile must incorporate the current region at ({0},{1}). File width {2} from {3} and file height {4} from {5} does not.", |
||
742 | m_scene.RegionInfo.RegionLocX, m_scene.RegionInfo.RegionLocY, fileWidth, fileStartX, fileHeight, fileStartY); |
||
743 | |||
744 | return; |
||
745 | } |
||
746 | |||
747 | // this region is included in the tile request |
||
748 | foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders) |
||
749 | { |
||
750 | if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave()) |
||
751 | { |
||
752 | lock (m_scene) |
||
753 | { |
||
754 | loader.Value.SaveFile(m_channel, filename, offsetX, offsetY, |
||
755 | fileWidth, fileHeight, |
||
756 | (int)m_scene.RegionInfo.RegionSizeX, |
||
757 | (int)m_scene.RegionInfo.RegionSizeY); |
||
758 | |||
759 | MainConsole.Instance.OutputFormat( |
||
760 | "Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}", |
||
761 | fileStartX, fileStartY, fileStartX + fileWidth - 1, fileStartY + fileHeight - 1, |
||
762 | m_scene.RegionInfo.RegionName, filename); |
||
763 | } |
||
764 | |||
765 | return; |
||
766 | } |
||
767 | } |
||
768 | |||
769 | MainConsole.Instance.OutputFormat( |
||
770 | "ERROR: Could not save terrain from {0} to {1}. Valid file extensions are {2}", |
||
771 | m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave); |
||
772 | } |
||
773 | |||
774 | /// <summary> |
||
775 | /// Called before processing of every simulation frame. |
||
776 | /// This is used to check to see of any of the terrain is tainted and, if so, schedule |
||
777 | /// updates for all the presences. |
||
778 | /// This also checks to see if there are updates that need to be sent for each presence. |
||
779 | /// This is where the logic is to send terrain updates to clients. |
||
780 | /// </summary> |
||
781 | private void EventManager_OnFrame() |
||
782 | { |
||
783 | TerrainData terrData = m_channel.GetTerrainData(); |
||
784 | |||
785 | bool shouldTaint = false; |
||
786 | for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize) |
||
787 | { |
||
788 | for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize) |
||
789 | { |
||
790 | if (terrData.IsTaintedAt(x, y)) |
||
791 | { |
||
792 | // Found a patch that was modified. Push this flag into the clients. |
||
793 | SendToClients(terrData, x, y); |
||
794 | shouldTaint = true; |
||
795 | } |
||
796 | } |
||
797 | } |
||
798 | |||
799 | // This event also causes changes to be sent to the clients |
||
800 | CheckSendingPatchesToClients(); |
||
801 | |||
802 | // If things changes, generate some events |
||
803 | if (shouldTaint) |
||
804 | { |
||
805 | m_scene.EventManager.TriggerTerrainTainted(); |
||
806 | m_tainted = true; |
||
807 | } |
||
808 | } |
||
809 | |||
810 | /// <summary> |
||
811 | /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections |
||
812 | /// Called infrequently (like every 5 seconds or so). Best used for storing terrain. |
||
813 | /// </summary> |
||
814 | private void EventManager_OnTerrainTick() |
||
815 | { |
||
816 | if (m_tainted) |
||
817 | { |
||
818 | m_tainted = false; |
||
819 | m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised()); |
||
820 | m_scene.SaveTerrain(); |
||
821 | |||
822 | // Clients who look at the map will never see changes after they looked at the map, so i've commented this out. |
||
823 | //m_scene.CreateTerrainTexture(true); |
||
824 | } |
||
825 | } |
||
826 | |||
827 | /// <summary> |
||
828 | /// Processes commandline input. Do not call directly. |
||
829 | /// </summary> |
||
830 | /// <param name="args">Commandline arguments</param> |
||
831 | private void EventManager_OnPluginConsole(string[] args) |
||
832 | { |
||
833 | if (args[0] == "terrain") |
||
834 | { |
||
835 | if (args.Length == 1) |
||
836 | { |
||
837 | m_commander.ProcessConsoleCommand("help", new string[0]); |
||
838 | return; |
||
839 | } |
||
840 | |||
841 | string[] tmpArgs = new string[args.Length - 2]; |
||
842 | int i; |
||
843 | for (i = 2; i < args.Length; i++) |
||
844 | tmpArgs[i - 2] = args[i]; |
||
845 | |||
846 | m_commander.ProcessConsoleCommand(args[1], tmpArgs); |
||
847 | } |
||
848 | } |
||
849 | |||
850 | /// <summary> |
||
851 | /// Installs terrain brush hook to IClientAPI |
||
852 | /// </summary> |
||
853 | /// <param name="client"></param> |
||
854 | private void EventManager_OnNewClient(IClientAPI client) |
||
855 | { |
||
856 | client.OnModifyTerrain += client_OnModifyTerrain; |
||
857 | client.OnBakeTerrain += client_OnBakeTerrain; |
||
858 | client.OnLandUndo += client_OnLandUndo; |
||
859 | client.OnUnackedTerrain += client_OnUnackedTerrain; |
||
860 | } |
||
861 | |||
862 | /// <summary> |
||
863 | /// Installs terrain brush hook to IClientAPI |
||
864 | /// </summary> |
||
865 | /// <param name="client"></param> |
||
866 | private void EventManager_OnClientClosed(UUID client, Scene scene) |
||
867 | { |
||
868 | ScenePresence presence = scene.GetScenePresence(client); |
||
869 | if (presence != null) |
||
870 | { |
||
871 | presence.ControllingClient.OnModifyTerrain -= client_OnModifyTerrain; |
||
872 | presence.ControllingClient.OnBakeTerrain -= client_OnBakeTerrain; |
||
873 | presence.ControllingClient.OnLandUndo -= client_OnLandUndo; |
||
874 | presence.ControllingClient.OnUnackedTerrain -= client_OnUnackedTerrain; |
||
875 | } |
||
876 | |||
877 | lock (m_perClientPatchUpdates) |
||
878 | m_perClientPatchUpdates.Remove(client); |
||
879 | } |
||
880 | |||
881 | /// <summary> |
||
882 | /// Scan over changes in the terrain and limit height changes. This enforces the |
||
883 | /// non-estate owner limits on rate of terrain editting. |
||
884 | /// Returns 'true' if any heights were limited. |
||
885 | /// </summary> |
||
886 | private bool EnforceEstateLimits() |
||
887 | { |
||
888 | TerrainData terrData = m_channel.GetTerrainData(); |
||
889 | |||
890 | bool wasLimited = false; |
||
891 | for (int x = 0; x < terrData.SizeX; x += Constants.TerrainPatchSize) |
||
892 | { |
||
893 | for (int y = 0; y < terrData.SizeY; y += Constants.TerrainPatchSize) |
||
894 | { |
||
895 | if (terrData.IsTaintedAt(x, y, false /* clearOnTest */)) |
||
896 | { |
||
897 | // If we should respect the estate settings then |
||
898 | // fixup and height deltas that don't respect them. |
||
899 | // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values. |
||
900 | wasLimited |= LimitChannelChanges(terrData, x, y); |
||
901 | } |
||
902 | } |
||
903 | } |
||
904 | return wasLimited; |
||
905 | } |
||
906 | |||
907 | /// <summary> |
||
908 | /// Checks to see height deltas in the tainted terrain patch at xStart ,yStart |
||
909 | /// are all within the current estate limits |
||
910 | /// <returns>true if changes were limited, false otherwise</returns> |
||
911 | /// </summary> |
||
912 | private bool LimitChannelChanges(TerrainData terrData, int xStart, int yStart) |
||
913 | { |
||
914 | bool changesLimited = false; |
||
915 | float minDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainLowerLimit; |
||
916 | float maxDelta = (float)m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit; |
||
917 | |||
918 | // loop through the height map for this patch and compare it against |
||
919 | // the revert map |
||
920 | for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++) |
||
921 | { |
||
922 | for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++) |
||
923 | { |
||
924 | float requestedHeight = terrData[x, y]; |
||
925 | float bakedHeight = (float)m_revert[x, y]; |
||
926 | float requestedDelta = requestedHeight - bakedHeight; |
||
927 | |||
928 | if (requestedDelta > maxDelta) |
||
929 | { |
||
930 | terrData[x, y] = bakedHeight + maxDelta; |
||
931 | changesLimited = true; |
||
932 | } |
||
933 | else if (requestedDelta < minDelta) |
||
934 | { |
||
935 | terrData[x, y] = bakedHeight + minDelta; //as lower is a -ve delta |
||
936 | changesLimited = true; |
||
937 | } |
||
938 | } |
||
939 | } |
||
940 | |||
941 | return changesLimited; |
||
942 | } |
||
943 | |||
944 | private void client_OnLandUndo(IClientAPI client) |
||
945 | { |
||
946 | lock (m_undo) |
||
947 | { |
||
948 | if (m_undo.Count > 0) |
||
949 | { |
||
950 | LandUndoState goback = m_undo.Pop(); |
||
951 | if (goback != null) |
||
952 | goback.PlaybackState(); |
||
953 | } |
||
954 | } |
||
955 | } |
||
956 | |||
957 | /// <summary> |
||
958 | /// Sends a copy of the current terrain to the scenes clients |
||
959 | /// </summary> |
||
960 | /// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param> |
||
961 | /// <param name="x">The patch corner to send</param> |
||
962 | /// <param name="y">The patch corner to send</param> |
||
963 | private void SendToClients(TerrainData terrData, int x, int y) |
||
964 | { |
||
965 | if (m_sendTerrainUpdatesByViewDistance) |
||
966 | { |
||
967 | // Add that this patch needs to be sent to the accounting for each client. |
||
968 | lock (m_perClientPatchUpdates) |
||
969 | { |
||
970 | m_scene.ForEachScenePresence(presence => |
||
971 | { |
||
972 | PatchUpdates thisClientUpdates; |
||
973 | if (!m_perClientPatchUpdates.TryGetValue(presence.UUID, out thisClientUpdates)) |
||
974 | { |
||
975 | // There is a ScenePresence without a send patch map. Create one. |
||
976 | thisClientUpdates = new PatchUpdates(terrData, presence); |
||
977 | m_perClientPatchUpdates.Add(presence.UUID, thisClientUpdates); |
||
978 | } |
||
979 | thisClientUpdates.SetByXY(x, y, true); |
||
980 | } |
||
981 | ); |
||
982 | } |
||
983 | } |
||
984 | else |
||
985 | { |
||
986 | // Legacy update sending where the update is sent out as soon as noticed |
||
987 | // We know the actual terrain data passed is ignored. This kludge saves changing IClientAPI. |
||
988 | //float[] heightMap = terrData.GetFloatsSerialized(); |
||
989 | float[] heightMap = new float[10]; |
||
990 | m_scene.ForEachClient( |
||
991 | delegate(IClientAPI controller) |
||
992 | { |
||
993 | controller.SendLayerData(x / Constants.TerrainPatchSize, |
||
994 | y / Constants.TerrainPatchSize, |
||
995 | heightMap); |
||
996 | } |
||
997 | ); |
||
998 | } |
||
999 | } |
||
1000 | |||
1001 | private class PatchesToSend : IComparable<PatchesToSend> |
||
1002 | { |
||
1003 | public int PatchX; |
||
1004 | public int PatchY; |
||
1005 | public float Dist; |
||
1006 | public PatchesToSend(int pX, int pY, float pDist) |
||
1007 | { |
||
1008 | PatchX = pX; |
||
1009 | PatchY = pY; |
||
1010 | Dist = pDist; |
||
1011 | } |
||
1012 | public int CompareTo(PatchesToSend other) |
||
1013 | { |
||
1014 | return Dist.CompareTo(other.Dist); |
||
1015 | } |
||
1016 | } |
||
1017 | |||
1018 | // Called each frame time to see if there are any patches to send to any of the |
||
1019 | // ScenePresences. |
||
1020 | // Loop through all the per-client info and send any patches necessary. |
||
1021 | private void CheckSendingPatchesToClients() |
||
1022 | { |
||
1023 | lock (m_perClientPatchUpdates) |
||
1024 | { |
||
1025 | foreach (PatchUpdates pups in m_perClientPatchUpdates.Values) |
||
1026 | { |
||
1027 | if (pups.HasUpdates()) |
||
1028 | { |
||
1029 | // There is something that could be sent to this client. |
||
1030 | List<PatchesToSend> toSend = GetModifiedPatchesInViewDistance(pups); |
||
1031 | if (toSend.Count > 0) |
||
1032 | { |
||
1033 | // m_log.DebugFormat("{0} CheckSendingPatchesToClient: sending {1} patches to {2} in region {3}", |
||
1034 | // LogHeader, toSend.Count, pups.Presence.Name, m_scene.RegionInfo.RegionName); |
||
1035 | // Sort the patches to send by the distance from the presence |
||
1036 | toSend.Sort(); |
||
1037 | /* |
||
1038 | foreach (PatchesToSend pts in toSend) |
||
1039 | { |
||
1040 | pups.Presence.ControllingClient.SendLayerData(pts.PatchX, pts.PatchY, null); |
||
1041 | // presence.ControllingClient.SendLayerData(xs.ToArray(), ys.ToArray(), null, TerrainPatch.LayerType.Land); |
||
1042 | } |
||
1043 | */ |
||
1044 | |||
1045 | int[] xPieces = new int[toSend.Count]; |
||
1046 | int[] yPieces = new int[toSend.Count]; |
||
1047 | float[] patchPieces = new float[toSend.Count * 2]; |
||
1048 | int pieceIndex = 0; |
||
1049 | foreach (PatchesToSend pts in toSend) |
||
1050 | { |
||
1051 | patchPieces[pieceIndex++] = pts.PatchX; |
||
1052 | patchPieces[pieceIndex++] = pts.PatchY; |
||
1053 | } |
||
1054 | pups.Presence.ControllingClient.SendLayerData(-toSend.Count, 0, patchPieces); |
||
1055 | } |
||
1056 | } |
||
1057 | } |
||
1058 | } |
||
1059 | } |
||
1060 | |||
1061 | private List<PatchesToSend> GetModifiedPatchesInViewDistance(PatchUpdates pups) |
||
1062 | { |
||
1063 | List<PatchesToSend> ret = new List<PatchesToSend>(); |
||
1064 | |||
1065 | ScenePresence presence = pups.Presence; |
||
1066 | if (presence == null) |
||
1067 | return ret; |
||
1068 | |||
1069 | // Compute the area of patches within our draw distance |
||
1070 | int startX = (((int) (presence.AbsolutePosition.X - presence.DrawDistance))/Constants.TerrainPatchSize) - 2; |
||
1071 | startX = Math.Max(startX, 0); |
||
1072 | startX = Math.Min(startX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize); |
||
1073 | int startY = (((int) (presence.AbsolutePosition.Y - presence.DrawDistance))/Constants.TerrainPatchSize) - 2; |
||
1074 | startY = Math.Max(startY, 0); |
||
1075 | startY = Math.Min(startY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); |
||
1076 | int endX = (((int) (presence.AbsolutePosition.X + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; |
||
1077 | endX = Math.Max(endX, 0); |
||
1078 | endX = Math.Min(endX, (int)m_scene.RegionInfo.RegionSizeX/Constants.TerrainPatchSize); |
||
1079 | int endY = (((int) (presence.AbsolutePosition.Y + presence.DrawDistance))/Constants.TerrainPatchSize) + 2; |
||
1080 | endY = Math.Max(endY, 0); |
||
1081 | endY = Math.Min(endY, (int)m_scene.RegionInfo.RegionSizeY/Constants.TerrainPatchSize); |
||
1082 | // m_log.DebugFormat("{0} GetModifiedPatchesInViewDistance. rName={1}, ddist={2}, apos={3}, start=<{4},{5}>, end=<{6},{7}>", |
||
1083 | // LogHeader, m_scene.RegionInfo.RegionName, |
||
1084 | // presence.DrawDistance, presence.AbsolutePosition, |
||
1085 | // startX, startY, endX, endY); |
||
1086 | for (int x = startX; x < endX; x++) |
||
1087 | { |
||
1088 | for (int y = startY; y < endY; y++) |
||
1089 | { |
||
1090 | //Need to make sure we don't send the same ones over and over |
||
1091 | Vector3 presencePos = presence.AbsolutePosition; |
||
1092 | Vector3 patchPos = new Vector3(x * Constants.TerrainPatchSize, y * Constants.TerrainPatchSize, presencePos.Z); |
||
1093 | if (pups.GetByPatch(x, y)) |
||
1094 | { |
||
1095 | //Check which has less distance, camera or avatar position, both have to be done. |
||
1096 | //Its not a radius, its a diameter and we add 50 so that it doesn't look like it cuts off |
||
1097 | if (Util.DistanceLessThan(presencePos, patchPos, presence.DrawDistance + 50) |
||
1098 | || Util.DistanceLessThan(presence.CameraPosition, patchPos, presence.DrawDistance + 50)) |
||
1099 | { |
||
1100 | //They can see it, send it to them |
||
1101 | pups.SetByPatch(x, y, false); |
||
1102 | float dist = Vector3.DistanceSquared(presencePos, patchPos); |
||
1103 | ret.Add(new PatchesToSend(x, y, dist)); |
||
1104 | //Wait and send them all at once |
||
1105 | // pups.client.SendLayerData(x, y, null); |
||
1106 | } |
||
1107 | } |
||
1108 | } |
||
1109 | } |
||
1110 | return ret; |
||
1111 | } |
||
1112 | |||
1113 | private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action, |
||
1114 | float north, float west, float south, float east, UUID agentId) |
||
1115 | { |
||
1116 | bool god = m_scene.Permissions.IsGod(user); |
||
1117 | bool allowed = false; |
||
1118 | if (north == south && east == west) |
||
1119 | { |
||
1120 | if (m_painteffects.ContainsKey((StandardTerrainEffects) action)) |
||
1121 | { |
||
1122 | bool[,] allowMask = new bool[m_channel.Width,m_channel.Height]; |
||
1123 | allowMask.Initialize(); |
||
1124 | int n = size + 1; |
||
1125 | if (n > 2) |
||
1126 | n = 4; |
||
1127 | |||
1128 | int zx = (int) (west + 0.5); |
||
1129 | int zy = (int) (north + 0.5); |
||
1130 | |||
1131 | int dx; |
||
1132 | for (dx=-n; dx<=n; dx++) |
||
1133 | { |
||
1134 | int dy; |
||
1135 | for (dy=-n; dy<=n; dy++) |
||
1136 | { |
||
1137 | int x = zx + dx; |
||
1138 | int y = zy + dy; |
||
1139 | if (x>=0 && y>=0 && x<m_channel.Width && y<m_channel.Height) |
||
1140 | { |
||
1141 | if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0))) |
||
1142 | { |
||
1143 | allowMask[x, y] = true; |
||
1144 | allowed = true; |
||
1145 | } |
||
1146 | } |
||
1147 | } |
||
1148 | } |
||
1149 | if (allowed) |
||
1150 | { |
||
1151 | StoreUndoState(); |
||
1152 | m_painteffects[(StandardTerrainEffects) action].PaintEffect( |
||
1153 | m_channel, allowMask, west, south, height, size, seconds); |
||
1154 | |||
1155 | //revert changes outside estate limits |
||
1156 | if (!god) |
||
1157 | EnforceEstateLimits(); |
||
1158 | } |
||
1159 | } |
||
1160 | else |
||
1161 | { |
||
1162 | m_log.Debug("Unknown terrain brush type " + action); |
||
1163 | } |
||
1164 | } |
||
1165 | else |
||
1166 | { |
||
1167 | if (m_floodeffects.ContainsKey((StandardTerrainEffects) action)) |
||
1168 | { |
||
1169 | bool[,] fillArea = new bool[m_channel.Width,m_channel.Height]; |
||
1170 | fillArea.Initialize(); |
||
1171 | |||
1172 | int x; |
||
1173 | for (x = 0; x < m_channel.Width; x++) |
||
1174 | { |
||
1175 | int y; |
||
1176 | for (y = 0; y < m_channel.Height; y++) |
||
1177 | { |
||
1178 | if (x < east && x > west) |
||
1179 | { |
||
1180 | if (y < north && y > south) |
||
1181 | { |
||
1182 | if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0))) |
||
1183 | { |
||
1184 | fillArea[x, y] = true; |
||
1185 | allowed = true; |
||
1186 | } |
||
1187 | } |
||
1188 | } |
||
1189 | } |
||
1190 | } |
||
1191 | |||
1192 | if (allowed) |
||
1193 | { |
||
1194 | StoreUndoState(); |
||
1195 | m_floodeffects[(StandardTerrainEffects) action].FloodEffect(m_channel, fillArea, size); |
||
1196 | |||
1197 | //revert changes outside estate limits |
||
1198 | if (!god) |
||
1199 | EnforceEstateLimits(); |
||
1200 | } |
||
1201 | } |
||
1202 | else |
||
1203 | { |
||
1204 | m_log.Debug("Unknown terrain flood type " + action); |
||
1205 | } |
||
1206 | } |
||
1207 | } |
||
1208 | |||
1209 | private void client_OnBakeTerrain(IClientAPI remoteClient) |
||
1210 | { |
||
1211 | // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area. |
||
1212 | // for now check a point in the centre of the region |
||
1213 | |||
1214 | if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true)) |
||
1215 | { |
||
1216 | InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter |
||
1217 | } |
||
1218 | } |
||
1219 | |||
1220 | protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY) |
||
1221 | { |
||
1222 | //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY); |
||
1223 | // SendLayerData does not use the heightmap parameter. This kludge is so as to not change IClientAPI. |
||
1224 | float[] heightMap = new float[10]; |
||
1225 | client.SendLayerData(patchX, patchY, heightMap); |
||
1226 | } |
||
1227 | |||
1228 | private void StoreUndoState() |
||
1229 | { |
||
1230 | lock (m_undo) |
||
1231 | { |
||
1232 | if (m_undo.Count > 0) |
||
1233 | { |
||
1234 | LandUndoState last = m_undo.Peek(); |
||
1235 | if (last != null) |
||
1236 | { |
||
1237 | if (last.Compare(m_channel)) |
||
1238 | return; |
||
1239 | } |
||
1240 | } |
||
1241 | |||
1242 | LandUndoState nUndo = new LandUndoState(this, m_channel); |
||
1243 | m_undo.Push(nUndo); |
||
1244 | } |
||
1245 | } |
||
1246 | |||
1247 | #region Console Commands |
||
1248 | |||
1249 | private void InterfaceLoadFile(Object[] args) |
||
1250 | { |
||
1251 | LoadFromFile((string) args[0]); |
||
1252 | } |
||
1253 | |||
1254 | private void InterfaceLoadTileFile(Object[] args) |
||
1255 | { |
||
1256 | LoadFromFile((string) args[0], |
||
1257 | (int) args[1], |
||
1258 | (int) args[2], |
||
1259 | (int) args[3], |
||
1260 | (int) args[4]); |
||
1261 | } |
||
1262 | |||
1263 | private void InterfaceSaveFile(Object[] args) |
||
1264 | { |
||
1265 | SaveToFile((string) args[0]); |
||
1266 | } |
||
1267 | |||
1268 | private void InterfaceSaveTileFile(Object[] args) |
||
1269 | { |
||
1270 | SaveToFile((string)args[0], |
||
1271 | (int)args[1], |
||
1272 | (int)args[2], |
||
1273 | (int)args[3], |
||
1274 | (int)args[4]); |
||
1275 | } |
||
1276 | |||
1277 | private void InterfaceBakeTerrain(Object[] args) |
||
1278 | { |
||
1279 | UpdateRevertMap(); |
||
1280 | } |
||
1281 | |||
1282 | private void InterfaceRevertTerrain(Object[] args) |
||
1283 | { |
||
1284 | int x, y; |
||
1285 | for (x = 0; x < m_channel.Width; x++) |
||
1286 | for (y = 0; y < m_channel.Height; y++) |
||
1287 | m_channel[x, y] = m_revert[x, y]; |
||
1288 | |||
1289 | } |
||
1290 | |||
1291 | private void InterfaceFlipTerrain(Object[] args) |
||
1292 | { |
||
1293 | String direction = (String)args[0]; |
||
1294 | |||
1295 | if (direction.ToLower().StartsWith("y")) |
||
1296 | { |
||
1297 | for (int x = 0; x < m_channel.Width; x++) |
||
1298 | { |
||
1299 | for (int y = 0; y < m_channel.Height / 2; y++) |
||
1300 | { |
||
1301 | double height = m_channel[x, y]; |
||
1302 | double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y]; |
||
1303 | m_channel[x, y] = flippedHeight; |
||
1304 | m_channel[x, (int)m_channel.Height - 1 - y] = height; |
||
1305 | |||
1306 | } |
||
1307 | } |
||
1308 | } |
||
1309 | else if (direction.ToLower().StartsWith("x")) |
||
1310 | { |
||
1311 | for (int y = 0; y < m_channel.Height; y++) |
||
1312 | { |
||
1313 | for (int x = 0; x < m_channel.Width / 2; x++) |
||
1314 | { |
||
1315 | double height = m_channel[x, y]; |
||
1316 | double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y]; |
||
1317 | m_channel[x, y] = flippedHeight; |
||
1318 | m_channel[(int)m_channel.Width - 1 - x, y] = height; |
||
1319 | |||
1320 | } |
||
1321 | } |
||
1322 | } |
||
1323 | else |
||
1324 | { |
||
1325 | MainConsole.Instance.OutputFormat("ERROR: Unrecognised direction {0} - need x or y", direction); |
||
1326 | } |
||
1327 | } |
||
1328 | |||
1329 | private void InterfaceRescaleTerrain(Object[] args) |
||
1330 | { |
||
1331 | double desiredMin = (double)args[0]; |
||
1332 | double desiredMax = (double)args[1]; |
||
1333 | |||
1334 | // determine desired scaling factor |
||
1335 | double desiredRange = desiredMax - desiredMin; |
||
1336 | //m_log.InfoFormat("Desired {0}, {1} = {2}", new Object[] { desiredMin, desiredMax, desiredRange }); |
||
1337 | |||
1338 | if (desiredRange == 0d) |
||
1339 | { |
||
1340 | // delta is zero so flatten at requested height |
||
1341 | InterfaceFillTerrain(new Object[] { args[1] }); |
||
1342 | } |
||
1343 | else |
||
1344 | { |
||
1345 | //work out current heightmap range |
||
1346 | double currMin = double.MaxValue; |
||
1347 | double currMax = double.MinValue; |
||
1348 | |||
1349 | int width = m_channel.Width; |
||
1350 | int height = m_channel.Height; |
||
1351 | |||
1352 | for (int x = 0; x < width; x++) |
||
1353 | { |
||
1354 | for (int y = 0; y < height; y++) |
||
1355 | { |
||
1356 | double currHeight = m_channel[x, y]; |
||
1357 | if (currHeight < currMin) |
||
1358 | { |
||
1359 | currMin = currHeight; |
||
1360 | } |
||
1361 | else if (currHeight > currMax) |
||
1362 | { |
||
1363 | currMax = currHeight; |
||
1364 | } |
||
1365 | } |
||
1366 | } |
||
1367 | |||
1368 | double currRange = currMax - currMin; |
||
1369 | double scale = desiredRange / currRange; |
||
1370 | |||
1371 | //m_log.InfoFormat("Current {0}, {1} = {2}", new Object[] { currMin, currMax, currRange }); |
||
1372 | //m_log.InfoFormat("Scale = {0}", scale); |
||
1373 | |||
1374 | // scale the heightmap accordingly |
||
1375 | for (int x = 0; x < width; x++) |
||
1376 | { |
||
1377 | for (int y = 0; y < height; y++) |
||
1378 | { |
||
1379 | double currHeight = m_channel[x, y] - currMin; |
||
1380 | m_channel[x, y] = desiredMin + (currHeight * scale); |
||
1381 | } |
||
1382 | } |
||
1383 | |||
1384 | } |
||
1385 | |||
1386 | } |
||
1387 | |||
1388 | private void InterfaceElevateTerrain(Object[] args) |
||
1389 | { |
||
1390 | int x, y; |
||
1391 | for (x = 0; x < m_channel.Width; x++) |
||
1392 | for (y = 0; y < m_channel.Height; y++) |
||
1393 | m_channel[x, y] += (double) args[0]; |
||
1394 | } |
||
1395 | |||
1396 | private void InterfaceMultiplyTerrain(Object[] args) |
||
1397 | { |
||
1398 | int x, y; |
||
1399 | for (x = 0; x < m_channel.Width; x++) |
||
1400 | for (y = 0; y < m_channel.Height; y++) |
||
1401 | m_channel[x, y] *= (double) args[0]; |
||
1402 | } |
||
1403 | |||
1404 | private void InterfaceLowerTerrain(Object[] args) |
||
1405 | { |
||
1406 | int x, y; |
||
1407 | for (x = 0; x < m_channel.Width; x++) |
||
1408 | for (y = 0; y < m_channel.Height; y++) |
||
1409 | m_channel[x, y] -= (double) args[0]; |
||
1410 | } |
||
1411 | |||
1412 | public void InterfaceFillTerrain(Object[] args) |
||
1413 | { |
||
1414 | int x, y; |
||
1415 | |||
1416 | for (x = 0; x < m_channel.Width; x++) |
||
1417 | for (y = 0; y < m_channel.Height; y++) |
||
1418 | m_channel[x, y] = (double) args[0]; |
||
1419 | } |
||
1420 | |||
1421 | private void InterfaceMinTerrain(Object[] args) |
||
1422 | { |
||
1423 | int x, y; |
||
1424 | for (x = 0; x < m_channel.Width; x++) |
||
1425 | { |
||
1426 | for (y = 0; y < m_channel.Height; y++) |
||
1427 | { |
||
1428 | m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]); |
||
1429 | } |
||
1430 | } |
||
1431 | } |
||
1432 | |||
1433 | private void InterfaceMaxTerrain(Object[] args) |
||
1434 | { |
||
1435 | int x, y; |
||
1436 | for (x = 0; x < m_channel.Width; x++) |
||
1437 | { |
||
1438 | for (y = 0; y < m_channel.Height; y++) |
||
1439 | { |
||
1440 | m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]); |
||
1441 | } |
||
1442 | } |
||
1443 | } |
||
1444 | |||
1445 | private void InterfaceShow(Object[] args) |
||
1446 | { |
||
1447 | Vector2 point; |
||
1448 | |||
1449 | if (!ConsoleUtil.TryParseConsole2DVector((string)args[0], null, out point)) |
||
1450 | { |
||
1451 | Console.WriteLine("ERROR: {0} is not a valid vector", args[0]); |
||
1452 | return; |
||
1453 | } |
||
1454 | |||
1455 | double height = m_channel[(int)point.X, (int)point.Y]; |
||
1456 | |||
1457 | Console.WriteLine("Terrain height at {0} is {1}", point, height); |
||
1458 | } |
||
1459 | |||
1460 | private void InterfaceShowDebugStats(Object[] args) |
||
1461 | { |
||
1462 | double max = Double.MinValue; |
||
1463 | double min = double.MaxValue; |
||
1464 | double sum = 0; |
||
1465 | |||
1466 | int x; |
||
1467 | for (x = 0; x < m_channel.Width; x++) |
||
1468 | { |
||
1469 | int y; |
||
1470 | for (y = 0; y < m_channel.Height; y++) |
||
1471 | { |
||
1472 | sum += m_channel[x, y]; |
||
1473 | if (max < m_channel[x, y]) |
||
1474 | max = m_channel[x, y]; |
||
1475 | if (min > m_channel[x, y]) |
||
1476 | min = m_channel[x, y]; |
||
1477 | } |
||
1478 | } |
||
1479 | |||
1480 | double avg = sum / (m_channel.Height * m_channel.Width); |
||
1481 | |||
1482 | MainConsole.Instance.OutputFormat("Channel {0}x{1}", m_channel.Width, m_channel.Height); |
||
1483 | MainConsole.Instance.OutputFormat("max/min/avg/sum: {0}/{1}/{2}/{3}", max, min, avg, sum); |
||
1484 | } |
||
1485 | |||
1486 | private void InterfaceEnableExperimentalBrushes(Object[] args) |
||
1487 | { |
||
1488 | if ((bool) args[0]) |
||
1489 | { |
||
1490 | m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere(); |
||
1491 | m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere(); |
||
1492 | m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere(); |
||
1493 | } |
||
1494 | else |
||
1495 | { |
||
1496 | InstallDefaultEffects(); |
||
1497 | } |
||
1498 | } |
||
1499 | |||
1500 | private void InterfaceRunPluginEffect(Object[] args) |
||
1501 | { |
||
1502 | string firstArg = (string)args[0]; |
||
1503 | |||
1504 | if (firstArg == "list") |
||
1505 | { |
||
1506 | MainConsole.Instance.Output("List of loaded plugins"); |
||
1507 | foreach (KeyValuePair<string, ITerrainEffect> kvp in m_plugineffects) |
||
1508 | { |
||
1509 | MainConsole.Instance.Output(kvp.Key); |
||
1510 | } |
||
1511 | return; |
||
1512 | } |
||
1513 | |||
1514 | if (firstArg == "reload") |
||
1515 | { |
||
1516 | LoadPlugins(); |
||
1517 | return; |
||
1518 | } |
||
1519 | |||
1520 | if (m_plugineffects.ContainsKey(firstArg)) |
||
1521 | { |
||
1522 | m_plugineffects[firstArg].RunEffect(m_channel); |
||
1523 | } |
||
1524 | else |
||
1525 | { |
||
1526 | MainConsole.Instance.Output("WARNING: No such plugin effect {0} loaded.", firstArg); |
||
1527 | } |
||
1528 | } |
||
1529 | |||
1530 | private void InstallInterfaces() |
||
1531 | { |
||
1532 | Command loadFromFileCommand = |
||
1533 | new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file."); |
||
1534 | loadFromFileCommand.AddArgument("filename", |
||
1535 | "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + |
||
1536 | m_supportedFileExtensions, "String"); |
||
1537 | |||
1538 | Command saveToFileCommand = |
||
1539 | new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file."); |
||
1540 | saveToFileCommand.AddArgument("filename", |
||
1541 | "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " + |
||
1542 | m_supportedFileExtensions, "String"); |
||
1543 | |||
1544 | Command loadFromTileCommand = |
||
1545 | new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file."); |
||
1546 | loadFromTileCommand.AddArgument("filename", |
||
1547 | "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " + |
||
1548 | m_supportedFileExtensions, "String"); |
||
1549 | loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer"); |
||
1550 | loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer"); |
||
1551 | loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file", |
||
1552 | "Integer"); |
||
1553 | loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file", |
||
1554 | "Integer"); |
||
1555 | |||
1556 | Command saveToTileCommand = |
||
1557 | new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file."); |
||
1558 | saveToTileCommand.AddArgument("filename", |
||
1559 | "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " + |
||
1560 | m_supportFileExtensionsForTileSave, "String"); |
||
1561 | saveToTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer"); |
||
1562 | saveToTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer"); |
||
1563 | saveToTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file", |
||
1564 | "Integer"); |
||
1565 | saveToTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first tile on the file\n" |
||
1566 | + "= Example =\n" |
||
1567 | + "To save a PNG file for a set of map tiles 2 regions wide and 3 regions high from map co-ordinate (9910,10234)\n" |
||
1568 | + " # terrain save-tile ST06.png 2 3 9910 10234\n", |
||
1569 | "Integer"); |
||
1570 | |||
1571 | // Terrain adjustments |
||
1572 | Command fillRegionCommand = |
||
1573 | new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value."); |
||
1574 | fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.", |
||
1575 | "Double"); |
||
1576 | |||
1577 | Command elevateCommand = |
||
1578 | new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount."); |
||
1579 | elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double"); |
||
1580 | |||
1581 | Command lowerCommand = |
||
1582 | new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount."); |
||
1583 | lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double"); |
||
1584 | |||
1585 | Command multiplyCommand = |
||
1586 | new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified."); |
||
1587 | multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double"); |
||
1588 | |||
1589 | Command bakeRegionCommand = |
||
1590 | new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map."); |
||
1591 | Command revertRegionCommand = |
||
1592 | new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap."); |
||
1593 | |||
1594 | Command flipCommand = |
||
1595 | new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis"); |
||
1596 | flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String"); |
||
1597 | |||
1598 | Command rescaleCommand = |
||
1599 | new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights"); |
||
1600 | rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Double"); |
||
1601 | rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Double"); |
||
1602 | |||
1603 | Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value."); |
||
1604 | minCommand.AddArgument("min", "terrain height to use as minimum", "Double"); |
||
1605 | |||
1606 | Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value."); |
||
1607 | maxCommand.AddArgument("min", "terrain height to use as maximum", "Double"); |
||
1608 | |||
1609 | |||
1610 | // Debug |
||
1611 | Command showDebugStatsCommand = |
||
1612 | new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats, |
||
1613 | "Shows some information about the regions heightmap for debugging purposes."); |
||
1614 | |||
1615 | Command showCommand = |
||
1616 | new Command("show", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceShow, |
||
1617 | "Shows terrain height at a given co-ordinate."); |
||
1618 | showCommand.AddArgument("point", "point in <x>,<y> format with no spaces (e.g. 45,45)", "String"); |
||
1619 | |||
1620 | Command experimentalBrushesCommand = |
||
1621 | new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes, |
||
1622 | "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time."); |
||
1623 | experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean"); |
||
1624 | |||
1625 | //Plugins |
||
1626 | Command pluginRunCommand = |
||
1627 | new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect"); |
||
1628 | pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String"); |
||
1629 | |||
1630 | m_commander.RegisterCommand("load", loadFromFileCommand); |
||
1631 | m_commander.RegisterCommand("load-tile", loadFromTileCommand); |
||
1632 | m_commander.RegisterCommand("save", saveToFileCommand); |
||
1633 | m_commander.RegisterCommand("save-tile", saveToTileCommand); |
||
1634 | m_commander.RegisterCommand("fill", fillRegionCommand); |
||
1635 | m_commander.RegisterCommand("elevate", elevateCommand); |
||
1636 | m_commander.RegisterCommand("lower", lowerCommand); |
||
1637 | m_commander.RegisterCommand("multiply", multiplyCommand); |
||
1638 | m_commander.RegisterCommand("bake", bakeRegionCommand); |
||
1639 | m_commander.RegisterCommand("revert", revertRegionCommand); |
||
1640 | m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand); |
||
1641 | m_commander.RegisterCommand("show", showCommand); |
||
1642 | m_commander.RegisterCommand("stats", showDebugStatsCommand); |
||
1643 | m_commander.RegisterCommand("effect", pluginRunCommand); |
||
1644 | m_commander.RegisterCommand("flip", flipCommand); |
||
1645 | m_commander.RegisterCommand("rescale", rescaleCommand); |
||
1646 | m_commander.RegisterCommand("min", minCommand); |
||
1647 | m_commander.RegisterCommand("max", maxCommand); |
||
1648 | |||
1649 | // Add this to our scene so scripts can call these functions |
||
1650 | m_scene.RegisterModuleCommander(m_commander); |
||
1651 | } |
||
1652 | |||
1653 | |||
1654 | #endregion |
||
1655 | |||
1656 | } |
||
1657 | } |