/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Reflection; using System.Timers; using OpenMetaverse; using log4net; using Mono.Addins; using Nini.Config; using OpenSim.Framework; using OpenSim.Region.CoreModules.Framework.InterfaceCommander; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using System.Xml; using System.Xml.Serialization; using System.IO; namespace OpenSim.Region.OptionalModules.World.TreePopulator { /// /// Version 2.02 - Still hacky /// [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "TreePopulatorModule")] public class TreePopulatorModule : INonSharedRegionModule, ICommandableModule, IVegetationModule { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private readonly Commander m_commander = new Commander("tree"); private Scene m_scene; [XmlRootAttribute(ElementName = "Copse", IsNullable = false)] public class Copse { public string m_name; public Boolean m_frozen; public Tree m_tree_type; public int m_tree_quantity; public float m_treeline_low; public float m_treeline_high; public Vector3 m_seed_point; public double m_range; public Vector3 m_initial_scale; public Vector3 m_maximum_scale; public Vector3 m_rate; [XmlIgnore] public Boolean m_planted; [XmlIgnore] public List m_trees; public Copse() { } public Copse(string fileName, Boolean planted) { Copse cp = (Copse)DeserializeObject(fileName); this.m_name = cp.m_name; this.m_frozen = cp.m_frozen; this.m_tree_quantity = cp.m_tree_quantity; this.m_treeline_high = cp.m_treeline_high; this.m_treeline_low = cp.m_treeline_low; this.m_range = cp.m_range; this.m_tree_type = cp.m_tree_type; this.m_seed_point = cp.m_seed_point; this.m_initial_scale = cp.m_initial_scale; this.m_maximum_scale = cp.m_maximum_scale; this.m_initial_scale = cp.m_initial_scale; this.m_rate = cp.m_rate; this.m_planted = planted; this.m_trees = new List(); } public Copse(string copsedef) { char[] delimiterChars = {':', ';'}; string[] field = copsedef.Split(delimiterChars); this.m_name = field[1].Trim(); this.m_frozen = (copsedef[0] == 'F'); this.m_tree_quantity = int.Parse(field[2]); this.m_treeline_high = float.Parse(field[3], Culture.NumberFormatInfo); this.m_treeline_low = float.Parse(field[4], Culture.NumberFormatInfo); this.m_range = double.Parse(field[5], Culture.NumberFormatInfo); this.m_tree_type = (Tree) Enum.Parse(typeof(Tree),field[6]); this.m_seed_point = Vector3.Parse(field[7]); this.m_initial_scale = Vector3.Parse(field[8]); this.m_maximum_scale = Vector3.Parse(field[9]); this.m_rate = Vector3.Parse(field[10]); this.m_planted = true; this.m_trees = new List(); } public Copse(string name, int quantity, float high, float low, double range, Vector3 point, Tree type, Vector3 scale, Vector3 max_scale, Vector3 rate, List trees) { this.m_name = name; this.m_frozen = false; this.m_tree_quantity = quantity; this.m_treeline_high = high; this.m_treeline_low = low; this.m_range = range; this.m_tree_type = type; this.m_seed_point = point; this.m_initial_scale = scale; this.m_maximum_scale = max_scale; this.m_rate = rate; this.m_planted = false; this.m_trees = trees; } public override string ToString() { string frozen = (this.m_frozen ? "F" : "A"); return string.Format("{0}TPM: {1}; {2}; {3:0.0}; {4:0.0}; {5:0.0}; {6}; {7:0.0}; {8:0.0}; {9:0.0}; {10:0.00};", frozen, this.m_name, this.m_tree_quantity, this.m_treeline_high, this.m_treeline_low, this.m_range, this.m_tree_type, this.m_seed_point.ToString(), this.m_initial_scale.ToString(), this.m_maximum_scale.ToString(), this.m_rate.ToString()); } } private List m_copse; private double m_update_ms = 1000.0; // msec between updates private bool m_active_trees = false; Timer CalculateTrees; #region ICommandableModule Members public ICommander CommandInterface { get { return m_commander; } } #endregion #region Region Module interface public void Initialise(IConfigSource config) { // ini file settings try { m_active_trees = config.Configs["Trees"].GetBoolean("active_trees", m_active_trees); } catch (Exception) { m_log.Debug("[TREES]: ini failure for active_trees - using default"); } try { m_update_ms = config.Configs["Trees"].GetDouble("update_rate", m_update_ms); } catch (Exception) { m_log.Debug("[TREES]: ini failure for update_rate - using default"); } InstallCommands(); m_log.Debug("[TREES]: Initialised tree module"); } public void AddRegion(Scene scene) { m_scene = scene; m_scene.RegisterModuleCommander(m_commander); m_scene.EventManager.OnPluginConsole += EventManager_OnPluginConsole; } public void RemoveRegion(Scene scene) { } public void RegionLoaded(Scene scene) { ReloadCopse(); if (m_copse.Count > 0) m_log.Info("[TREES]: Copse load complete"); if (m_active_trees) activeizeTreeze(true); } public void Close() { } public string Name { get { return "TreePopulatorModule"; } } public Type ReplaceableInterface { get { return null; } } #endregion //-------------------------------------------------------------- #region ICommandableModule Members private void HandleTreeActive(Object[] args) { if ((Boolean)args[0] && !m_active_trees) { m_log.InfoFormat("[TREES]: Activating Trees"); m_active_trees = true; activeizeTreeze(m_active_trees); } else if (!(Boolean)args[0] && m_active_trees) { m_log.InfoFormat("[TREES]: Trees module is no longer active"); m_active_trees = false; activeizeTreeze(m_active_trees); } else { m_log.InfoFormat("[TREES]: Trees module is already in the required state"); } } private void HandleTreeFreeze(Object[] args) { string copsename = ((string)args[0]).Trim(); Boolean freezeState = (Boolean) args[1]; foreach (Copse cp in m_copse) { if (cp.m_name == copsename && (!cp.m_frozen && freezeState || cp.m_frozen && !freezeState)) { cp.m_frozen = freezeState; foreach (UUID tree in cp.m_trees) { SceneObjectPart sop = ((SceneObjectGroup)m_scene.Entities[tree]).RootPart; sop.Name = (freezeState ? sop.Name.Replace("ATPM", "FTPM") : sop.Name.Replace("FTPM", "ATPM")); sop.ParentGroup.HasGroupChanged = true; } m_log.InfoFormat("[TREES]: Activity for copse {0} is frozen {1}", copsename, freezeState); return; } else if (cp.m_name == copsename && (cp.m_frozen && freezeState || !cp.m_frozen && !freezeState)) { m_log.InfoFormat("[TREES]: Copse {0} is already in the requested freeze state", copsename); return; } } m_log.InfoFormat("[TREES]: Copse {0} was not found - command failed", copsename); } private void HandleTreeLoad(Object[] args) { Copse copse; m_log.InfoFormat("[TREES]: Loading copse definition...."); copse = new Copse(((string)args[0]), false); foreach (Copse cp in m_copse) { if (cp.m_name == copse.m_name) { m_log.InfoFormat("[TREES]: Copse: {0} is already defined - command failed", copse.m_name); return; } } m_copse.Add(copse); m_log.InfoFormat("[TREES]: Loaded copse: {0}", copse.ToString()); } private void HandleTreePlant(Object[] args) { string copsename = ((string)args[0]).Trim(); m_log.InfoFormat("[TREES]: New tree planting for copse {0}", copsename); UUID uuid = m_scene.RegionInfo.EstateSettings.EstateOwner; foreach (Copse copse in m_copse) { if (copse.m_name == copsename) { if (!copse.m_planted) { // The first tree for a copse is created here CreateTree(uuid, copse, copse.m_seed_point); copse.m_planted = true; return; } else { m_log.InfoFormat("[TREES]: Copse {0} has already been planted", copsename); } } } m_log.InfoFormat("[TREES]: Copse {0} not found for planting", copsename); } private void HandleTreeRate(Object[] args) { m_update_ms = (double)args[0]; if (m_update_ms >= 1000.0) { if (m_active_trees) { activeizeTreeze(false); activeizeTreeze(true); } m_log.InfoFormat("[TREES]: Update rate set to {0} mSec", m_update_ms); } else { m_log.InfoFormat("[TREES]: minimum rate is 1000.0 mSec - command failed"); } } private void HandleTreeReload(Object[] args) { if (m_active_trees) { CalculateTrees.Stop(); } ReloadCopse(); if (m_active_trees) { CalculateTrees.Start(); } } private void HandleTreeRemove(Object[] args) { string copsename = ((string)args[0]).Trim(); Copse copseIdentity = null; foreach (Copse cp in m_copse) { if (cp.m_name == copsename) { copseIdentity = cp; } } if (copseIdentity != null) { foreach (UUID tree in copseIdentity.m_trees) { if (m_scene.Entities.ContainsKey(tree)) { SceneObjectPart selectedTree = ((SceneObjectGroup)m_scene.Entities[tree]).RootPart; // Delete tree and alert clients (not silent) m_scene.DeleteSceneObject(selectedTree.ParentGroup, false); } else { m_log.DebugFormat("[TREES]: Tree not in scene {0}", tree); } } copseIdentity.m_trees = new List(); m_copse.Remove(copseIdentity); m_log.InfoFormat("[TREES]: Copse {0} has been removed", copsename); } else { m_log.InfoFormat("[TREES]: Copse {0} was not found - command failed", copsename); } } private void HandleTreeStatistics(Object[] args) { m_log.InfoFormat("[TREES]: Activity State: {0}; Update Rate: {1}", m_active_trees, m_update_ms); foreach (Copse cp in m_copse) { m_log.InfoFormat("[TREES]: Copse {0}; {1} trees; frozen {2}", cp.m_name, cp.m_trees.Count, cp.m_frozen); } } private void InstallCommands() { Command treeActiveCommand = new Command("active", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeActive, "Change activity state for the trees module"); treeActiveCommand.AddArgument("activeTF", "The required activity state", "Boolean"); Command treeFreezeCommand = new Command("freeze", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeFreeze, "Freeze/Unfreeze activity for a defined copse"); treeFreezeCommand.AddArgument("copse", "The required copse", "String"); treeFreezeCommand.AddArgument("freezeTF", "The required freeze state", "Boolean"); Command treeLoadCommand = new Command("load", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeLoad, "Load a copse definition from an xml file"); treeLoadCommand.AddArgument("filename", "The (xml) file you wish to load", "String"); Command treePlantCommand = new Command("plant", CommandIntentions.COMMAND_HAZARDOUS, HandleTreePlant, "Start the planting on a copse"); treePlantCommand.AddArgument("copse", "The required copse", "String"); Command treeRateCommand = new Command("rate", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeRate, "Reset the tree update rate (mSec)"); treeRateCommand.AddArgument("updateRate", "The required update rate (minimum 1000.0)", "Double"); Command treeReloadCommand = new Command("reload", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeReload, "Reload copse definitions from the in-scene trees"); Command treeRemoveCommand = new Command("remove", CommandIntentions.COMMAND_HAZARDOUS, HandleTreeRemove, "Remove a copse definition and all its in-scene trees"); treeRemoveCommand.AddArgument("copse", "The required copse", "String"); Command treeStatisticsCommand = new Command("statistics", CommandIntentions.COMMAND_STATISTICAL, HandleTreeStatistics, "Log statistics about the trees"); m_commander.RegisterCommand("active", treeActiveCommand); m_commander.RegisterCommand("freeze", treeFreezeCommand); m_commander.RegisterCommand("load", treeLoadCommand); m_commander.RegisterCommand("plant", treePlantCommand); m_commander.RegisterCommand("rate", treeRateCommand); m_commander.RegisterCommand("reload", treeReloadCommand); m_commander.RegisterCommand("remove", treeRemoveCommand); m_commander.RegisterCommand("statistics", treeStatisticsCommand); } /// /// Processes commandline input. Do not call directly. /// /// Commandline arguments private void EventManager_OnPluginConsole(string[] args) { if (args[0] == "tree") { if (args.Length == 1) { m_commander.ProcessConsoleCommand("help", new string[0]); return; } string[] tmpArgs = new string[args.Length - 2]; int i; for (i = 2; i < args.Length; i++) { tmpArgs[i - 2] = args[i]; } m_commander.ProcessConsoleCommand(args[1], tmpArgs); } } #endregion #region IVegetationModule Members public SceneObjectGroup AddTree( UUID uuid, UUID groupID, Vector3 scale, Quaternion rotation, Vector3 position, Tree treeType, bool newTree) { PrimitiveBaseShape treeShape = new PrimitiveBaseShape(); treeShape.PathCurve = 16; treeShape.PathEnd = 49900; treeShape.PCode = newTree ? (byte)PCode.NewTree : (byte)PCode.Tree; treeShape.Scale = scale; treeShape.State = (byte)treeType; return m_scene.AddNewPrim(uuid, groupID, position, rotation, treeShape); } #endregion #region IEntityCreator Members protected static readonly PCode[] creationCapabilities = new PCode[] { PCode.NewTree, PCode.Tree }; public PCode[] CreationCapabilities { get { return creationCapabilities; } } public SceneObjectGroup CreateEntity( UUID ownerID, UUID groupID, Vector3 pos, Quaternion rot, PrimitiveBaseShape shape) { if (Array.IndexOf(creationCapabilities, (PCode)shape.PCode) < 0) { m_log.DebugFormat("[VEGETATION]: PCode {0} not handled by {1}", shape.PCode, Name); return null; } SceneObjectGroup sceneObject = new SceneObjectGroup(ownerID, pos, rot, shape); SceneObjectPart rootPart = sceneObject.GetPart(sceneObject.UUID); rootPart.AddFlag(PrimFlags.Phantom); m_scene.AddNewSceneObject(sceneObject, true); sceneObject.SetGroup(groupID, null); return sceneObject; } #endregion //-------------------------------------------------------------- #region Tree Utilities static public void SerializeObject(string fileName, Object obj) { try { XmlSerializer xs = new XmlSerializer(typeof(Copse)); using (XmlTextWriter writer = new XmlTextWriter(fileName, Util.UTF8)) { writer.Formatting = Formatting.Indented; xs.Serialize(writer, obj); } } catch (SystemException ex) { throw new ApplicationException("Unexpected failure in Tree serialization", ex); } } static public object DeserializeObject(string fileName) { try { XmlSerializer xs = new XmlSerializer(typeof(Copse)); using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read)) return xs.Deserialize(fs); } catch (SystemException ex) { throw new ApplicationException("Unexpected failure in Tree de-serialization", ex); } } private void ReloadCopse() { m_copse = new List(); EntityBase[] objs = m_scene.GetEntities(); foreach (EntityBase obj in objs) { if (obj is SceneObjectGroup) { SceneObjectGroup grp = (SceneObjectGroup)obj; if (grp.Name.Length > 5 && (grp.Name.Substring(0, 5) == "ATPM:" || grp.Name.Substring(0, 5) == "FTPM:")) { // Create a new copse definition or add uuid to an existing definition try { Boolean copsefound = false; Copse copse = new Copse(grp.Name); foreach (Copse cp in m_copse) { if (cp.m_name == copse.m_name) { copsefound = true; cp.m_trees.Add(grp.UUID); //m_log.DebugFormat("[TREES]: Found tree {0}", grp.UUID); } } if (!copsefound) { m_log.InfoFormat("[TREES]: Found copse {0}", grp.Name); m_copse.Add(copse); copse.m_trees.Add(grp.UUID); } } catch { m_log.InfoFormat("[TREES]: Ill formed copse definition {0} - ignoring", grp.Name); } } } } } #endregion private void activeizeTreeze(bool activeYN) { if (activeYN) { CalculateTrees = new Timer(m_update_ms); CalculateTrees.Elapsed += CalculateTrees_Elapsed; CalculateTrees.Start(); } else { CalculateTrees.Stop(); } } private void growTrees() { foreach (Copse copse in m_copse) { if (!copse.m_frozen) { foreach (UUID tree in copse.m_trees) { if (m_scene.Entities.ContainsKey(tree)) { SceneObjectPart s_tree = ((SceneObjectGroup)m_scene.Entities[tree]).RootPart; if (s_tree.Scale.X < copse.m_maximum_scale.X && s_tree.Scale.Y < copse.m_maximum_scale.Y && s_tree.Scale.Z < copse.m_maximum_scale.Z) { s_tree.Scale += copse.m_rate; s_tree.ParentGroup.HasGroupChanged = true; s_tree.ScheduleFullUpdate(); } } else { m_log.DebugFormat("[TREES]: Tree not in scene {0}", tree); } } } } } private void seedTrees() { foreach (Copse copse in m_copse) { if (!copse.m_frozen) { foreach (UUID tree in copse.m_trees) { if (m_scene.Entities.ContainsKey(tree)) { SceneObjectPart s_tree = ((SceneObjectGroup)m_scene.Entities[tree]).RootPart; if (copse.m_trees.Count < copse.m_tree_quantity) { // Tree has grown enough to seed if it has grown by at least 25% of seeded to full grown height if (s_tree.Scale.Z > copse.m_initial_scale.Z + (copse.m_maximum_scale.Z - copse.m_initial_scale.Z) / 4.0) { if (Util.RandomClass.NextDouble() > 0.75) { SpawnChild(copse, s_tree); } } } } else { m_log.DebugFormat("[TREES]: Tree not in scene {0}", tree); } } } } } private void killTrees() { foreach (Copse copse in m_copse) { if (!copse.m_frozen && copse.m_trees.Count >= copse.m_tree_quantity) { foreach (UUID tree in copse.m_trees) { double killLikelyhood = 0.0; if (m_scene.Entities.ContainsKey(tree)) { SceneObjectPart selectedTree = ((SceneObjectGroup)m_scene.Entities[tree]).RootPart; double selectedTreeScale = Math.Sqrt(Math.Pow(selectedTree.Scale.X, 2) + Math.Pow(selectedTree.Scale.Y, 2) + Math.Pow(selectedTree.Scale.Z, 2)); foreach (UUID picktree in copse.m_trees) { if (picktree != tree) { SceneObjectPart pickedTree = ((SceneObjectGroup)m_scene.Entities[picktree]).RootPart; double pickedTreeScale = Math.Sqrt(Math.Pow(pickedTree.Scale.X, 2) + Math.Pow(pickedTree.Scale.Y, 2) + Math.Pow(pickedTree.Scale.Z, 2)); double pickedTreeDistance = Vector3.Distance(pickedTree.AbsolutePosition, selectedTree.AbsolutePosition); killLikelyhood += (selectedTreeScale / (pickedTreeScale * pickedTreeDistance)) * 0.1; } } if (Util.RandomClass.NextDouble() < killLikelyhood) { // Delete tree and alert clients (not silent) m_scene.DeleteSceneObject(selectedTree.ParentGroup, false); copse.m_trees.Remove(selectedTree.ParentGroup.UUID); break; } } else { m_log.DebugFormat("[TREES]: Tree not in scene {0}", tree); } } } } } private void SpawnChild(Copse copse, SceneObjectPart s_tree) { Vector3 position = new Vector3(); double randX = ((Util.RandomClass.NextDouble() * 2.0) - 1.0) * (s_tree.Scale.X * 3); double randY = ((Util.RandomClass.NextDouble() * 2.0) - 1.0) * (s_tree.Scale.X * 3); position.X = s_tree.AbsolutePosition.X + (float)randX; position.Y = s_tree.AbsolutePosition.Y + (float)randY; if (position.X <= ((int)Constants.RegionSize - 1) && position.X >= 0 && position.Y <= ((int)Constants.RegionSize - 1) && position.Y >= 0 && Util.GetDistanceTo(position, copse.m_seed_point) <= copse.m_range) { UUID uuid = m_scene.RegionInfo.EstateSettings.EstateOwner; CreateTree(uuid, copse, position); } } private void CreateTree(UUID uuid, Copse copse, Vector3 position) { position.Z = (float)m_scene.Heightmap[(int)position.X, (int)position.Y]; if (position.Z >= copse.m_treeline_low && position.Z <= copse.m_treeline_high) { SceneObjectGroup tree = AddTree(uuid, UUID.Zero, copse.m_initial_scale, Quaternion.Identity, position, copse.m_tree_type, false); tree.Name = copse.ToString(); copse.m_trees.Add(tree.UUID); tree.SendGroupFullUpdate(); } } private void CalculateTrees_Elapsed(object sender, ElapsedEventArgs e) { growTrees(); seedTrees(); killTrees(); } } }