diff options
author | Teravus Ovares | 2009-05-09 05:21:56 +0000 |
---|---|---|
committer | Teravus Ovares | 2009-05-09 05:21:56 +0000 |
commit | dac793ea47a5ec370f86dd54b624de8510dc124f (patch) | |
tree | 862e4bd25ff423ec357c54b752cf1b97c6d434d3 /OpenSim/Region/ReplaceableModules | |
parent | Make remote assets work through the new server system (diff) | |
download | opensim-SC-dac793ea47a5ec370f86dd54b624de8510dc124f.zip opensim-SC-dac793ea47a5ec370f86dd54b624de8510dc124f.tar.gz opensim-SC-dac793ea47a5ec370f86dd54b624de8510dc124f.tar.bz2 opensim-SC-dac793ea47a5ec370f86dd54b624de8510dc124f.tar.xz |
* Break out the SampleMoneyModule to a new namespace
* Create the OpenSim.Region.ReplaceableModules namespace for modules that we intend to have people replace (see readme)
* Create the OpenSim.Region.ReplaceableModules.MoneyModule namespace
* Put our current Sample MoneyModule in this namespace. (more modifications here next commit)
Diffstat (limited to 'OpenSim/Region/ReplaceableModules')
3 files changed, 1618 insertions, 0 deletions
diff --git a/OpenSim/Region/ReplaceableModules/MoneyModule/Resources/MoneyModulePlugin.addin.xml b/OpenSim/Region/ReplaceableModules/MoneyModule/Resources/MoneyModulePlugin.addin.xml new file mode 100644 index 0000000..a25f297 --- /dev/null +++ b/OpenSim/Region/ReplaceableModules/MoneyModule/Resources/MoneyModulePlugin.addin.xml | |||
@@ -0,0 +1,8 @@ | |||
1 | <Addin id="OpenSim.Region.ReplaceableModules.MoneyModule" version="0.2"> | ||
2 | <Runtime> | ||
3 | <Import assembly="OpenSim.Region.ReplaceableModules.MoneyModule.dll"/> | ||
4 | </Runtime> | ||
5 | <Dependencies> | ||
6 | <Addin id="OpenSim" version="0.5" /> | ||
7 | </Dependencies> | ||
8 | </Addin> \ No newline at end of file | ||
diff --git a/OpenSim/Region/ReplaceableModules/MoneyModule/SampleMoneyModule.cs b/OpenSim/Region/ReplaceableModules/MoneyModule/SampleMoneyModule.cs new file mode 100644 index 0000000..7d79102 --- /dev/null +++ b/OpenSim/Region/ReplaceableModules/MoneyModule/SampleMoneyModule.cs | |||
@@ -0,0 +1,1605 @@ | |||
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 OpenSim 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; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Net; | ||
32 | using System.Net.Sockets; | ||
33 | using System.Reflection; | ||
34 | using System.Xml; | ||
35 | using log4net; | ||
36 | using Nini.Config; | ||
37 | using Nwc.XmlRpc; | ||
38 | using OpenMetaverse; | ||
39 | using OpenSim.Framework; | ||
40 | using OpenSim.Framework.Communications.Cache; | ||
41 | using OpenSim.Framework.Servers.HttpServer; | ||
42 | using OpenSim.Region.Framework.Interfaces; | ||
43 | using OpenSim.Region.Framework.Scenes; | ||
44 | |||
45 | namespace OpenSim.Region.ReplaceableModules.MoneyModule | ||
46 | { | ||
47 | /// <summary> | ||
48 | /// Demo Economy/Money Module. This is not a production quality money/economy module! | ||
49 | /// This is a demo for you to use when making one that works for you. | ||
50 | /// // To use the following you need to add: | ||
51 | /// -helperuri <ADDRESS TO HERE OR grid MONEY SERVER> | ||
52 | /// to the command line parameters you use to start up your client | ||
53 | /// This commonly looks like -helperuri http://127.0.0.1:9000/ | ||
54 | /// | ||
55 | /// Centralized grid structure example using OpenSimWi Redux revision 9+ | ||
56 | /// svn co https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux | ||
57 | /// </summary> | ||
58 | public class SampleMoneyModule : IMoneyModule, IRegionModule | ||
59 | { | ||
60 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
61 | |||
62 | /// <summary> | ||
63 | /// Where Stipends come from and Fees go to. | ||
64 | /// </summary> | ||
65 | // private UUID EconomyBaseAccount = UUID.Zero; | ||
66 | |||
67 | private float EnergyEfficiency = 0f; | ||
68 | private bool gridmode = false; | ||
69 | // private ObjectPaid handerOnObjectPaid; | ||
70 | private bool m_enabled = true; | ||
71 | |||
72 | private IConfigSource m_gConfig; | ||
73 | |||
74 | private bool m_keepMoneyAcrossLogins = true; | ||
75 | private Dictionary<UUID, int> m_KnownClientFunds = new Dictionary<UUID, int>(); | ||
76 | // private string m_LandAddress = String.Empty; | ||
77 | |||
78 | private int m_minFundsBeforeRefresh = 100; | ||
79 | private string m_MoneyAddress = String.Empty; | ||
80 | |||
81 | /// <summary> | ||
82 | /// Region UUIDS indexed by AgentID | ||
83 | /// </summary> | ||
84 | private Dictionary<UUID, UUID> m_rootAgents = new Dictionary<UUID, UUID>(); | ||
85 | |||
86 | /// <summary> | ||
87 | /// Scenes by Region Handle | ||
88 | /// </summary> | ||
89 | private Dictionary<ulong, Scene> m_scenel = new Dictionary<ulong, Scene>(); | ||
90 | |||
91 | private int m_stipend = 1000; | ||
92 | |||
93 | private int ObjectCapacity = 45000; | ||
94 | private int ObjectCount = 0; | ||
95 | private int PriceEnergyUnit = 0; | ||
96 | private int PriceGroupCreate = 0; | ||
97 | private int PriceObjectClaim = 0; | ||
98 | private float PriceObjectRent = 0f; | ||
99 | private float PriceObjectScaleFactor = 0f; | ||
100 | private int PriceParcelClaim = 0; | ||
101 | private float PriceParcelClaimFactor = 0f; | ||
102 | private int PriceParcelRent = 0; | ||
103 | private int PricePublicObjectDecay = 0; | ||
104 | private int PricePublicObjectDelete = 0; | ||
105 | private int PriceRentLight = 0; | ||
106 | private int PriceUpload = 0; | ||
107 | private int TeleportMinPrice = 0; | ||
108 | |||
109 | private float TeleportPriceExponent = 0f; | ||
110 | // private int UserLevelPaysFees = 2; | ||
111 | // private Scene XMLRPCHandler; | ||
112 | |||
113 | #region IMoneyModule Members | ||
114 | |||
115 | public event ObjectPaid OnObjectPaid; | ||
116 | |||
117 | /// <summary> | ||
118 | /// Startup | ||
119 | /// </summary> | ||
120 | /// <param name="scene"></param> | ||
121 | /// <param name="config"></param> | ||
122 | public void Initialise(Scene scene, IConfigSource config) | ||
123 | { | ||
124 | m_gConfig = config; | ||
125 | |||
126 | IConfig startupConfig = m_gConfig.Configs["Startup"]; | ||
127 | IConfig economyConfig = m_gConfig.Configs["Economy"]; | ||
128 | |||
129 | |||
130 | ReadConfigAndPopulate(scene, startupConfig, "Startup"); | ||
131 | ReadConfigAndPopulate(scene, economyConfig, "Economy"); | ||
132 | |||
133 | if (m_enabled) | ||
134 | { | ||
135 | scene.RegisterModuleInterface<IMoneyModule>(this); | ||
136 | IHttpServer httpServer = scene.CommsManager.HttpServer; | ||
137 | |||
138 | lock (m_scenel) | ||
139 | { | ||
140 | if (m_scenel.Count == 0) | ||
141 | { | ||
142 | // XMLRPCHandler = scene; | ||
143 | |||
144 | // To use the following you need to add: | ||
145 | // -helperuri <ADDRESS TO HERE OR grid MONEY SERVER> | ||
146 | // to the command line parameters you use to start up your client | ||
147 | // This commonly looks like -helperuri http://127.0.0.1:9000/ | ||
148 | |||
149 | if (m_MoneyAddress.Length > 0) | ||
150 | { | ||
151 | // Centralized grid structure using OpenSimWi Redux revision 9+ | ||
152 | // https://opensimwiredux.svn.sourceforge.net/svnroot/opensimwiredux | ||
153 | httpServer.AddXmlRPCHandler("balanceUpdateRequest", GridMoneyUpdate); | ||
154 | httpServer.AddXmlRPCHandler("userAlert", UserAlert); | ||
155 | } | ||
156 | else | ||
157 | { | ||
158 | // Local Server.. enables functionality only. | ||
159 | httpServer.AddXmlRPCHandler("getCurrencyQuote", quote_func); | ||
160 | httpServer.AddXmlRPCHandler("buyCurrency", buy_func); | ||
161 | httpServer.AddXmlRPCHandler("preflightBuyLandPrep", preflightBuyLandPrep_func); | ||
162 | httpServer.AddXmlRPCHandler("buyLandPrep", landBuy_func); | ||
163 | } | ||
164 | } | ||
165 | |||
166 | if (m_scenel.ContainsKey(scene.RegionInfo.RegionHandle)) | ||
167 | { | ||
168 | m_scenel[scene.RegionInfo.RegionHandle] = scene; | ||
169 | } | ||
170 | else | ||
171 | { | ||
172 | m_scenel.Add(scene.RegionInfo.RegionHandle, scene); | ||
173 | } | ||
174 | } | ||
175 | |||
176 | scene.EventManager.OnNewClient += OnNewClient; | ||
177 | scene.EventManager.OnMoneyTransfer += MoneyTransferAction; | ||
178 | scene.EventManager.OnClientClosed += ClientClosed; | ||
179 | scene.EventManager.OnAvatarEnteringNewParcel += AvatarEnteringParcel; | ||
180 | scene.EventManager.OnMakeChildAgent += MakeChildAgent; | ||
181 | scene.EventManager.OnClientClosed += ClientLoggedOut; | ||
182 | scene.EventManager.OnValidateLandBuy += ValidateLandBuy; | ||
183 | scene.EventManager.OnLandBuy += processLandBuy; | ||
184 | } | ||
185 | } | ||
186 | |||
187 | // Please do not refactor these to be just one method | ||
188 | // Existing implementations need the distinction | ||
189 | // | ||
190 | public void ApplyUploadCharge(UUID agentID) | ||
191 | { | ||
192 | } | ||
193 | |||
194 | public void ApplyGroupCreationCharge(UUID agentID) | ||
195 | { | ||
196 | } | ||
197 | |||
198 | public void ApplyCharge(UUID agentID, int amount, string text) | ||
199 | { | ||
200 | } | ||
201 | |||
202 | public bool ObjectGiveMoney(UUID objectID, UUID fromID, UUID toID, int amount) | ||
203 | { | ||
204 | string description = String.Format("Object {0} pays {1}", resolveObjectName(objectID), resolveAgentName(toID)); | ||
205 | |||
206 | bool give_result = doMoneyTransfer(fromID, toID, amount, 2, description); | ||
207 | |||
208 | if (m_MoneyAddress.Length == 0) | ||
209 | BalanceUpdate(fromID, toID, give_result, description); | ||
210 | |||
211 | return give_result; | ||
212 | } | ||
213 | |||
214 | public void PostInitialise() | ||
215 | { | ||
216 | } | ||
217 | |||
218 | public void Close() | ||
219 | { | ||
220 | } | ||
221 | |||
222 | public string Name | ||
223 | { | ||
224 | get { return "BetaGridLikeMoneyModule"; } | ||
225 | } | ||
226 | |||
227 | public bool IsSharedModule | ||
228 | { | ||
229 | get { return true; } | ||
230 | } | ||
231 | |||
232 | #endregion | ||
233 | |||
234 | /// <summary> | ||
235 | /// Parse Configuration | ||
236 | /// </summary> | ||
237 | /// <param name="scene"></param> | ||
238 | /// <param name="startupConfig"></param> | ||
239 | /// <param name="config"></param> | ||
240 | private void ReadConfigAndPopulate(Scene scene, IConfig startupConfig, string config) | ||
241 | { | ||
242 | if (config == "Startup" && startupConfig != null) | ||
243 | { | ||
244 | gridmode = startupConfig.GetBoolean("gridmode", false); | ||
245 | m_enabled = (startupConfig.GetString("economymodule", "BetaGridLikeMoneyModule") == "BetaGridLikeMoneyModule"); | ||
246 | } | ||
247 | |||
248 | if (config == "Economy" && startupConfig != null) | ||
249 | { | ||
250 | ObjectCapacity = startupConfig.GetInt("ObjectCapacity", 45000); | ||
251 | PriceEnergyUnit = startupConfig.GetInt("PriceEnergyUnit", 100); | ||
252 | PriceObjectClaim = startupConfig.GetInt("PriceObjectClaim", 10); | ||
253 | PricePublicObjectDecay = startupConfig.GetInt("PricePublicObjectDecay", 4); | ||
254 | PricePublicObjectDelete = startupConfig.GetInt("PricePublicObjectDelete", 4); | ||
255 | PriceParcelClaim = startupConfig.GetInt("PriceParcelClaim", 1); | ||
256 | PriceParcelClaimFactor = startupConfig.GetFloat("PriceParcelClaimFactor", 1f); | ||
257 | PriceUpload = startupConfig.GetInt("PriceUpload", 0); | ||
258 | PriceRentLight = startupConfig.GetInt("PriceRentLight", 5); | ||
259 | TeleportMinPrice = startupConfig.GetInt("TeleportMinPrice", 2); | ||
260 | TeleportPriceExponent = startupConfig.GetFloat("TeleportPriceExponent", 2f); | ||
261 | EnergyEfficiency = startupConfig.GetFloat("EnergyEfficiency", 1); | ||
262 | PriceObjectRent = startupConfig.GetFloat("PriceObjectRent", 1); | ||
263 | PriceObjectScaleFactor = startupConfig.GetFloat("PriceObjectScaleFactor", 10); | ||
264 | PriceParcelRent = startupConfig.GetInt("PriceParcelRent", 1); | ||
265 | PriceGroupCreate = startupConfig.GetInt("PriceGroupCreate", -1); | ||
266 | // string EBA = startupConfig.GetString("EconomyBaseAccount", UUID.Zero.ToString()); | ||
267 | // Helpers.TryParse(EBA, out EconomyBaseAccount); | ||
268 | |||
269 | // UserLevelPaysFees = startupConfig.GetInt("UserLevelPaysFees", -1); | ||
270 | m_stipend = startupConfig.GetInt("UserStipend", 1000); | ||
271 | m_minFundsBeforeRefresh = startupConfig.GetInt("IssueStipendWhenClientIsBelowAmount", 10); | ||
272 | m_keepMoneyAcrossLogins = startupConfig.GetBoolean("KeepMoneyAcrossLogins", true); | ||
273 | m_MoneyAddress = startupConfig.GetString("CurrencyServer", String.Empty); | ||
274 | // m_LandAddress = startupConfig.GetString("LandServer", String.Empty); | ||
275 | } | ||
276 | |||
277 | // Send ObjectCapacity to Scene.. Which sends it to the SimStatsReporter. | ||
278 | scene.SetObjectCapacity(ObjectCapacity); | ||
279 | } | ||
280 | |||
281 | public EconomyData GetEconomyData() | ||
282 | { | ||
283 | EconomyData edata = new EconomyData(); | ||
284 | edata.ObjectCapacity = ObjectCapacity; | ||
285 | edata.ObjectCount = ObjectCount; | ||
286 | edata.PriceEnergyUnit = PriceEnergyUnit; | ||
287 | edata.PriceGroupCreate = PriceGroupCreate; | ||
288 | edata.PriceObjectClaim = PriceObjectClaim; | ||
289 | edata.PriceObjectRent = PriceObjectRent; | ||
290 | edata.PriceObjectScaleFactor = PriceObjectScaleFactor; | ||
291 | edata.PriceParcelClaim = PriceParcelClaim; | ||
292 | edata.PriceParcelClaimFactor = PriceParcelClaimFactor; | ||
293 | edata.PriceParcelRent = PriceParcelRent; | ||
294 | edata.PricePublicObjectDecay = PricePublicObjectDecay; | ||
295 | edata.PricePublicObjectDelete = PricePublicObjectDelete; | ||
296 | edata.PriceRentLight = PriceRentLight; | ||
297 | edata.PriceUpload = PriceUpload; | ||
298 | edata.TeleportMinPrice = TeleportMinPrice; | ||
299 | return edata; | ||
300 | } | ||
301 | |||
302 | private void GetClientFunds(IClientAPI client) | ||
303 | { | ||
304 | // Here we check if we're in grid mode | ||
305 | // I imagine that the 'check balance' | ||
306 | // function for the client should be here or shortly after | ||
307 | |||
308 | if (gridmode) | ||
309 | { | ||
310 | if (m_MoneyAddress.Length == 0) | ||
311 | { | ||
312 | CheckExistAndRefreshFunds(client.AgentId); | ||
313 | } | ||
314 | else | ||
315 | { | ||
316 | bool childYN = true; | ||
317 | ScenePresence agent = null; | ||
318 | //client.SecureSessionId; | ||
319 | Scene s = LocateSceneClientIn(client.AgentId); | ||
320 | if (s != null) | ||
321 | { | ||
322 | agent = s.GetScenePresence(client.AgentId); | ||
323 | if (agent != null) | ||
324 | childYN = agent.IsChildAgent; | ||
325 | } | ||
326 | if (s != null && agent != null && childYN == false) | ||
327 | { | ||
328 | //s.RegionInfo.RegionHandle; | ||
329 | UUID agentID = UUID.Zero; | ||
330 | int funds = 0; | ||
331 | |||
332 | Hashtable hbinfo = | ||
333 | GetBalanceForUserFromMoneyServer(client.AgentId, client.SecureSessionId, s.RegionInfo.originRegionID, | ||
334 | s.RegionInfo.regionSecret); | ||
335 | if ((bool) hbinfo["success"] == true) | ||
336 | { | ||
337 | UUID.TryParse((string)hbinfo["agentId"], out agentID); | ||
338 | try | ||
339 | { | ||
340 | funds = (Int32) hbinfo["funds"]; | ||
341 | } | ||
342 | catch (ArgumentException) | ||
343 | { | ||
344 | } | ||
345 | catch (FormatException) | ||
346 | { | ||
347 | } | ||
348 | catch (OverflowException) | ||
349 | { | ||
350 | m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentID); | ||
351 | client.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); | ||
352 | } | ||
353 | catch (InvalidCastException) | ||
354 | { | ||
355 | funds = 0; | ||
356 | } | ||
357 | |||
358 | m_KnownClientFunds[agentID] = funds; | ||
359 | } | ||
360 | else | ||
361 | { | ||
362 | m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentID, | ||
363 | (string) hbinfo["errorMessage"]); | ||
364 | client.SendAlertMessage((string) hbinfo["errorMessage"]); | ||
365 | } | ||
366 | SendMoneyBalance(client, agentID, client.SessionId, UUID.Zero); | ||
367 | } | ||
368 | } | ||
369 | } | ||
370 | else | ||
371 | { | ||
372 | CheckExistAndRefreshFunds(client.AgentId); | ||
373 | } | ||
374 | |||
375 | } | ||
376 | |||
377 | /// <summary> | ||
378 | /// New Client Event Handler | ||
379 | /// </summary> | ||
380 | /// <param name="client"></param> | ||
381 | private void OnNewClient(IClientAPI client) | ||
382 | { | ||
383 | GetClientFunds(client); | ||
384 | |||
385 | // Subscribe to Money messages | ||
386 | client.OnEconomyDataRequest += EconomyDataRequestHandler; | ||
387 | client.OnMoneyBalanceRequest += SendMoneyBalance; | ||
388 | client.OnRequestPayPrice += requestPayPrice; | ||
389 | client.OnObjectBuy += ObjectBuy; | ||
390 | client.OnLogout += ClientClosed; | ||
391 | } | ||
392 | |||
393 | /// <summary> | ||
394 | /// Transfer money | ||
395 | /// </summary> | ||
396 | /// <param name="Sender"></param> | ||
397 | /// <param name="Receiver"></param> | ||
398 | /// <param name="amount"></param> | ||
399 | /// <returns></returns> | ||
400 | private bool doMoneyTransfer(UUID Sender, UUID Receiver, int amount, int transactiontype, string description) | ||
401 | { | ||
402 | bool result = false; | ||
403 | if (amount >= 0) | ||
404 | { | ||
405 | lock (m_KnownClientFunds) | ||
406 | { | ||
407 | // If we don't know about the sender, then the sender can't | ||
408 | // actually be here and therefore this is likely fraud or outdated. | ||
409 | if (m_MoneyAddress.Length == 0) | ||
410 | { | ||
411 | if (m_KnownClientFunds.ContainsKey(Sender)) | ||
412 | { | ||
413 | // Does the sender have enough funds to give? | ||
414 | if (m_KnownClientFunds[Sender] >= amount) | ||
415 | { | ||
416 | // Subtract the funds from the senders account | ||
417 | m_KnownClientFunds[Sender] -= amount; | ||
418 | |||
419 | // do we know about the receiver? | ||
420 | if (!m_KnownClientFunds.ContainsKey(Receiver)) | ||
421 | { | ||
422 | // Make a record for them so they get the updated balance when they login | ||
423 | CheckExistAndRefreshFunds(Receiver); | ||
424 | } | ||
425 | if (m_enabled) | ||
426 | { | ||
427 | //Add the amount to the Receiver's funds | ||
428 | m_KnownClientFunds[Receiver] += amount; | ||
429 | result = true; | ||
430 | } | ||
431 | } | ||
432 | else | ||
433 | { | ||
434 | // These below are redundant to make this clearer to read | ||
435 | result = false; | ||
436 | } | ||
437 | } | ||
438 | else | ||
439 | { | ||
440 | result = false; | ||
441 | } | ||
442 | } | ||
443 | else | ||
444 | { | ||
445 | result = TransferMoneyonMoneyServer(Sender, Receiver, amount, transactiontype, description); | ||
446 | } | ||
447 | } | ||
448 | } | ||
449 | return result; | ||
450 | } | ||
451 | |||
452 | |||
453 | /// <summary> | ||
454 | /// Sends the the stored money balance to the client | ||
455 | /// </summary> | ||
456 | /// <param name="client"></param> | ||
457 | /// <param name="agentID"></param> | ||
458 | /// <param name="SessionID"></param> | ||
459 | /// <param name="TransactionID"></param> | ||
460 | public void SendMoneyBalance(IClientAPI client, UUID agentID, UUID SessionID, UUID TransactionID) | ||
461 | { | ||
462 | if (client.AgentId == agentID && client.SessionId == SessionID) | ||
463 | { | ||
464 | int returnfunds = 0; | ||
465 | |||
466 | try | ||
467 | { | ||
468 | returnfunds = GetFundsForAgentID(agentID); | ||
469 | } | ||
470 | catch (Exception e) | ||
471 | { | ||
472 | client.SendAlertMessage(e.Message + " "); | ||
473 | } | ||
474 | |||
475 | client.SendMoneyBalance(TransactionID, true, new byte[0], returnfunds); | ||
476 | } | ||
477 | else | ||
478 | { | ||
479 | client.SendAlertMessage("Unable to send your money balance to you!"); | ||
480 | } | ||
481 | } | ||
482 | |||
483 | /// <summary> | ||
484 | /// Gets the current balance for the user from the Grid Money Server | ||
485 | /// </summary> | ||
486 | /// <param name="agentId"></param> | ||
487 | /// <param name="secureSessionID"></param> | ||
488 | /// <param name="regionId"></param> | ||
489 | /// <param name="regionSecret"></param> | ||
490 | /// <returns></returns> | ||
491 | public Hashtable GetBalanceForUserFromMoneyServer(UUID agentId, UUID secureSessionID, UUID regionId, string regionSecret) | ||
492 | { | ||
493 | Hashtable MoneyBalanceRequestParams = new Hashtable(); | ||
494 | MoneyBalanceRequestParams["agentId"] = agentId.ToString(); | ||
495 | MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); | ||
496 | MoneyBalanceRequestParams["regionId"] = regionId.ToString(); | ||
497 | MoneyBalanceRequestParams["secret"] = regionSecret; | ||
498 | MoneyBalanceRequestParams["currencySecret"] = ""; // per - region/user currency secret gotten from the money system | ||
499 | |||
500 | Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorUserBalanceRequest"); | ||
501 | |||
502 | return MoneyRespData; | ||
503 | } | ||
504 | |||
505 | |||
506 | /// <summary> | ||
507 | /// Generic XMLRPC client abstraction | ||
508 | /// </summary> | ||
509 | /// <param name="ReqParams">Hashtable containing parameters to the method</param> | ||
510 | /// <param name="method">Method to invoke</param> | ||
511 | /// <returns>Hashtable with success=>bool and other values</returns> | ||
512 | public Hashtable genericCurrencyXMLRPCRequest(Hashtable ReqParams, string method) | ||
513 | { | ||
514 | ArrayList SendParams = new ArrayList(); | ||
515 | SendParams.Add(ReqParams); | ||
516 | // Send Request | ||
517 | XmlRpcResponse MoneyResp; | ||
518 | try | ||
519 | { | ||
520 | XmlRpcRequest BalanceRequestReq = new XmlRpcRequest(method, SendParams); | ||
521 | MoneyResp = BalanceRequestReq.Send(m_MoneyAddress, 30000); | ||
522 | } | ||
523 | catch (WebException ex) | ||
524 | { | ||
525 | m_log.ErrorFormat( | ||
526 | "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", | ||
527 | m_MoneyAddress, ex); | ||
528 | |||
529 | Hashtable ErrorHash = new Hashtable(); | ||
530 | ErrorHash["success"] = false; | ||
531 | ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; | ||
532 | ErrorHash["errorURI"] = ""; | ||
533 | |||
534 | return ErrorHash; | ||
535 | //throw (ex); | ||
536 | } | ||
537 | catch (SocketException ex) | ||
538 | { | ||
539 | m_log.ErrorFormat( | ||
540 | "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", | ||
541 | m_MoneyAddress, ex); | ||
542 | |||
543 | Hashtable ErrorHash = new Hashtable(); | ||
544 | ErrorHash["success"] = false; | ||
545 | ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; | ||
546 | ErrorHash["errorURI"] = ""; | ||
547 | |||
548 | return ErrorHash; | ||
549 | //throw (ex); | ||
550 | } | ||
551 | catch (XmlException ex) | ||
552 | { | ||
553 | m_log.ErrorFormat( | ||
554 | "[MONEY]: Unable to connect to Money Server {0}. Exception {1}", | ||
555 | m_MoneyAddress, ex); | ||
556 | |||
557 | Hashtable ErrorHash = new Hashtable(); | ||
558 | ErrorHash["success"] = false; | ||
559 | ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; | ||
560 | ErrorHash["errorURI"] = ""; | ||
561 | |||
562 | return ErrorHash; | ||
563 | } | ||
564 | if (MoneyResp.IsFault) | ||
565 | { | ||
566 | Hashtable ErrorHash = new Hashtable(); | ||
567 | ErrorHash["success"] = false; | ||
568 | ErrorHash["errorMessage"] = "Unable to manage your money at this time. Purchases may be unavailable"; | ||
569 | ErrorHash["errorURI"] = ""; | ||
570 | |||
571 | return ErrorHash; | ||
572 | } | ||
573 | Hashtable MoneyRespData = (Hashtable) MoneyResp.Value; | ||
574 | |||
575 | return MoneyRespData; | ||
576 | } | ||
577 | |||
578 | /// <summary> | ||
579 | /// This informs the Money Grid Server that the avatar is in this simulator | ||
580 | /// </summary> | ||
581 | /// <param name="agentId"></param> | ||
582 | /// <param name="secureSessionID"></param> | ||
583 | /// <param name="regionId"></param> | ||
584 | /// <param name="regionSecret"></param> | ||
585 | /// <returns></returns> | ||
586 | public Hashtable claim_user(UUID agentId, UUID secureSessionID, UUID regionId, string regionSecret) | ||
587 | { | ||
588 | Hashtable MoneyBalanceRequestParams = new Hashtable(); | ||
589 | MoneyBalanceRequestParams["agentId"] = agentId.ToString(); | ||
590 | MoneyBalanceRequestParams["secureSessionId"] = secureSessionID.ToString(); | ||
591 | MoneyBalanceRequestParams["regionId"] = regionId.ToString(); | ||
592 | MoneyBalanceRequestParams["secret"] = regionSecret; | ||
593 | |||
594 | Hashtable MoneyRespData = genericCurrencyXMLRPCRequest(MoneyBalanceRequestParams, "simulatorClaimUserRequest"); | ||
595 | IClientAPI sendMoneyBal = LocateClientObject(agentId); | ||
596 | if (sendMoneyBal != null) | ||
597 | { | ||
598 | SendMoneyBalance(sendMoneyBal, agentId, sendMoneyBal.SessionId, UUID.Zero); | ||
599 | } | ||
600 | return MoneyRespData; | ||
601 | } | ||
602 | |||
603 | private SceneObjectPart findPrim(UUID objectID) | ||
604 | { | ||
605 | lock (m_scenel) | ||
606 | { | ||
607 | foreach (Scene s in m_scenel.Values) | ||
608 | { | ||
609 | SceneObjectPart part = s.GetSceneObjectPart(objectID); | ||
610 | if (part != null) | ||
611 | { | ||
612 | return part; | ||
613 | } | ||
614 | } | ||
615 | } | ||
616 | return null; | ||
617 | } | ||
618 | |||
619 | private string resolveObjectName(UUID objectID) | ||
620 | { | ||
621 | SceneObjectPart part = findPrim(objectID); | ||
622 | if (part != null) | ||
623 | { | ||
624 | return part.Name; | ||
625 | } | ||
626 | return String.Empty; | ||
627 | } | ||
628 | |||
629 | private string resolveAgentName(UUID agentID) | ||
630 | { | ||
631 | // try avatar username surname | ||
632 | Scene scene = GetRandomScene(); | ||
633 | CachedUserInfo profile = scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID); | ||
634 | if (profile != null && profile.UserProfile != null) | ||
635 | { | ||
636 | string avatarname = profile.UserProfile.FirstName + " " + profile.UserProfile.SurName; | ||
637 | return avatarname; | ||
638 | } | ||
639 | else | ||
640 | { | ||
641 | m_log.ErrorFormat( | ||
642 | "[MONEY]: Could not resolve user {0}", | ||
643 | agentID); | ||
644 | } | ||
645 | |||
646 | return String.Empty; | ||
647 | } | ||
648 | |||
649 | private void BalanceUpdate(UUID senderID, UUID receiverID, bool transactionresult, string description) | ||
650 | { | ||
651 | IClientAPI sender = LocateClientObject(senderID); | ||
652 | IClientAPI receiver = LocateClientObject(receiverID); | ||
653 | |||
654 | if (senderID != receiverID) | ||
655 | { | ||
656 | if (sender != null) | ||
657 | { | ||
658 | sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(senderID)); | ||
659 | } | ||
660 | |||
661 | if (receiver != null) | ||
662 | { | ||
663 | receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(description), GetFundsForAgentID(receiverID)); | ||
664 | } | ||
665 | } | ||
666 | } | ||
667 | |||
668 | /// <summary> | ||
669 | /// Informs the Money Grid Server of a transfer. | ||
670 | /// </summary> | ||
671 | /// <param name="sourceId"></param> | ||
672 | /// <param name="destId"></param> | ||
673 | /// <param name="amount"></param> | ||
674 | /// <returns></returns> | ||
675 | public bool TransferMoneyonMoneyServer(UUID sourceId, UUID destId, int amount, int transactiontype, string description) | ||
676 | { | ||
677 | int aggregatePermInventory = 0; | ||
678 | int aggregatePermNextOwner = 0; | ||
679 | int flags = 0; | ||
680 | bool rvalue = false; | ||
681 | |||
682 | IClientAPI cli = LocateClientObject(sourceId); | ||
683 | if (cli != null) | ||
684 | { | ||
685 | Scene userScene = null; | ||
686 | lock (m_rootAgents) | ||
687 | { | ||
688 | userScene = GetSceneByUUID(m_rootAgents[sourceId]); | ||
689 | } | ||
690 | if (userScene != null) | ||
691 | { | ||
692 | Hashtable ht = new Hashtable(); | ||
693 | ht["agentId"] = sourceId.ToString(); | ||
694 | ht["secureSessionId"] = cli.SecureSessionId.ToString(); | ||
695 | ht["regionId"] = userScene.RegionInfo.originRegionID.ToString(); | ||
696 | ht["secret"] = userScene.RegionInfo.regionSecret; | ||
697 | ht["currencySecret"] = " "; | ||
698 | ht["destId"] = destId.ToString(); | ||
699 | ht["cash"] = amount; | ||
700 | ht["aggregatePermInventory"] = aggregatePermInventory; | ||
701 | ht["aggregatePermNextOwner"] = aggregatePermNextOwner; | ||
702 | ht["flags"] = flags; | ||
703 | ht["transactionType"] = transactiontype; | ||
704 | ht["description"] = description; | ||
705 | |||
706 | Hashtable hresult = genericCurrencyXMLRPCRequest(ht, "regionMoveMoney"); | ||
707 | |||
708 | if ((bool) hresult["success"] == true) | ||
709 | { | ||
710 | int funds1 = 0; | ||
711 | int funds2 = 0; | ||
712 | try | ||
713 | { | ||
714 | funds1 = (Int32) hresult["funds"]; | ||
715 | } | ||
716 | catch (InvalidCastException) | ||
717 | { | ||
718 | funds1 = 0; | ||
719 | } | ||
720 | SetLocalFundsForAgentID(sourceId, funds1); | ||
721 | if (m_KnownClientFunds.ContainsKey(destId)) | ||
722 | { | ||
723 | try | ||
724 | { | ||
725 | funds2 = (Int32) hresult["funds2"]; | ||
726 | } | ||
727 | catch (InvalidCastException) | ||
728 | { | ||
729 | funds2 = 0; | ||
730 | } | ||
731 | SetLocalFundsForAgentID(destId, funds2); | ||
732 | } | ||
733 | |||
734 | |||
735 | rvalue = true; | ||
736 | } | ||
737 | else | ||
738 | { | ||
739 | cli.SendAgentAlertMessage((string) hresult["errorMessage"], true); | ||
740 | } | ||
741 | } | ||
742 | } | ||
743 | else | ||
744 | { | ||
745 | m_log.ErrorFormat("[MONEY]: Client {0} not found", sourceId.ToString()); | ||
746 | } | ||
747 | |||
748 | return rvalue; | ||
749 | } | ||
750 | |||
751 | public int GetRemoteBalance(UUID agentId) | ||
752 | { | ||
753 | int funds = 0; | ||
754 | |||
755 | IClientAPI aClient = LocateClientObject(agentId); | ||
756 | if (aClient != null) | ||
757 | { | ||
758 | Scene s = LocateSceneClientIn(agentId); | ||
759 | if (s != null) | ||
760 | { | ||
761 | if (m_MoneyAddress.Length > 0) | ||
762 | { | ||
763 | Hashtable hbinfo = | ||
764 | GetBalanceForUserFromMoneyServer(aClient.AgentId, aClient.SecureSessionId, s.RegionInfo.originRegionID, | ||
765 | s.RegionInfo.regionSecret); | ||
766 | if ((bool) hbinfo["success"] == true) | ||
767 | { | ||
768 | try | ||
769 | { | ||
770 | funds = (Int32) hbinfo["funds"]; | ||
771 | } | ||
772 | catch (ArgumentException) | ||
773 | { | ||
774 | } | ||
775 | catch (FormatException) | ||
776 | { | ||
777 | } | ||
778 | catch (OverflowException) | ||
779 | { | ||
780 | m_log.ErrorFormat("[MONEY]: While getting the Currency for user {0}, the return funds overflowed.", agentId); | ||
781 | aClient.SendAlertMessage("Unable to get your money balance, money operations will be unavailable"); | ||
782 | } | ||
783 | catch (InvalidCastException) | ||
784 | { | ||
785 | funds = 0; | ||
786 | } | ||
787 | } | ||
788 | else | ||
789 | { | ||
790 | m_log.WarnFormat("[MONEY]: Getting Money for user {0} failed with the following message:{1}", agentId, | ||
791 | (string) hbinfo["errorMessage"]); | ||
792 | aClient.SendAlertMessage((string) hbinfo["errorMessage"]); | ||
793 | } | ||
794 | } | ||
795 | |||
796 | SetLocalFundsForAgentID(agentId, funds); | ||
797 | SendMoneyBalance(aClient, agentId, aClient.SessionId, UUID.Zero); | ||
798 | } | ||
799 | else | ||
800 | { | ||
801 | m_log.Debug("[MONEY]: Got balance request update for agent that is here, but couldn't find which scene."); | ||
802 | } | ||
803 | } | ||
804 | else | ||
805 | { | ||
806 | m_log.Debug("[MONEY]: Got balance request update for agent that isn't here."); | ||
807 | } | ||
808 | return funds; | ||
809 | } | ||
810 | |||
811 | public XmlRpcResponse GridMoneyUpdate(XmlRpcRequest request) | ||
812 | { | ||
813 | m_log.Debug("[MONEY]: Dynamic balance update called."); | ||
814 | Hashtable requestData = (Hashtable) request.Params[0]; | ||
815 | |||
816 | if (requestData.ContainsKey("agentId")) | ||
817 | { | ||
818 | UUID agentId = UUID.Zero; | ||
819 | |||
820 | UUID.TryParse((string) requestData["agentId"], out agentId); | ||
821 | if (agentId != UUID.Zero) | ||
822 | { | ||
823 | GetRemoteBalance(agentId); | ||
824 | } | ||
825 | else | ||
826 | { | ||
827 | m_log.Debug("[MONEY]: invalid agentId specified, dropping."); | ||
828 | } | ||
829 | } | ||
830 | else | ||
831 | { | ||
832 | m_log.Debug("[MONEY]: no agentId specified, dropping."); | ||
833 | } | ||
834 | XmlRpcResponse r = new XmlRpcResponse(); | ||
835 | Hashtable rparms = new Hashtable(); | ||
836 | rparms["success"] = true; | ||
837 | |||
838 | r.Value = rparms; | ||
839 | return r; | ||
840 | } | ||
841 | |||
842 | /// <summary> | ||
843 | /// XMLRPC handler to send alert message and sound to client | ||
844 | /// </summary> | ||
845 | public XmlRpcResponse UserAlert(XmlRpcRequest request) | ||
846 | { | ||
847 | XmlRpcResponse ret = new XmlRpcResponse(); | ||
848 | Hashtable retparam = new Hashtable(); | ||
849 | Hashtable requestData = (Hashtable) request.Params[0]; | ||
850 | |||
851 | UUID agentId; | ||
852 | UUID soundId; | ||
853 | UUID regionId; | ||
854 | |||
855 | UUID.TryParse((string) requestData["agentId"], out agentId); | ||
856 | UUID.TryParse((string) requestData["soundId"], out soundId); | ||
857 | UUID.TryParse((string) requestData["regionId"], out regionId); | ||
858 | string text = (string) requestData["text"]; | ||
859 | string secret = (string) requestData["secret"]; | ||
860 | |||
861 | Scene userScene = GetSceneByUUID(regionId); | ||
862 | if (userScene != null) | ||
863 | { | ||
864 | if (userScene.RegionInfo.regionSecret == secret) | ||
865 | { | ||
866 | |||
867 | IClientAPI client = LocateClientObject(agentId); | ||
868 | if (client != null) | ||
869 | { | ||
870 | |||
871 | if (soundId != UUID.Zero) | ||
872 | client.SendPlayAttachedSound(soundId, UUID.Zero, UUID.Zero, 1.0f, 0); | ||
873 | |||
874 | client.SendBlueBoxMessage(UUID.Zero, "", text); | ||
875 | |||
876 | retparam.Add("success", true); | ||
877 | } | ||
878 | else | ||
879 | { | ||
880 | retparam.Add("success", false); | ||
881 | } | ||
882 | } | ||
883 | else | ||
884 | { | ||
885 | retparam.Add("success", false); | ||
886 | } | ||
887 | } | ||
888 | |||
889 | ret.Value = retparam; | ||
890 | return ret; | ||
891 | } | ||
892 | |||
893 | # region Standalone box enablers only | ||
894 | |||
895 | public XmlRpcResponse quote_func(XmlRpcRequest request) | ||
896 | { | ||
897 | Hashtable requestData = (Hashtable) request.Params[0]; | ||
898 | UUID agentId = UUID.Zero; | ||
899 | int amount = 0; | ||
900 | Hashtable quoteResponse = new Hashtable(); | ||
901 | XmlRpcResponse returnval = new XmlRpcResponse(); | ||
902 | |||
903 | if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) | ||
904 | { | ||
905 | UUID.TryParse((string) requestData["agentId"], out agentId); | ||
906 | try | ||
907 | { | ||
908 | amount = (Int32) requestData["currencyBuy"]; | ||
909 | } | ||
910 | catch (InvalidCastException) | ||
911 | { | ||
912 | } | ||
913 | Hashtable currencyResponse = new Hashtable(); | ||
914 | currencyResponse.Add("estimatedCost", 0); | ||
915 | currencyResponse.Add("currencyBuy", amount); | ||
916 | |||
917 | quoteResponse.Add("success", true); | ||
918 | quoteResponse.Add("currency", currencyResponse); | ||
919 | quoteResponse.Add("confirm", "asdfad9fj39ma9fj"); | ||
920 | |||
921 | returnval.Value = quoteResponse; | ||
922 | return returnval; | ||
923 | } | ||
924 | |||
925 | |||
926 | quoteResponse.Add("success", false); | ||
927 | quoteResponse.Add("errorMessage", "Invalid parameters passed to the quote box"); | ||
928 | quoteResponse.Add("errorURI", "http://www.opensimulator.org/wiki"); | ||
929 | returnval.Value = quoteResponse; | ||
930 | return returnval; | ||
931 | } | ||
932 | |||
933 | public XmlRpcResponse buy_func(XmlRpcRequest request) | ||
934 | { | ||
935 | Hashtable requestData = (Hashtable) request.Params[0]; | ||
936 | UUID agentId = UUID.Zero; | ||
937 | int amount = 0; | ||
938 | if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) | ||
939 | { | ||
940 | UUID.TryParse((string) requestData["agentId"], out agentId); | ||
941 | try | ||
942 | { | ||
943 | amount = (Int32) requestData["currencyBuy"]; | ||
944 | } | ||
945 | catch (InvalidCastException) | ||
946 | { | ||
947 | } | ||
948 | if (agentId != UUID.Zero) | ||
949 | { | ||
950 | lock (m_KnownClientFunds) | ||
951 | { | ||
952 | if (m_KnownClientFunds.ContainsKey(agentId)) | ||
953 | { | ||
954 | m_KnownClientFunds[agentId] += amount; | ||
955 | } | ||
956 | else | ||
957 | { | ||
958 | m_KnownClientFunds.Add(agentId, amount); | ||
959 | } | ||
960 | } | ||
961 | IClientAPI client = LocateClientObject(agentId); | ||
962 | if (client != null) | ||
963 | { | ||
964 | SendMoneyBalance(client, agentId, client.SessionId, UUID.Zero); | ||
965 | } | ||
966 | } | ||
967 | } | ||
968 | XmlRpcResponse returnval = new XmlRpcResponse(); | ||
969 | Hashtable returnresp = new Hashtable(); | ||
970 | returnresp.Add("success", true); | ||
971 | returnval.Value = returnresp; | ||
972 | return returnval; | ||
973 | } | ||
974 | |||
975 | public XmlRpcResponse preflightBuyLandPrep_func(XmlRpcRequest request) | ||
976 | { | ||
977 | XmlRpcResponse ret = new XmlRpcResponse(); | ||
978 | Hashtable retparam = new Hashtable(); | ||
979 | Hashtable membershiplevels = new Hashtable(); | ||
980 | ArrayList levels = new ArrayList(); | ||
981 | Hashtable level = new Hashtable(); | ||
982 | level.Add("id", "00000000-0000-0000-0000-000000000000"); | ||
983 | level.Add("description", "some level"); | ||
984 | levels.Add(level); | ||
985 | //membershiplevels.Add("levels",levels); | ||
986 | |||
987 | Hashtable landuse = new Hashtable(); | ||
988 | landuse.Add("upgrade", false); | ||
989 | landuse.Add("action", "http://invaliddomaininvalid.com/"); | ||
990 | |||
991 | Hashtable currency = new Hashtable(); | ||
992 | currency.Add("estimatedCost", 0); | ||
993 | |||
994 | Hashtable membership = new Hashtable(); | ||
995 | membershiplevels.Add("upgrade", false); | ||
996 | membershiplevels.Add("action", "http://invaliddomaininvalid.com/"); | ||
997 | membershiplevels.Add("levels", membershiplevels); | ||
998 | |||
999 | retparam.Add("success", true); | ||
1000 | retparam.Add("currency", currency); | ||
1001 | retparam.Add("membership", membership); | ||
1002 | retparam.Add("landuse", landuse); | ||
1003 | retparam.Add("confirm", "asdfajsdkfjasdkfjalsdfjasdf"); | ||
1004 | |||
1005 | ret.Value = retparam; | ||
1006 | |||
1007 | return ret; | ||
1008 | } | ||
1009 | |||
1010 | public XmlRpcResponse landBuy_func(XmlRpcRequest request) | ||
1011 | { | ||
1012 | XmlRpcResponse ret = new XmlRpcResponse(); | ||
1013 | Hashtable retparam = new Hashtable(); | ||
1014 | Hashtable requestData = (Hashtable) request.Params[0]; | ||
1015 | |||
1016 | UUID agentId = UUID.Zero; | ||
1017 | int amount = 0; | ||
1018 | if (requestData.ContainsKey("agentId") && requestData.ContainsKey("currencyBuy")) | ||
1019 | { | ||
1020 | UUID.TryParse((string) requestData["agentId"], out agentId); | ||
1021 | try | ||
1022 | { | ||
1023 | amount = (Int32) requestData["currencyBuy"]; | ||
1024 | } | ||
1025 | catch (InvalidCastException) | ||
1026 | { | ||
1027 | } | ||
1028 | if (agentId != UUID.Zero) | ||
1029 | { | ||
1030 | lock (m_KnownClientFunds) | ||
1031 | { | ||
1032 | if (m_KnownClientFunds.ContainsKey(agentId)) | ||
1033 | { | ||
1034 | m_KnownClientFunds[agentId] += amount; | ||
1035 | } | ||
1036 | else | ||
1037 | { | ||
1038 | m_KnownClientFunds.Add(agentId, amount); | ||
1039 | } | ||
1040 | } | ||
1041 | IClientAPI client = LocateClientObject(agentId); | ||
1042 | if (client != null) | ||
1043 | { | ||
1044 | SendMoneyBalance(client, agentId, client.SessionId, UUID.Zero); | ||
1045 | } | ||
1046 | } | ||
1047 | } | ||
1048 | retparam.Add("success", true); | ||
1049 | ret.Value = retparam; | ||
1050 | |||
1051 | return ret; | ||
1052 | } | ||
1053 | |||
1054 | #endregion | ||
1055 | |||
1056 | #region local Fund Management | ||
1057 | |||
1058 | /// <summary> | ||
1059 | /// Ensures that the agent accounting data is set up in this instance. | ||
1060 | /// </summary> | ||
1061 | /// <param name="agentID"></param> | ||
1062 | private void CheckExistAndRefreshFunds(UUID agentID) | ||
1063 | { | ||
1064 | lock (m_KnownClientFunds) | ||
1065 | { | ||
1066 | if (!m_KnownClientFunds.ContainsKey(agentID)) | ||
1067 | { | ||
1068 | m_KnownClientFunds.Add(agentID, m_stipend); | ||
1069 | } | ||
1070 | else | ||
1071 | { | ||
1072 | if (m_KnownClientFunds[agentID] <= m_minFundsBeforeRefresh) | ||
1073 | { | ||
1074 | m_KnownClientFunds[agentID] = m_stipend; | ||
1075 | } | ||
1076 | } | ||
1077 | } | ||
1078 | } | ||
1079 | |||
1080 | /// <summary> | ||
1081 | /// Gets the amount of Funds for an agent | ||
1082 | /// </summary> | ||
1083 | /// <param name="AgentID"></param> | ||
1084 | /// <returns></returns> | ||
1085 | private int GetFundsForAgentID(UUID AgentID) | ||
1086 | { | ||
1087 | int returnfunds = 0; | ||
1088 | lock (m_KnownClientFunds) | ||
1089 | { | ||
1090 | if (m_KnownClientFunds.ContainsKey(AgentID)) | ||
1091 | { | ||
1092 | returnfunds = m_KnownClientFunds[AgentID]; | ||
1093 | } | ||
1094 | else | ||
1095 | { | ||
1096 | //throw new Exception("Unable to get funds."); | ||
1097 | } | ||
1098 | } | ||
1099 | return returnfunds; | ||
1100 | } | ||
1101 | |||
1102 | private void SetLocalFundsForAgentID(UUID AgentID, int amount) | ||
1103 | { | ||
1104 | lock (m_KnownClientFunds) | ||
1105 | { | ||
1106 | if (m_KnownClientFunds.ContainsKey(AgentID)) | ||
1107 | { | ||
1108 | m_KnownClientFunds[AgentID] = amount; | ||
1109 | } | ||
1110 | else | ||
1111 | { | ||
1112 | m_KnownClientFunds.Add(AgentID, amount); | ||
1113 | } | ||
1114 | } | ||
1115 | } | ||
1116 | |||
1117 | #endregion | ||
1118 | |||
1119 | #region Utility Helpers | ||
1120 | |||
1121 | /// <summary> | ||
1122 | /// Locates a IClientAPI for the client specified | ||
1123 | /// </summary> | ||
1124 | /// <param name="AgentID"></param> | ||
1125 | /// <returns></returns> | ||
1126 | private IClientAPI LocateClientObject(UUID AgentID) | ||
1127 | { | ||
1128 | ScenePresence tPresence = null; | ||
1129 | IClientAPI rclient = null; | ||
1130 | |||
1131 | lock (m_scenel) | ||
1132 | { | ||
1133 | foreach (Scene _scene in m_scenel.Values) | ||
1134 | { | ||
1135 | tPresence = _scene.GetScenePresence(AgentID); | ||
1136 | if (tPresence != null) | ||
1137 | { | ||
1138 | if (!tPresence.IsChildAgent) | ||
1139 | { | ||
1140 | rclient = tPresence.ControllingClient; | ||
1141 | } | ||
1142 | } | ||
1143 | if (rclient != null) | ||
1144 | { | ||
1145 | return rclient; | ||
1146 | } | ||
1147 | } | ||
1148 | } | ||
1149 | return null; | ||
1150 | } | ||
1151 | |||
1152 | private Scene LocateSceneClientIn(UUID AgentId) | ||
1153 | { | ||
1154 | lock (m_scenel) | ||
1155 | { | ||
1156 | foreach (Scene _scene in m_scenel.Values) | ||
1157 | { | ||
1158 | ScenePresence tPresence = _scene.GetScenePresence(AgentId); | ||
1159 | if (tPresence != null) | ||
1160 | { | ||
1161 | if (!tPresence.IsChildAgent) | ||
1162 | { | ||
1163 | return _scene; | ||
1164 | } | ||
1165 | } | ||
1166 | } | ||
1167 | } | ||
1168 | return null; | ||
1169 | } | ||
1170 | |||
1171 | /// <summary> | ||
1172 | /// Utility function Gets a Random scene in the instance. For when which scene exactly you're doing something with doesn't matter | ||
1173 | /// </summary> | ||
1174 | /// <returns></returns> | ||
1175 | public Scene GetRandomScene() | ||
1176 | { | ||
1177 | lock (m_scenel) | ||
1178 | { | ||
1179 | foreach (Scene rs in m_scenel.Values) | ||
1180 | return rs; | ||
1181 | } | ||
1182 | return null; | ||
1183 | } | ||
1184 | |||
1185 | /// <summary> | ||
1186 | /// Utility function to get a Scene by RegionID in a module | ||
1187 | /// </summary> | ||
1188 | /// <param name="RegionID"></param> | ||
1189 | /// <returns></returns> | ||
1190 | public Scene GetSceneByUUID(UUID RegionID) | ||
1191 | { | ||
1192 | lock (m_scenel) | ||
1193 | { | ||
1194 | foreach (Scene rs in m_scenel.Values) | ||
1195 | { | ||
1196 | if (rs.RegionInfo.originRegionID == RegionID) | ||
1197 | { | ||
1198 | return rs; | ||
1199 | } | ||
1200 | } | ||
1201 | } | ||
1202 | return null; | ||
1203 | } | ||
1204 | |||
1205 | #endregion | ||
1206 | |||
1207 | #region event Handlers | ||
1208 | |||
1209 | public void requestPayPrice(IClientAPI client, UUID objectID) | ||
1210 | { | ||
1211 | Scene scene = LocateSceneClientIn(client.AgentId); | ||
1212 | if (scene == null) | ||
1213 | return; | ||
1214 | |||
1215 | SceneObjectPart task = scene.GetSceneObjectPart(objectID); | ||
1216 | if (task == null) | ||
1217 | return; | ||
1218 | SceneObjectGroup group = task.ParentGroup; | ||
1219 | SceneObjectPart root = group.RootPart; | ||
1220 | |||
1221 | client.SendPayPrice(objectID, root.PayPrice); | ||
1222 | } | ||
1223 | |||
1224 | /// <summary> | ||
1225 | /// When the client closes the connection we remove their accounting info from memory to free up resources. | ||
1226 | /// </summary> | ||
1227 | /// <param name="AgentID"></param> | ||
1228 | public void ClientClosed(UUID AgentID) | ||
1229 | { | ||
1230 | lock (m_KnownClientFunds) | ||
1231 | { | ||
1232 | if (m_keepMoneyAcrossLogins && m_MoneyAddress.Length == 0) | ||
1233 | { | ||
1234 | } | ||
1235 | else | ||
1236 | { | ||
1237 | m_KnownClientFunds.Remove(AgentID); | ||
1238 | } | ||
1239 | } | ||
1240 | } | ||
1241 | |||
1242 | /// <summary> | ||
1243 | /// Event called Economy Data Request handler. | ||
1244 | /// </summary> | ||
1245 | /// <param name="agentId"></param> | ||
1246 | public void EconomyDataRequestHandler(UUID agentId) | ||
1247 | { | ||
1248 | IClientAPI user = LocateClientObject(agentId); | ||
1249 | |||
1250 | if (user != null) | ||
1251 | { | ||
1252 | user.SendEconomyData(EnergyEfficiency, ObjectCapacity, ObjectCount, PriceEnergyUnit, PriceGroupCreate, | ||
1253 | PriceObjectClaim, PriceObjectRent, PriceObjectScaleFactor, PriceParcelClaim, PriceParcelClaimFactor, | ||
1254 | PriceParcelRent, PricePublicObjectDecay, PricePublicObjectDelete, PriceRentLight, PriceUpload, | ||
1255 | TeleportMinPrice, TeleportPriceExponent); | ||
1256 | } | ||
1257 | } | ||
1258 | |||
1259 | private void ValidateLandBuy(Object osender, EventManager.LandBuyArgs e) | ||
1260 | { | ||
1261 | if (m_MoneyAddress.Length == 0) | ||
1262 | { | ||
1263 | lock (m_KnownClientFunds) | ||
1264 | { | ||
1265 | if (m_KnownClientFunds.ContainsKey(e.agentId)) | ||
1266 | { | ||
1267 | // Does the sender have enough funds to give? | ||
1268 | if (m_KnownClientFunds[e.agentId] >= e.parcelPrice) | ||
1269 | { | ||
1270 | lock (e) | ||
1271 | { | ||
1272 | e.economyValidated = true; | ||
1273 | } | ||
1274 | } | ||
1275 | } | ||
1276 | } | ||
1277 | } | ||
1278 | else | ||
1279 | { | ||
1280 | if (GetRemoteBalance(e.agentId) >= e.parcelPrice) | ||
1281 | { | ||
1282 | lock (e) | ||
1283 | { | ||
1284 | e.economyValidated = true; | ||
1285 | } | ||
1286 | } | ||
1287 | } | ||
1288 | } | ||
1289 | |||
1290 | private void processLandBuy(Object osender, EventManager.LandBuyArgs e) | ||
1291 | { | ||
1292 | lock (e) | ||
1293 | { | ||
1294 | if (e.economyValidated == true && e.transactionID == 0) | ||
1295 | { | ||
1296 | e.transactionID = Util.UnixTimeSinceEpoch(); | ||
1297 | |||
1298 | if (doMoneyTransfer(e.agentId, e.parcelOwnerID, e.parcelPrice, 0, "Land purchase")) | ||
1299 | { | ||
1300 | lock (e) | ||
1301 | { | ||
1302 | e.amountDebited = e.parcelPrice; | ||
1303 | } | ||
1304 | } | ||
1305 | } | ||
1306 | } | ||
1307 | } | ||
1308 | |||
1309 | /// <summary> | ||
1310 | /// THis method gets called when someone pays someone else as a gift. | ||
1311 | /// </summary> | ||
1312 | /// <param name="osender"></param> | ||
1313 | /// <param name="e"></param> | ||
1314 | private void MoneyTransferAction(Object osender, EventManager.MoneyTransferArgs e) | ||
1315 | { | ||
1316 | IClientAPI sender = null; | ||
1317 | IClientAPI receiver = null; | ||
1318 | |||
1319 | if (m_MoneyAddress.Length > 0) // Handled on server | ||
1320 | e.description = String.Empty; | ||
1321 | |||
1322 | if (e.transactiontype == 5008) // Object gets paid | ||
1323 | { | ||
1324 | sender = LocateClientObject(e.sender); | ||
1325 | if (sender != null) | ||
1326 | { | ||
1327 | SceneObjectPart part = findPrim(e.receiver); | ||
1328 | if (part == null) | ||
1329 | return; | ||
1330 | |||
1331 | string name = resolveAgentName(part.OwnerID); | ||
1332 | if (name == String.Empty) | ||
1333 | name = "(hippos)"; | ||
1334 | |||
1335 | receiver = LocateClientObject(part.OwnerID); | ||
1336 | |||
1337 | string description = String.Format("Paid {0} via object {1}", name, e.description); | ||
1338 | bool transactionresult = doMoneyTransfer(e.sender, part.OwnerID, e.amount, e.transactiontype, description); | ||
1339 | |||
1340 | if (transactionresult) | ||
1341 | { | ||
1342 | ObjectPaid handlerOnObjectPaid = OnObjectPaid; | ||
1343 | if (handlerOnObjectPaid != null) | ||
1344 | { | ||
1345 | handlerOnObjectPaid(e.receiver, e.sender, e.amount); | ||
1346 | } | ||
1347 | } | ||
1348 | |||
1349 | if (e.sender != e.receiver) | ||
1350 | { | ||
1351 | sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.sender)); | ||
1352 | } | ||
1353 | if (receiver != null) | ||
1354 | { | ||
1355 | receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(part.OwnerID)); | ||
1356 | } | ||
1357 | } | ||
1358 | return; | ||
1359 | } | ||
1360 | |||
1361 | sender = LocateClientObject(e.sender); | ||
1362 | if (sender != null) | ||
1363 | { | ||
1364 | receiver = LocateClientObject(e.receiver); | ||
1365 | |||
1366 | bool transactionresult = doMoneyTransfer(e.sender, e.receiver, e.amount, e.transactiontype, e.description); | ||
1367 | |||
1368 | if (e.sender != e.receiver) | ||
1369 | { | ||
1370 | if (sender != null) | ||
1371 | { | ||
1372 | sender.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.sender)); | ||
1373 | } | ||
1374 | } | ||
1375 | |||
1376 | if (receiver != null) | ||
1377 | { | ||
1378 | receiver.SendMoneyBalance(UUID.Random(), transactionresult, Utils.StringToBytes(e.description), GetFundsForAgentID(e.receiver)); | ||
1379 | } | ||
1380 | } | ||
1381 | else | ||
1382 | { | ||
1383 | m_log.Warn("[MONEY]: Potential Fraud Warning, got money transfer request for avatar that isn't in this simulator - Details; Sender:" + | ||
1384 | e.sender.ToString() + " Receiver: " + e.receiver.ToString() + " Amount: " + e.amount.ToString()); | ||
1385 | } | ||
1386 | } | ||
1387 | |||
1388 | /// <summary> | ||
1389 | /// Event Handler for when a root agent becomes a child agent | ||
1390 | /// </summary> | ||
1391 | /// <param name="avatar"></param> | ||
1392 | private void MakeChildAgent(ScenePresence avatar) | ||
1393 | { | ||
1394 | lock (m_rootAgents) | ||
1395 | { | ||
1396 | if (m_rootAgents.ContainsKey(avatar.UUID)) | ||
1397 | { | ||
1398 | if (m_rootAgents[avatar.UUID] == avatar.Scene.RegionInfo.originRegionID) | ||
1399 | { | ||
1400 | m_rootAgents.Remove(avatar.UUID); | ||
1401 | // m_log.Debug("[MONEY]: Removing " + avatar.Firstname + " " + avatar.Lastname + " as a root agent"); | ||
1402 | } | ||
1403 | } | ||
1404 | } | ||
1405 | } | ||
1406 | |||
1407 | /// <summary> | ||
1408 | /// Event Handler for when the client logs out. | ||
1409 | /// </summary> | ||
1410 | /// <param name="AgentId"></param> | ||
1411 | private void ClientLoggedOut(UUID AgentId) | ||
1412 | { | ||
1413 | lock (m_rootAgents) | ||
1414 | { | ||
1415 | if (m_rootAgents.ContainsKey(AgentId)) | ||
1416 | { | ||
1417 | m_rootAgents.Remove(AgentId); | ||
1418 | //m_log.Info("[MONEY]: Removing " + AgentId + ". Agent logged out."); | ||
1419 | } | ||
1420 | } | ||
1421 | } | ||
1422 | |||
1423 | /// <summary> | ||
1424 | /// Call this when the client disconnects. | ||
1425 | /// </summary> | ||
1426 | /// <param name="client"></param> | ||
1427 | public void ClientClosed(IClientAPI client) | ||
1428 | { | ||
1429 | ClientClosed(client.AgentId); | ||
1430 | } | ||
1431 | |||
1432 | /// <summary> | ||
1433 | /// Event Handler for when an Avatar enters one of the parcels in the simulator. | ||
1434 | /// </summary> | ||
1435 | /// <param name="avatar"></param> | ||
1436 | /// <param name="localLandID"></param> | ||
1437 | /// <param name="regionID"></param> | ||
1438 | private void AvatarEnteringParcel(ScenePresence avatar, int localLandID, UUID regionID) | ||
1439 | { | ||
1440 | lock (m_rootAgents) | ||
1441 | { | ||
1442 | if (m_rootAgents.ContainsKey(avatar.UUID)) | ||
1443 | { | ||
1444 | if (avatar.Scene.RegionInfo.originRegionID != m_rootAgents[avatar.UUID]) | ||
1445 | { | ||
1446 | m_rootAgents[avatar.UUID] = avatar.Scene.RegionInfo.originRegionID; | ||
1447 | |||
1448 | |||
1449 | //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); | ||
1450 | // Claim User! my user! Mine mine mine! | ||
1451 | if (m_MoneyAddress.Length > 0) | ||
1452 | { | ||
1453 | Scene RegionItem = GetSceneByUUID(regionID); | ||
1454 | if (RegionItem != null) | ||
1455 | { | ||
1456 | Hashtable hresult = | ||
1457 | claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); | ||
1458 | if ((bool)hresult["success"] == true) | ||
1459 | { | ||
1460 | int funds = 0; | ||
1461 | try | ||
1462 | { | ||
1463 | funds = (Int32)hresult["funds"]; | ||
1464 | } | ||
1465 | catch (InvalidCastException) | ||
1466 | { | ||
1467 | } | ||
1468 | SetLocalFundsForAgentID(avatar.UUID, funds); | ||
1469 | } | ||
1470 | else | ||
1471 | { | ||
1472 | avatar.ControllingClient.SendAgentAlertMessage((string)hresult["errorMessage"], true); | ||
1473 | } | ||
1474 | } | ||
1475 | } | ||
1476 | } | ||
1477 | else | ||
1478 | { | ||
1479 | ILandObject obj = avatar.Scene.LandChannel.GetLandObject(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y); | ||
1480 | if ((obj.landData.Flags & (uint)Parcel.ParcelFlags.AllowDamage) != 0) | ||
1481 | { | ||
1482 | avatar.Invulnerable = false; | ||
1483 | } | ||
1484 | else | ||
1485 | { | ||
1486 | avatar.Invulnerable = true; | ||
1487 | } | ||
1488 | } | ||
1489 | } | ||
1490 | else | ||
1491 | { | ||
1492 | lock (m_rootAgents) | ||
1493 | { | ||
1494 | m_rootAgents.Add(avatar.UUID, avatar.Scene.RegionInfo.originRegionID); | ||
1495 | } | ||
1496 | if (m_MoneyAddress.Length > 0) | ||
1497 | { | ||
1498 | Scene RegionItem = GetSceneByUUID(regionID); | ||
1499 | if (RegionItem != null) | ||
1500 | { | ||
1501 | Hashtable hresult = claim_user(avatar.UUID, avatar.ControllingClient.SecureSessionId, regionID, RegionItem.RegionInfo.regionSecret); | ||
1502 | if ((bool) hresult["success"] == true) | ||
1503 | { | ||
1504 | int funds = 0; | ||
1505 | try | ||
1506 | { | ||
1507 | funds = (Int32) hresult["funds"]; | ||
1508 | } | ||
1509 | catch (InvalidCastException) | ||
1510 | { | ||
1511 | } | ||
1512 | SetLocalFundsForAgentID(avatar.UUID, funds); | ||
1513 | } | ||
1514 | else | ||
1515 | { | ||
1516 | avatar.ControllingClient.SendAgentAlertMessage((string) hresult["errorMessage"], true); | ||
1517 | } | ||
1518 | } | ||
1519 | } | ||
1520 | |||
1521 | //m_log.Info("[MONEY]: Claiming " + avatar.Firstname + " " + avatar.Lastname + " in region:" + avatar.RegionHandle + "."); | ||
1522 | } | ||
1523 | } | ||
1524 | //m_log.Info("[FRIEND]: " + avatar.Name + " status:" + (!avatar.IsChildAgent).ToString()); | ||
1525 | } | ||
1526 | |||
1527 | public int GetBalance(IClientAPI client) | ||
1528 | { | ||
1529 | GetClientFunds(client); | ||
1530 | |||
1531 | lock (m_KnownClientFunds) | ||
1532 | { | ||
1533 | if (!m_KnownClientFunds.ContainsKey(client.AgentId)) | ||
1534 | return 0; | ||
1535 | |||
1536 | return m_KnownClientFunds[client.AgentId]; | ||
1537 | } | ||
1538 | } | ||
1539 | |||
1540 | // Please do not refactor these to be just one method | ||
1541 | // Existing implementations need the distinction | ||
1542 | // | ||
1543 | public bool UploadCovered(IClientAPI client) | ||
1544 | { | ||
1545 | return AmountCovered(client, PriceUpload); | ||
1546 | } | ||
1547 | |||
1548 | public bool GroupCreationCovered(IClientAPI client) | ||
1549 | { | ||
1550 | return AmountCovered(client, PriceGroupCreate); | ||
1551 | } | ||
1552 | |||
1553 | public bool AmountCovered(IClientAPI client, int amount) | ||
1554 | { | ||
1555 | if (GetBalance(client) < amount) | ||
1556 | return false; | ||
1557 | return true; | ||
1558 | } | ||
1559 | |||
1560 | #endregion | ||
1561 | |||
1562 | public void ObjectBuy(IClientAPI remoteClient, UUID agentID, | ||
1563 | UUID sessionID, UUID groupID, UUID categoryID, | ||
1564 | uint localID, byte saleType, int salePrice) | ||
1565 | { | ||
1566 | GetClientFunds(remoteClient); | ||
1567 | |||
1568 | if (!m_KnownClientFunds.ContainsKey(remoteClient.AgentId)) | ||
1569 | { | ||
1570 | remoteClient.SendAgentAlertMessage("Unable to buy now. Your account balance was not found.", false); | ||
1571 | return; | ||
1572 | } | ||
1573 | |||
1574 | int funds = m_KnownClientFunds[remoteClient.AgentId]; | ||
1575 | |||
1576 | if (salePrice != 0 && funds < salePrice) | ||
1577 | { | ||
1578 | remoteClient.SendAgentAlertMessage("Unable to buy now. You don't have sufficient funds.", false); | ||
1579 | return; | ||
1580 | } | ||
1581 | |||
1582 | Scene s = LocateSceneClientIn(remoteClient.AgentId); | ||
1583 | |||
1584 | SceneObjectPart part = s.GetSceneObjectPart(localID); | ||
1585 | if (part == null) | ||
1586 | { | ||
1587 | remoteClient.SendAgentAlertMessage("Unable to buy now. The object was not found.", false); | ||
1588 | return; | ||
1589 | } | ||
1590 | |||
1591 | if (s.PerformObjectBuy(remoteClient, categoryID, localID, saleType)) | ||
1592 | doMoneyTransfer(remoteClient.AgentId, part.OwnerID, salePrice, 5000, "Object buy"); | ||
1593 | } | ||
1594 | } | ||
1595 | |||
1596 | public enum TransactionType : int | ||
1597 | { | ||
1598 | SystemGenerated = 0, | ||
1599 | RegionMoneyRequest = 1, | ||
1600 | Gift = 2, | ||
1601 | Purchase = 3 | ||
1602 | } | ||
1603 | |||
1604 | |||
1605 | } | ||
diff --git a/OpenSim/Region/ReplaceableModules/README.txt b/OpenSim/Region/ReplaceableModules/README.txt new file mode 100644 index 0000000..e8e759b --- /dev/null +++ b/OpenSim/Region/ReplaceableModules/README.txt | |||
@@ -0,0 +1,5 @@ | |||
1 | This folder is for modules that we intend to let users and system admins replace. | ||
2 | |||
3 | This folder should never end up a project. Only subfolders should end up as a project. The idea here is that each folder | ||
4 | will produce a project and a separate .dll assembly for the module that will get picked up by the module loader. | ||
5 | To replace the functionality, you simply replace the .dll with a different one. \ No newline at end of file | ||