clockwerk-opensim-stable – Blame information for rev 1

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