opensim-development – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 eva 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.Region.CoreModules.Framework.InterfaceCommander;
43 using OpenSim.Region.CoreModules.World.Terrain.FileLoaders;
44 using OpenSim.Region.CoreModules.World.Terrain.FloodBrushes;
45 using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes;
46 using OpenSim.Region.Framework.Interfaces;
47 using OpenSim.Region.Framework.Scenes;
48  
49 namespace OpenSim.Region.CoreModules.World.Terrain
50 {
51 [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "TerrainModule")]
52 public class TerrainModule : INonSharedRegionModule, ICommandableModule, ITerrainModule
53 {
54 #region StandardTerrainEffects enum
55  
56 /// <summary>
57 /// A standard set of terrain brushes and effects recognised by viewers
58 /// </summary>
59 public enum StandardTerrainEffects : byte
60 {
61 Flatten = 0,
62 Raise = 1,
63 Lower = 2,
64 Smooth = 3,
65 Noise = 4,
66 Revert = 5,
67  
68 // Extended brushes
69 Erode = 255,
70 Weather = 254,
71 Olsen = 253
72 }
73  
74 #endregion
75  
76 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
77 private static readonly string LogHeader = "[TERRAIN MODULE]";
78  
79 private readonly Commander m_commander = new Commander("terrain");
80  
81 private readonly Dictionary<StandardTerrainEffects, ITerrainFloodEffect> m_floodeffects =
82 new Dictionary<StandardTerrainEffects, ITerrainFloodEffect>();
83  
84 private readonly Dictionary<string, ITerrainLoader> m_loaders = new Dictionary<string, ITerrainLoader>();
85  
86 private readonly Dictionary<StandardTerrainEffects, ITerrainPaintableEffect> m_painteffects =
87 new Dictionary<StandardTerrainEffects, ITerrainPaintableEffect>();
88  
89 private ITerrainChannel m_channel;
90 private Dictionary<string, ITerrainEffect> m_plugineffects;
91 private ITerrainChannel m_revert;
92 private Scene m_scene;
93 private volatile bool m_tainted;
94 private readonly Stack<LandUndoState> m_undo = new Stack<LandUndoState>(5);
95  
96 private String m_InitialTerrain = "pinhead-island";
97  
98 /// <summary>
99 /// Human readable list of terrain file extensions that are supported.
100 /// </summary>
101 private string m_supportedFileExtensions = "";
102  
103 //For terrain save-tile file extensions
104 private string m_supportFileExtensionsForTileSave = "";
105  
106 #region ICommandableModule Members
107  
108 public ICommander CommandInterface
109 {
110 get { return m_commander; }
111 }
112  
113 #endregion
114  
115 #region INonSharedRegionModule Members
116  
117 /// <summary>
118 /// Creates and initialises a terrain module for a region
119 /// </summary>
120 /// <param name="scene">Region initialising</param>
121 /// <param name="config">Config for the region</param>
122 public void Initialise(IConfigSource config)
123 {
124 IConfig terrainConfig = config.Configs["Terrain"];
125 if (terrainConfig != null)
126 m_InitialTerrain = terrainConfig.GetString("InitialTerrain", m_InitialTerrain);
127 }
128  
129 public void AddRegion(Scene scene)
130 {
131 m_scene = scene;
132  
133 // Install terrain module in the simulator
134 lock (m_scene)
135 {
136 if (m_scene.Heightmap == null)
137 {
138 m_channel = new TerrainChannel(m_InitialTerrain, (int)m_scene.RegionInfo.RegionSizeX,
139 (int)m_scene.RegionInfo.RegionSizeY,
140 (int)m_scene.RegionInfo.RegionSizeZ);
141 m_scene.Heightmap = m_channel;
142 UpdateRevertMap();
143 }
144 else
145 {
146 m_channel = m_scene.Heightmap;
147 UpdateRevertMap();
148 }
149  
150 m_scene.RegisterModuleInterface<ITerrainModule>(this);
151 m_scene.EventManager.OnNewClient += EventManager_OnNewClient;
152 m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole;
153 m_scene.EventManager.OnTerrainTick += EventManager_OnTerrainTick;
154 }
155  
156 InstallDefaultEffects();
157 LoadPlugins();
158  
159 // Generate user-readable extensions list
160 string supportedFilesSeparator = "";
161 string supportedFilesSeparatorForTileSave = "";
162  
163 m_supportFileExtensionsForTileSave = "";
164 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
165 {
166 m_supportedFileExtensions += supportedFilesSeparator + loader.Key + " (" + loader.Value + ")";
167 supportedFilesSeparator = ", ";
168  
169 //For terrain save-tile file extensions
170 if (loader.Value.SupportsTileSave() == true)
171 {
172 m_supportFileExtensionsForTileSave += supportedFilesSeparatorForTileSave + loader.Key + " (" + loader.Value + ")";
173 supportedFilesSeparatorForTileSave = ", ";
174 }
175 }
176 }
177  
178 public void RegionLoaded(Scene scene)
179 {
180 //Do this here to give file loaders time to initialize and
181 //register their supported file extensions and file formats.
182 InstallInterfaces();
183 }
184  
185 public void RemoveRegion(Scene scene)
186 {
187 lock (m_scene)
188 {
189 // remove the commands
190 m_scene.UnregisterModuleCommander(m_commander.Name);
191 // remove the event-handlers
192 m_scene.EventManager.OnTerrainTick -= EventManager_OnTerrainTick;
193 m_scene.EventManager.OnPluginConsole -= EventManager_OnPluginConsole;
194 m_scene.EventManager.OnNewClient -= EventManager_OnNewClient;
195 // remove the interface
196 m_scene.UnregisterModuleInterface<ITerrainModule>(this);
197 }
198 }
199  
200 public void Close()
201 {
202 }
203  
204 public Type ReplaceableInterface
205 {
206 get { return null; }
207 }
208  
209 public string Name
210 {
211 get { return "TerrainModule"; }
212 }
213  
214 #endregion
215  
216 #region ITerrainModule Members
217  
218 public void UndoTerrain(ITerrainChannel channel)
219 {
220 m_channel = channel;
221 }
222  
223 /// <summary>
224 /// Loads a terrain file from disk and installs it in the scene.
225 /// </summary>
226 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
227 public void LoadFromFile(string filename)
228 {
229 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
230 {
231 if (filename.EndsWith(loader.Key))
232 {
233 lock (m_scene)
234 {
235 try
236 {
237 ITerrainChannel channel = loader.Value.LoadFile(filename);
238 if (channel.Width != m_scene.RegionInfo.RegionSizeX || channel.Height != m_scene.RegionInfo.RegionSizeY)
239 {
240 // TerrainChannel expects a RegionSize x RegionSize map, currently
241 throw new ArgumentException(String.Format("wrong size, use a file with size {0} x {1}",
242 m_scene.RegionInfo.RegionSizeX, m_scene.RegionInfo.RegionSizeY));
243 }
244 m_log.DebugFormat("[TERRAIN]: Loaded terrain, wd/ht: {0}/{1}", channel.Width, channel.Height);
245 m_scene.Heightmap = channel;
246 m_channel = channel;
247 UpdateRevertMap();
248 }
249 catch (NotImplementedException)
250 {
251 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
252 " parser does not support file loading. (May be save only)");
253 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
254 }
255 catch (FileNotFoundException)
256 {
257 m_log.Error(
258 "[TERRAIN]: Unable to load heightmap, file not found. (A directory permissions error may also cause this)");
259 throw new TerrainException(
260 String.Format("unable to load heightmap: file {0} not found (or permissions do not allow access", filename));
261 }
262 catch (ArgumentException e)
263 {
264 m_log.ErrorFormat("[TERRAIN]: Unable to load heightmap: {0}", e.Message);
265 throw new TerrainException(
266 String.Format("Unable to load heightmap: {0}", e.Message));
267 }
268 }
269 CheckForTerrainUpdates();
270 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
271 return;
272 }
273 }
274  
275 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
276 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
277 }
278  
279 /// <summary>
280 /// Saves the current heightmap to a specified file.
281 /// </summary>
282 /// <param name="filename">The destination filename</param>
283 public void SaveToFile(string filename)
284 {
285 try
286 {
287 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
288 {
289 if (filename.EndsWith(loader.Key))
290 {
291 loader.Value.SaveFile(filename, m_channel);
292 m_log.InfoFormat("[TERRAIN]: Saved terrain from {0} to {1}", m_scene.RegionInfo.RegionName, filename);
293 return;
294 }
295 }
296 }
297 catch (IOException ioe)
298 {
299 m_log.Error(String.Format("[TERRAIN]: Unable to save to {0}, {1}", filename, ioe.Message));
300 }
301  
302 m_log.ErrorFormat(
303 "[TERRAIN]: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
304 m_scene.RegionInfo.RegionName, filename, m_supportedFileExtensions);
305 }
306  
307 /// <summary>
308 /// Loads a terrain file from the specified URI
309 /// </summary>
310 /// <param name="filename">The name of the terrain to load</param>
311 /// <param name="pathToTerrainHeightmap">The URI to the terrain height map</param>
312 public void LoadFromStream(string filename, Uri pathToTerrainHeightmap)
313 {
314 LoadFromStream(filename, URIFetch(pathToTerrainHeightmap));
315 }
316  
317 public void LoadFromStream(string filename, Stream stream)
318 {
319 LoadFromStream(filename, Vector3.Zero, 0f, Vector2.Zero, stream);
320 }
321  
322 /// <summary>
323 /// Loads a terrain file from a stream and installs it in the scene.
324 /// </summary>
325 /// <param name="filename">Filename to terrain file. Type is determined by extension.</param>
326 /// <param name="stream"></param>
327 public void LoadFromStream(string filename, Vector3 displacement,
328 float radianRotation, Vector2 rotationDisplacement, Stream stream)
329 {
330 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
331 {
332 if (filename.EndsWith(loader.Key))
333 {
334 lock (m_scene)
335 {
336 try
337 {
338 ITerrainChannel channel = loader.Value.LoadStream(stream);
339 m_channel.Merge(channel, displacement, radianRotation, rotationDisplacement);
340 UpdateRevertMap();
341 }
342 catch (NotImplementedException)
343 {
344 m_log.Error("[TERRAIN]: Unable to load heightmap, the " + loader.Value +
345 " parser does not support file loading. (May be save only)");
346 throw new TerrainException(String.Format("unable to load heightmap: parser {0} does not support loading", loader.Value));
347 }
348 }
349  
350 CheckForTerrainUpdates();
351 m_log.Info("[TERRAIN]: File (" + filename + ") loaded successfully");
352 return;
353 }
354 }
355 m_log.Error("[TERRAIN]: Unable to load heightmap, no file loader available for that format.");
356 throw new TerrainException(String.Format("unable to load heightmap from file {0}: no loader available for that format", filename));
357 }
358  
359 private static Stream URIFetch(Uri uri)
360 {
361 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
362  
363 // request.Credentials = credentials;
364  
365 request.ContentLength = 0;
366 request.KeepAlive = false;
367  
368 WebResponse response = request.GetResponse();
369 Stream file = response.GetResponseStream();
370  
371 if (response.ContentLength == 0)
372 throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
373  
374 // return new BufferedStream(file, (int) response.ContentLength);
375 return new BufferedStream(file, 1000000);
376 }
377  
378 /// <summary>
379 /// Modify Land
380 /// </summary>
381 /// <param name="pos">Land-position (X,Y,0)</param>
382 /// <param name="size">The size of the brush (0=small, 1=medium, 2=large)</param>
383 /// <param name="action">0=LAND_LEVEL, 1=LAND_RAISE, 2=LAND_LOWER, 3=LAND_SMOOTH, 4=LAND_NOISE, 5=LAND_REVERT</param>
384 /// <param name="agentId">UUID of script-owner</param>
385 public void ModifyTerrain(UUID user, Vector3 pos, byte size, byte action, UUID agentId)
386 {
387 float duration = 0.25f;
388 if (action == 0)
389 duration = 4.0f;
390  
391 client_OnModifyTerrain(user, (float)pos.Z, duration, size, action, pos.Y, pos.X, pos.Y, pos.X, agentId);
392 }
393  
394 /// <summary>
395 /// Saves the current heightmap to a specified stream.
396 /// </summary>
397 /// <param name="filename">The destination filename. Used here only to identify the image type</param>
398 /// <param name="stream"></param>
399 public void SaveToStream(string filename, Stream stream)
400 {
401 try
402 {
403 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
404 {
405 if (filename.EndsWith(loader.Key))
406 {
407 loader.Value.SaveStream(stream, m_channel);
408 return;
409 }
410 }
411 }
412 catch (NotImplementedException)
413 {
414 m_log.Error("Unable to save to " + filename + ", saving of this file format has not been implemented.");
415 throw new TerrainException(String.Format("Unable to save heightmap: saving of this file format not implemented"));
416 }
417 }
418  
419 public void TaintTerrain ()
420 {
421 CheckForTerrainUpdates();
422 }
423  
424 #region Plugin Loading Methods
425  
426 private void LoadPlugins()
427 {
428 m_plugineffects = new Dictionary<string, ITerrainEffect>();
429 LoadPlugins(Assembly.GetCallingAssembly());
430 string plugineffectsPath = "Terrain";
431  
432 // Load the files in the Terrain/ dir
433 if (!Directory.Exists(plugineffectsPath))
434 return;
435  
436 string[] files = Directory.GetFiles(plugineffectsPath);
437 foreach (string file in files)
438 {
439 m_log.Info("Loading effects in " + file);
440 try
441 {
442 Assembly library = Assembly.LoadFrom(file);
443 LoadPlugins(library);
444 }
445 catch (BadImageFormatException)
446 {
447 }
448 }
449 }
450  
451 private void LoadPlugins(Assembly library)
452 {
453 foreach (Type pluginType in library.GetTypes())
454 {
455 try
456 {
457 if (pluginType.IsAbstract || pluginType.IsNotPublic)
458 continue;
459  
460 string typeName = pluginType.Name;
461  
462 if (pluginType.GetInterface("ITerrainEffect", false) != null)
463 {
464 ITerrainEffect terEffect = (ITerrainEffect)Activator.CreateInstance(library.GetType(pluginType.ToString()));
465  
466 InstallPlugin(typeName, terEffect);
467 }
468 else if (pluginType.GetInterface("ITerrainLoader", false) != null)
469 {
470 ITerrainLoader terLoader = (ITerrainLoader)Activator.CreateInstance(library.GetType(pluginType.ToString()));
471 m_loaders[terLoader.FileExtension] = terLoader;
472 m_log.Info("L ... " + typeName);
473 }
474 }
475 catch (AmbiguousMatchException)
476 {
477 }
478 }
479 }
480  
481 public void InstallPlugin(string pluginName, ITerrainEffect effect)
482 {
483 lock (m_plugineffects)
484 {
485 if (!m_plugineffects.ContainsKey(pluginName))
486 {
487 m_plugineffects.Add(pluginName, effect);
488 m_log.Info("E ... " + pluginName);
489 }
490 else
491 {
492 m_plugineffects[pluginName] = effect;
493 m_log.Info("E ... " + pluginName + " (Replaced)");
494 }
495 }
496 }
497  
498 #endregion
499  
500 #endregion
501  
502 /// <summary>
503 /// Installs into terrain module the standard suite of brushes
504 /// </summary>
505 private void InstallDefaultEffects()
506 {
507 // Draggable Paint Brush Effects
508 m_painteffects[StandardTerrainEffects.Raise] = new RaiseSphere();
509 m_painteffects[StandardTerrainEffects.Lower] = new LowerSphere();
510 m_painteffects[StandardTerrainEffects.Smooth] = new SmoothSphere();
511 m_painteffects[StandardTerrainEffects.Noise] = new NoiseSphere();
512 m_painteffects[StandardTerrainEffects.Flatten] = new FlattenSphere();
513 m_painteffects[StandardTerrainEffects.Revert] = new RevertSphere(m_revert);
514 m_painteffects[StandardTerrainEffects.Erode] = new ErodeSphere();
515 m_painteffects[StandardTerrainEffects.Weather] = new WeatherSphere();
516 m_painteffects[StandardTerrainEffects.Olsen] = new OlsenSphere();
517  
518 // Area of effect selection effects
519 m_floodeffects[StandardTerrainEffects.Raise] = new RaiseArea();
520 m_floodeffects[StandardTerrainEffects.Lower] = new LowerArea();
521 m_floodeffects[StandardTerrainEffects.Smooth] = new SmoothArea();
522 m_floodeffects[StandardTerrainEffects.Noise] = new NoiseArea();
523 m_floodeffects[StandardTerrainEffects.Flatten] = new FlattenArea();
524 m_floodeffects[StandardTerrainEffects.Revert] = new RevertArea(m_revert);
525  
526 // Filesystem load/save loaders
527 m_loaders[".r32"] = new RAW32();
528 m_loaders[".f32"] = m_loaders[".r32"];
529 m_loaders[".ter"] = new Terragen();
530 m_loaders[".raw"] = new LLRAW();
531 m_loaders[".jpg"] = new JPEG();
532 m_loaders[".jpeg"] = m_loaders[".jpg"];
533 m_loaders[".bmp"] = new BMP();
534 m_loaders[".png"] = new PNG();
535 m_loaders[".gif"] = new GIF();
536 m_loaders[".tif"] = new TIFF();
537 m_loaders[".tiff"] = m_loaders[".tif"];
538 }
539  
540 /// <summary>
541 /// Saves the current state of the region into the revert map buffer.
542 /// </summary>
543 public void UpdateRevertMap()
544 {
545 /*
546 int x;
547 for (x = 0; x < m_channel.Width; x++)
548 {
549 int y;
550 for (y = 0; y < m_channel.Height; y++)
551 {
552 m_revert[x, y] = m_channel[x, y];
553 }
554 }
555 */
556 m_revert = m_channel.MakeCopy();
557 }
558  
559 /// <summary>
560 /// Loads a tile from a larger terrain file and installs it into the region.
561 /// </summary>
562 /// <param name="filename">The terrain file to load</param>
563 /// <param name="fileWidth">The width of the file in units</param>
564 /// <param name="fileHeight">The height of the file in units</param>
565 /// <param name="fileStartX">Where to begin our slice</param>
566 /// <param name="fileStartY">Where to begin our slice</param>
567 public void LoadFromFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
568 {
569 int offsetX = (int) m_scene.RegionInfo.RegionLocX - fileStartX;
570 int offsetY = (int) m_scene.RegionInfo.RegionLocY - fileStartY;
571  
572 if (offsetX >= 0 && offsetX < fileWidth && offsetY >= 0 && offsetY < fileHeight)
573 {
574 // this region is included in the tile request
575 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
576 {
577 if (filename.EndsWith(loader.Key))
578 {
579 lock (m_scene)
580 {
581 ITerrainChannel channel = loader.Value.LoadFile(filename, offsetX, offsetY,
582 fileWidth, fileHeight,
583 (int) m_scene.RegionInfo.RegionSizeX,
584 (int) m_scene.RegionInfo.RegionSizeY);
585 m_scene.Heightmap = channel;
586 m_channel = channel;
587 UpdateRevertMap();
588 }
589  
590 return;
591 }
592 }
593 }
594 }
595  
596 /// <summary>
597 /// Save a number of map tiles to a single big image file.
598 /// </summary>
599 /// <remarks>
600 /// If the image file already exists then the tiles saved will replace those already in the file - other tiles
601 /// will be untouched.
602 /// </remarks>
603 /// <param name="filename">The terrain file to save</param>
604 /// <param name="fileWidth">The number of tiles to save along the X axis.</param>
605 /// <param name="fileHeight">The number of tiles to save along the Y axis.</param>
606 /// <param name="fileStartX">The map x co-ordinate at which to begin the save.</param>
607 /// <param name="fileStartY">The may y co-ordinate at which to begin the save.</param>
608 public void SaveToFile(string filename, int fileWidth, int fileHeight, int fileStartX, int fileStartY)
609 {
610 int offsetX = (int)m_scene.RegionInfo.RegionLocX - fileStartX;
611 int offsetY = (int)m_scene.RegionInfo.RegionLocY - fileStartY;
612  
613 if (offsetX < 0 || offsetX >= fileWidth || offsetY < 0 || offsetY >= fileHeight)
614 {
615 MainConsole.Instance.OutputFormat(
616 "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.",
617 m_scene.RegionInfo.RegionLocX, m_scene.RegionInfo.RegionLocY, fileWidth, fileStartX, fileHeight, fileStartY);
618  
619 return;
620 }
621  
622 // this region is included in the tile request
623 foreach (KeyValuePair<string, ITerrainLoader> loader in m_loaders)
624 {
625 if (filename.EndsWith(loader.Key) && loader.Value.SupportsTileSave())
626 {
627 lock (m_scene)
628 {
629 loader.Value.SaveFile(m_channel, filename, offsetX, offsetY,
630 fileWidth, fileHeight,
631 (int)m_scene.RegionInfo.RegionSizeX,
632 (int)m_scene.RegionInfo.RegionSizeY);
633  
634 MainConsole.Instance.OutputFormat(
635 "Saved terrain from ({0},{1}) to ({2},{3}) from {4} to {5}",
636 fileStartX, fileStartY, fileStartX + fileWidth - 1, fileStartY + fileHeight - 1,
637 m_scene.RegionInfo.RegionName, filename);
638 }
639  
640 return;
641 }
642 }
643  
644 MainConsole.Instance.OutputFormat(
645 "ERROR: Could not save terrain from {0} to {1}. Valid file extensions are {2}",
646 m_scene.RegionInfo.RegionName, filename, m_supportFileExtensionsForTileSave);
647 }
648  
649 /// <summary>
650 /// Performs updates to the region periodically, synchronising physics and other heightmap aware sections
651 /// </summary>
652 private void EventManager_OnTerrainTick()
653 {
654 if (m_tainted)
655 {
656 m_tainted = false;
657 m_scene.PhysicsScene.SetTerrain(m_channel.GetFloatsSerialised());
658 m_scene.SaveTerrain();
659  
660 // Clients who look at the map will never see changes after they looked at the map, so i've commented this out.
661 //m_scene.CreateTerrainTexture(true);
662 }
663 }
664  
665 /// <summary>
666 /// Processes commandline input. Do not call directly.
667 /// </summary>
668 /// <param name="args">Commandline arguments</param>
669 private void EventManager_OnPluginConsole(string[] args)
670 {
671 if (args[0] == "terrain")
672 {
673 if (args.Length == 1)
674 {
675 m_commander.ProcessConsoleCommand("help", new string[0]);
676 return;
677 }
678  
679 string[] tmpArgs = new string[args.Length - 2];
680 int i;
681 for (i = 2; i < args.Length; i++)
682 tmpArgs[i - 2] = args[i];
683  
684 m_commander.ProcessConsoleCommand(args[1], tmpArgs);
685 }
686 }
687  
688 /// <summary>
689 /// Installs terrain brush hook to IClientAPI
690 /// </summary>
691 /// <param name="client"></param>
692 private void EventManager_OnNewClient(IClientAPI client)
693 {
694 client.OnModifyTerrain += client_OnModifyTerrain;
695 client.OnBakeTerrain += client_OnBakeTerrain;
696 client.OnLandUndo += client_OnLandUndo;
697 client.OnUnackedTerrain += client_OnUnackedTerrain;
698 }
699  
700 /// <summary>
701 /// Checks to see if the terrain has been modified since last check
702 /// but won't attempt to limit those changes to the limits specified in the estate settings
703 /// currently invoked by the command line operations in the region server only
704 /// </summary>
705 private void CheckForTerrainUpdates()
706 {
707 CheckForTerrainUpdates(false);
708 }
709  
710 /// <summary>
711 /// Checks to see if the terrain has been modified since last check.
712 /// If it has been modified, every all the terrain patches are sent to the client.
713 /// If the call is asked to respect the estate settings for terrain_raise_limit and
714 /// terrain_lower_limit, it will clamp terrain updates between these values
715 /// currently invoked by client_OnModifyTerrain only and not the Commander interfaces
716 /// <param name="respectEstateSettings">should height map deltas be limited to the estate settings limits</param>
717 /// </summary>
718 private void CheckForTerrainUpdates(bool respectEstateSettings)
719 {
720 bool shouldTaint = false;
721 float[] terrHeights = m_channel.GetFloatsSerialised();
722 int x;
723 for (x = 0; x < m_channel.Width; x += Constants.TerrainPatchSize)
724 {
725 int y;
726 for (y = 0; y < m_channel.Height; y += Constants.TerrainPatchSize)
727 {
728 if (m_channel.Tainted(x, y))
729 {
730 // If we should respect the estate settings then
731 // fixup and height deltas that don't respect them.
732 // Note that LimitChannelChanges() modifies the TerrainChannel with the limited height values.
733 if (respectEstateSettings && LimitChannelChanges(x, y))
734 {
735 // Terrain heights were modified. Refetch the terrain info.
736 terrHeights = m_channel.GetFloatsSerialised();
737 }
738  
739 // m_log.DebugFormat("{0} Patch modified. Sending (x,y) = ({1},{2})", LogHeader, x, y);
740 SendToClients(terrHeights, x, y);
741 shouldTaint = true;
742 }
743 }
744 }
745 if (shouldTaint)
746 {
747 m_scene.EventManager.TriggerTerrainTainted();
748 m_tainted = true;
749 }
750 }
751  
752 /// <summary>
753 /// Checks to see height deltas in the tainted terrain patch at xStart ,yStart
754 /// are all within the current estate limits
755 /// <returns>true if changes were limited, false otherwise</returns>
756 /// </summary>
757 private bool LimitChannelChanges(int xStart, int yStart)
758 {
759 bool changesLimited = false;
760 double minDelta = m_scene.RegionInfo.RegionSettings.TerrainLowerLimit;
761 double maxDelta = m_scene.RegionInfo.RegionSettings.TerrainRaiseLimit;
762  
763 // loop through the height map for this patch and compare it against
764 // the revert map
765 for (int x = xStart; x < xStart + Constants.TerrainPatchSize; x++)
766 {
767 for (int y = yStart; y < yStart + Constants.TerrainPatchSize; y++)
768 {
769  
770 double requestedHeight = m_channel[x, y];
771 double bakedHeight = m_revert[x, y];
772 double requestedDelta = requestedHeight - bakedHeight;
773  
774 if (requestedDelta > maxDelta)
775 {
776 m_channel[x, y] = bakedHeight + maxDelta;
777 changesLimited = true;
778 }
779 else if (requestedDelta < minDelta)
780 {
781 m_channel[x, y] = bakedHeight + minDelta; //as lower is a -ve delta
782 changesLimited = true;
783 }
784 }
785 }
786  
787 return changesLimited;
788 }
789  
790 private void client_OnLandUndo(IClientAPI client)
791 {
792 lock (m_undo)
793 {
794 if (m_undo.Count > 0)
795 {
796 LandUndoState goback = m_undo.Pop();
797 if (goback != null)
798 goback.PlaybackState();
799 }
800 }
801 }
802  
803 /// <summary>
804 /// Sends a copy of the current terrain to the scenes clients
805 /// </summary>
806 /// <param name="serialised">A copy of the terrain as a 1D float array of size w*h</param>
807 /// <param name="x">The patch corner to send</param>
808 /// <param name="y">The patch corner to send</param>
809 private void SendToClients(float[] heightMap, int x, int y)
810 {
811 m_scene.ForEachClient(
812 delegate(IClientAPI controller)
813 { controller.SendLayerData( x / Constants.TerrainPatchSize, y / Constants.TerrainPatchSize, heightMap); }
814 );
815 }
816  
817 private void client_OnModifyTerrain(UUID user, float height, float seconds, byte size, byte action,
818 float north, float west, float south, float east, UUID agentId)
819 {
820 bool god = m_scene.Permissions.IsGod(user);
821 bool allowed = false;
822 if (north == south && east == west)
823 {
824 if (m_painteffects.ContainsKey((StandardTerrainEffects) action))
825 {
826 bool[,] allowMask = new bool[m_channel.Width,m_channel.Height];
827 allowMask.Initialize();
828 int n = size + 1;
829 if (n > 2)
830 n = 4;
831  
832 int zx = (int) (west + 0.5);
833 int zy = (int) (north + 0.5);
834  
835 int dx;
836 for (dx=-n; dx<=n; dx++)
837 {
838 int dy;
839 for (dy=-n; dy<=n; dy++)
840 {
841 int x = zx + dx;
842 int y = zy + dy;
843 if (x>=0 && y>=0 && x<m_channel.Width && y<m_channel.Height)
844 {
845 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
846 {
847 allowMask[x, y] = true;
848 allowed = true;
849 }
850 }
851 }
852 }
853 if (allowed)
854 {
855 StoreUndoState();
856 m_painteffects[(StandardTerrainEffects) action].PaintEffect(
857 m_channel, allowMask, west, south, height, size, seconds);
858  
859 CheckForTerrainUpdates(!god); //revert changes outside estate limits
860 }
861 }
862 else
863 {
864 m_log.Debug("Unknown terrain brush type " + action);
865 }
866 }
867 else
868 {
869 if (m_floodeffects.ContainsKey((StandardTerrainEffects) action))
870 {
871 bool[,] fillArea = new bool[m_channel.Width,m_channel.Height];
872 fillArea.Initialize();
873  
874 int x;
875 for (x = 0; x < m_channel.Width; x++)
876 {
877 int y;
878 for (y = 0; y < m_channel.Height; y++)
879 {
880 if (x < east && x > west)
881 {
882 if (y < north && y > south)
883 {
884 if (m_scene.Permissions.CanTerraformLand(agentId, new Vector3(x,y,0)))
885 {
886 fillArea[x, y] = true;
887 allowed = true;
888 }
889 }
890 }
891 }
892 }
893  
894 if (allowed)
895 {
896 StoreUndoState();
897 m_floodeffects[(StandardTerrainEffects) action].FloodEffect(
898 m_channel, fillArea, size);
899  
900 CheckForTerrainUpdates(!god); //revert changes outside estate limits
901 }
902 }
903 else
904 {
905 m_log.Debug("Unknown terrain flood type " + action);
906 }
907 }
908 }
909  
910 private void client_OnBakeTerrain(IClientAPI remoteClient)
911 {
912 // Not a good permissions check (see client_OnModifyTerrain above), need to check the entire area.
913 // for now check a point in the centre of the region
914  
915 if (m_scene.Permissions.CanIssueEstateCommand(remoteClient.AgentId, true))
916 {
917 InterfaceBakeTerrain(null); //bake terrain does not use the passed in parameter
918 }
919 }
920  
921 protected void client_OnUnackedTerrain(IClientAPI client, int patchX, int patchY)
922 {
923 //m_log.Debug("Terrain packet unacked, resending patch: " + patchX + " , " + patchY);
924 client.SendLayerData(patchX, patchY, m_scene.Heightmap.GetFloatsSerialised());
925 }
926  
927 private void StoreUndoState()
928 {
929 lock (m_undo)
930 {
931 if (m_undo.Count > 0)
932 {
933 LandUndoState last = m_undo.Peek();
934 if (last != null)
935 {
936 if (last.Compare(m_channel))
937 return;
938 }
939 }
940  
941 LandUndoState nUndo = new LandUndoState(this, m_channel);
942 m_undo.Push(nUndo);
943 }
944 }
945  
946 #region Console Commands
947  
948 private void InterfaceLoadFile(Object[] args)
949 {
950 LoadFromFile((string) args[0]);
951 CheckForTerrainUpdates();
952 }
953  
954 private void InterfaceLoadTileFile(Object[] args)
955 {
956 LoadFromFile((string) args[0],
957 (int) args[1],
958 (int) args[2],
959 (int) args[3],
960 (int) args[4]);
961 CheckForTerrainUpdates();
962 }
963  
964 private void InterfaceSaveFile(Object[] args)
965 {
966 SaveToFile((string) args[0]);
967 }
968  
969 private void InterfaceSaveTileFile(Object[] args)
970 {
971 SaveToFile((string)args[0],
972 (int)args[1],
973 (int)args[2],
974 (int)args[3],
975 (int)args[4]);
976 }
977  
978 private void InterfaceBakeTerrain(Object[] args)
979 {
980 UpdateRevertMap();
981 }
982  
983 private void InterfaceRevertTerrain(Object[] args)
984 {
985 int x, y;
986 for (x = 0; x < m_channel.Width; x++)
987 for (y = 0; y < m_channel.Height; y++)
988 m_channel[x, y] = m_revert[x, y];
989  
990 CheckForTerrainUpdates();
991 }
992  
993 private void InterfaceFlipTerrain(Object[] args)
994 {
995 String direction = (String)args[0];
996  
997 if (direction.ToLower().StartsWith("y"))
998 {
999 for (int x = 0; x < m_channel.Width; x++)
1000 {
1001 for (int y = 0; y < m_channel.Height / 2; y++)
1002 {
1003 double height = m_channel[x, y];
1004 double flippedHeight = m_channel[x, (int)m_channel.Height - 1 - y];
1005 m_channel[x, y] = flippedHeight;
1006 m_channel[x, (int)m_channel.Height - 1 - y] = height;
1007  
1008 }
1009 }
1010 }
1011 else if (direction.ToLower().StartsWith("x"))
1012 {
1013 for (int y = 0; y < m_channel.Height; y++)
1014 {
1015 for (int x = 0; x < m_channel.Width / 2; x++)
1016 {
1017 double height = m_channel[x, y];
1018 double flippedHeight = m_channel[(int)m_channel.Width - 1 - x, y];
1019 m_channel[x, y] = flippedHeight;
1020 m_channel[(int)m_channel.Width - 1 - x, y] = height;
1021  
1022 }
1023 }
1024 }
1025 else
1026 {
1027 m_log.Error("Unrecognised direction - need x or y");
1028 }
1029  
1030  
1031 CheckForTerrainUpdates();
1032 }
1033  
1034 private void InterfaceRescaleTerrain(Object[] args)
1035 {
1036 double desiredMin = (double)args[0];
1037 double desiredMax = (double)args[1];
1038  
1039 // determine desired scaling factor
1040 double desiredRange = desiredMax - desiredMin;
1041 //m_log.InfoFormat("Desired {0}, {1} = {2}", new Object[] { desiredMin, desiredMax, desiredRange });
1042  
1043 if (desiredRange == 0d)
1044 {
1045 // delta is zero so flatten at requested height
1046 InterfaceFillTerrain(new Object[] { args[1] });
1047 }
1048 else
1049 {
1050 //work out current heightmap range
1051 double currMin = double.MaxValue;
1052 double currMax = double.MinValue;
1053  
1054 int width = m_channel.Width;
1055 int height = m_channel.Height;
1056  
1057 for (int x = 0; x < width; x++)
1058 {
1059 for (int y = 0; y < height; y++)
1060 {
1061 double currHeight = m_channel[x, y];
1062 if (currHeight < currMin)
1063 {
1064 currMin = currHeight;
1065 }
1066 else if (currHeight > currMax)
1067 {
1068 currMax = currHeight;
1069 }
1070 }
1071 }
1072  
1073 double currRange = currMax - currMin;
1074 double scale = desiredRange / currRange;
1075  
1076 //m_log.InfoFormat("Current {0}, {1} = {2}", new Object[] { currMin, currMax, currRange });
1077 //m_log.InfoFormat("Scale = {0}", scale);
1078  
1079 // scale the heightmap accordingly
1080 for (int x = 0; x < width; x++)
1081 {
1082 for (int y = 0; y < height; y++)
1083 {
1084 double currHeight = m_channel[x, y] - currMin;
1085 m_channel[x, y] = desiredMin + (currHeight * scale);
1086 }
1087 }
1088  
1089 CheckForTerrainUpdates();
1090 }
1091  
1092 }
1093  
1094 private void InterfaceElevateTerrain(Object[] args)
1095 {
1096 int x, y;
1097 for (x = 0; x < m_channel.Width; x++)
1098 for (y = 0; y < m_channel.Height; y++)
1099 m_channel[x, y] += (double) args[0];
1100 CheckForTerrainUpdates();
1101 }
1102  
1103 private void InterfaceMultiplyTerrain(Object[] args)
1104 {
1105 int x, y;
1106 for (x = 0; x < m_channel.Width; x++)
1107 for (y = 0; y < m_channel.Height; y++)
1108 m_channel[x, y] *= (double) args[0];
1109 CheckForTerrainUpdates();
1110 }
1111  
1112 private void InterfaceLowerTerrain(Object[] args)
1113 {
1114 int x, y;
1115 for (x = 0; x < m_channel.Width; x++)
1116 for (y = 0; y < m_channel.Height; y++)
1117 m_channel[x, y] -= (double) args[0];
1118 CheckForTerrainUpdates();
1119 }
1120  
1121 private void InterfaceFillTerrain(Object[] args)
1122 {
1123 int x, y;
1124  
1125 for (x = 0; x < m_channel.Width; x++)
1126 for (y = 0; y < m_channel.Height; y++)
1127 m_channel[x, y] = (double) args[0];
1128 CheckForTerrainUpdates();
1129 }
1130  
1131 private void InterfaceMinTerrain(Object[] args)
1132 {
1133 int x, y;
1134 for (x = 0; x < m_channel.Width; x++)
1135 {
1136 for (y = 0; y < m_channel.Height; y++)
1137 {
1138 m_channel[x, y] = Math.Max((double)args[0], m_channel[x, y]);
1139 }
1140 }
1141 CheckForTerrainUpdates();
1142 }
1143  
1144 private void InterfaceMaxTerrain(Object[] args)
1145 {
1146 int x, y;
1147 for (x = 0; x < m_channel.Width; x++)
1148 {
1149 for (y = 0; y < m_channel.Height; y++)
1150 {
1151 m_channel[x, y] = Math.Min((double)args[0], m_channel[x, y]);
1152 }
1153 }
1154 CheckForTerrainUpdates();
1155 }
1156  
1157 private void InterfaceShowDebugStats(Object[] args)
1158 {
1159 double max = Double.MinValue;
1160 double min = double.MaxValue;
1161 double sum = 0;
1162  
1163 int x;
1164 for (x = 0; x < m_channel.Width; x++)
1165 {
1166 int y;
1167 for (y = 0; y < m_channel.Height; y++)
1168 {
1169 sum += m_channel[x, y];
1170 if (max < m_channel[x, y])
1171 max = m_channel[x, y];
1172 if (min > m_channel[x, y])
1173 min = m_channel[x, y];
1174 }
1175 }
1176  
1177 double avg = sum / (m_channel.Height * m_channel.Width);
1178  
1179 m_log.Info("Channel " + m_channel.Width + "x" + m_channel.Height);
1180 m_log.Info("max/min/avg/sum: " + max + "/" + min + "/" + avg + "/" + sum);
1181 }
1182  
1183 private void InterfaceEnableExperimentalBrushes(Object[] args)
1184 {
1185 if ((bool) args[0])
1186 {
1187 m_painteffects[StandardTerrainEffects.Revert] = new WeatherSphere();
1188 m_painteffects[StandardTerrainEffects.Flatten] = new OlsenSphere();
1189 m_painteffects[StandardTerrainEffects.Smooth] = new ErodeSphere();
1190 }
1191 else
1192 {
1193 InstallDefaultEffects();
1194 }
1195 }
1196  
1197 private void InterfaceRunPluginEffect(Object[] args)
1198 {
1199 string firstArg = (string)args[0];
1200 if (firstArg == "list")
1201 {
1202 m_log.Info("List of loaded plugins");
1203 foreach (KeyValuePair<string, ITerrainEffect> kvp in m_plugineffects)
1204 {
1205 m_log.Info(kvp.Key);
1206 }
1207 return;
1208 }
1209 if (firstArg == "reload")
1210 {
1211 LoadPlugins();
1212 return;
1213 }
1214 if (m_plugineffects.ContainsKey(firstArg))
1215 {
1216 m_plugineffects[firstArg].RunEffect(m_channel);
1217 CheckForTerrainUpdates();
1218 }
1219 else
1220 {
1221 m_log.Warn("No such plugin effect loaded.");
1222 }
1223 }
1224  
1225 private void InstallInterfaces()
1226 {
1227 Command loadFromFileCommand =
1228 new Command("load", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadFile, "Loads a terrain from a specified file.");
1229 loadFromFileCommand.AddArgument("filename",
1230 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1231 m_supportedFileExtensions, "String");
1232  
1233 Command saveToFileCommand =
1234 new Command("save", CommandIntentions.COMMAND_NON_HAZARDOUS, InterfaceSaveFile, "Saves the current heightmap to a specified file.");
1235 saveToFileCommand.AddArgument("filename",
1236 "The destination filename for your heightmap, the file extension determines the format to save in. Supported extensions include: " +
1237 m_supportedFileExtensions, "String");
1238  
1239 Command loadFromTileCommand =
1240 new Command("load-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLoadTileFile, "Loads a terrain from a section of a larger file.");
1241 loadFromTileCommand.AddArgument("filename",
1242 "The file you wish to load from, the file extension determines the loader to be used. Supported extensions include: " +
1243 m_supportedFileExtensions, "String");
1244 loadFromTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1245 loadFromTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1246 loadFromTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1247 "Integer");
1248 loadFromTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first section on the file",
1249 "Integer");
1250  
1251 Command saveToTileCommand =
1252 new Command("save-tile", CommandIntentions.COMMAND_HAZARDOUS, InterfaceSaveTileFile, "Saves the current heightmap to the larger file.");
1253 saveToTileCommand.AddArgument("filename",
1254 "The file you wish to save to, the file extension determines the loader to be used. Supported extensions include: " +
1255 m_supportFileExtensionsForTileSave, "String");
1256 saveToTileCommand.AddArgument("file width", "The width of the file in tiles", "Integer");
1257 saveToTileCommand.AddArgument("file height", "The height of the file in tiles", "Integer");
1258 saveToTileCommand.AddArgument("minimum X tile", "The X region coordinate of the first section on the file",
1259 "Integer");
1260 saveToTileCommand.AddArgument("minimum Y tile", "The Y region coordinate of the first tile on the file\n"
1261 + "= Example =\n"
1262 + "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"
1263 + " # terrain save-tile ST06.png 2 3 9910 10234\n",
1264 "Integer");
1265  
1266 // Terrain adjustments
1267 Command fillRegionCommand =
1268 new Command("fill", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFillTerrain, "Fills the current heightmap with a specified value.");
1269 fillRegionCommand.AddArgument("value", "The numeric value of the height you wish to set your region to.",
1270 "Double");
1271  
1272 Command elevateCommand =
1273 new Command("elevate", CommandIntentions.COMMAND_HAZARDOUS, InterfaceElevateTerrain, "Raises the current heightmap by the specified amount.");
1274 elevateCommand.AddArgument("amount", "The amount of height to add to the terrain in meters.", "Double");
1275  
1276 Command lowerCommand =
1277 new Command("lower", CommandIntentions.COMMAND_HAZARDOUS, InterfaceLowerTerrain, "Lowers the current heightmap by the specified amount.");
1278 lowerCommand.AddArgument("amount", "The amount of height to remove from the terrain in meters.", "Double");
1279  
1280 Command multiplyCommand =
1281 new Command("multiply", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMultiplyTerrain, "Multiplies the heightmap by the value specified.");
1282 multiplyCommand.AddArgument("value", "The value to multiply the heightmap by.", "Double");
1283  
1284 Command bakeRegionCommand =
1285 new Command("bake", CommandIntentions.COMMAND_HAZARDOUS, InterfaceBakeTerrain, "Saves the current terrain into the regions revert map.");
1286 Command revertRegionCommand =
1287 new Command("revert", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRevertTerrain, "Loads the revert map terrain into the regions heightmap.");
1288  
1289 Command flipCommand =
1290 new Command("flip", CommandIntentions.COMMAND_HAZARDOUS, InterfaceFlipTerrain, "Flips the current terrain about the X or Y axis");
1291 flipCommand.AddArgument("direction", "[x|y] the direction to flip the terrain in", "String");
1292  
1293 Command rescaleCommand =
1294 new Command("rescale", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRescaleTerrain, "Rescales the current terrain to fit between the given min and max heights");
1295 rescaleCommand.AddArgument("min", "min terrain height after rescaling", "Double");
1296 rescaleCommand.AddArgument("max", "max terrain height after rescaling", "Double");
1297  
1298 Command minCommand = new Command("min", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMinTerrain, "Sets the minimum terrain height to the specified value.");
1299 minCommand.AddArgument("min", "terrain height to use as minimum", "Double");
1300  
1301 Command maxCommand = new Command("max", CommandIntentions.COMMAND_HAZARDOUS, InterfaceMaxTerrain, "Sets the maximum terrain height to the specified value.");
1302 maxCommand.AddArgument("min", "terrain height to use as maximum", "Double");
1303  
1304  
1305 // Debug
1306 Command showDebugStatsCommand =
1307 new Command("stats", CommandIntentions.COMMAND_STATISTICAL, InterfaceShowDebugStats,
1308 "Shows some information about the regions heightmap for debugging purposes.");
1309  
1310 Command experimentalBrushesCommand =
1311 new Command("newbrushes", CommandIntentions.COMMAND_HAZARDOUS, InterfaceEnableExperimentalBrushes,
1312 "Enables experimental brushes which replace the standard terrain brushes. WARNING: This is a debug setting and may be removed at any time.");
1313 experimentalBrushesCommand.AddArgument("Enabled?", "true / false - Enable new brushes", "Boolean");
1314  
1315 //Plugins
1316 Command pluginRunCommand =
1317 new Command("effect", CommandIntentions.COMMAND_HAZARDOUS, InterfaceRunPluginEffect, "Runs a specified plugin effect");
1318 pluginRunCommand.AddArgument("name", "The plugin effect you wish to run, or 'list' to see all plugins", "String");
1319  
1320 m_commander.RegisterCommand("load", loadFromFileCommand);
1321 m_commander.RegisterCommand("load-tile", loadFromTileCommand);
1322 m_commander.RegisterCommand("save", saveToFileCommand);
1323 m_commander.RegisterCommand("save-tile", saveToTileCommand);
1324 m_commander.RegisterCommand("fill", fillRegionCommand);
1325 m_commander.RegisterCommand("elevate", elevateCommand);
1326 m_commander.RegisterCommand("lower", lowerCommand);
1327 m_commander.RegisterCommand("multiply", multiplyCommand);
1328 m_commander.RegisterCommand("bake", bakeRegionCommand);
1329 m_commander.RegisterCommand("revert", revertRegionCommand);
1330 m_commander.RegisterCommand("newbrushes", experimentalBrushesCommand);
1331 m_commander.RegisterCommand("stats", showDebugStatsCommand);
1332 m_commander.RegisterCommand("effect", pluginRunCommand);
1333 m_commander.RegisterCommand("flip", flipCommand);
1334 m_commander.RegisterCommand("rescale", rescaleCommand);
1335 m_commander.RegisterCommand("min", minCommand);
1336 m_commander.RegisterCommand("max", maxCommand);
1337  
1338 // Add this to our scene so scripts can call these functions
1339 m_scene.RegisterModuleCommander(m_commander);
1340 }
1341  
1342  
1343 #endregion
1344  
1345 }
1346 }