private Dictionary <long, PurchaseCollection> FindFilteredBuyables(PurchaseMode mode, bool filterRandom = true) { var buyables = FindBuyables(); if (mode == PurchaseMode.RANDOM) { if (filterRandom) { return(FilterBuyablesForNpcRatio(buyables, mode)); } else { return(buyables); } } var filtered = new Dictionary <long, PurchaseCollection>(); foreach (var entry in buyables) { long identityId = entry.Key; PurchaseCollection modes = entry.Value; if (!modes.Contains(mode)) { continue; } filtered.Add(identityId, modes); } return(FilterBuyablesForNpcRatio(filtered, mode)); }
/// <summary> /// Called on server when someone requests to place an object from inventory. /// Immediately "fails" verification to be queued later, once the data has been retrieved. /// </summary> /// <param name="vm"></param> /// <param name="caller"></param> /// <returns></returns> public override bool Verify(VM vm, VMAvatar caller) { if (Verified) { return(true); //set internally when transaction succeeds. trust that the verification happened. } //typically null caller, non-roommate cause failure. some lot specific things may apply. Mode = vm.PlatformState.Validator.GetPurchaseMode(Mode, caller, 0, true); if (Mode == PurchaseMode.Disallowed || !vm.TSOState.CanPlaceNewUserObject(vm)) { return(false); } vm.GlobalLink.RetrieveFromInventory(vm, ObjectPID, caller.PersistID, true, (uint guid, byte[] data) => { if (guid == 0) { return; //todo: error feedback? } GUID = guid; Data = data; Verified = true; vm.ForwardCommand(this); }); return(false); }
public override PurchaseMode GetPurchaseMode(PurchaseMode desired, VMAvatar ava, uint guid, bool fromInventory) { if (desired > PurchaseMode.Donate) { return(PurchaseMode.Disallowed); } if (ava == null) { return(PurchaseMode.Disallowed); } if (ava.AvatarState.Permissions < VMTSOAvatarPermissions.Roommate) { return(PurchaseMode.Disallowed); } //todo: build/buy limits for inventory, but allow rare/non-catalog placement if (!vm.TS1 && !fromInventory) { var catalog = Content.Content.Get().WorldCatalog; var item = catalog.GetItemByGUID(guid); var whitelist = (ava.AvatarState.Permissions == VMTSOAvatarPermissions.Roommate) ? RoomieWhiteList : BuilderWhiteList; if (item == null || !whitelist.Contains(item.Value.Category)) { if (ava.AvatarState.Permissions != VMTSOAvatarPermissions.Admin) { return(PurchaseMode.Disallowed); } } } return(PurchaseMode.Normal); }
public override bool Verify(VM vm, VMAvatar caller) { if (Verified) { return(true); //set internally when transaction succeeds. trust that the verification happened. } value = 0; //do not trust value from net if (!vm.TS1) { Mode = vm.PlatformState.Validator.GetPurchaseMode(Mode, caller, GUID, false); if (Mode == PurchaseMode.Disallowed) { return(false); } if (Mode == PurchaseMode.Normal && !vm.PlatformState.CanPlaceNewUserObject(vm)) { return(false); } if (Mode == PurchaseMode.Donate && !vm.PlatformState.CanPlaceNewDonatedObject(vm)) { return(false); } } //TODO: error feedback for client var catalog = Content.Content.Get().WorldCatalog; var item = catalog.GetItemByGUID(GUID); if (item != null) { var price = (int)item.Value.Price; var dcPercent = VMBuildableAreaInfo.GetDiscountFor(item.Value, vm); value = (price * (100 - dcPercent)) / 100; if (Mode == PurchaseMode.Donate) { value -= (value * 2) / 3; } } //TODO: fine grained purchase control based on user status //perform the transaction. If it succeeds, requeue the command vm.GlobalLink.PerformTransaction(vm, false, caller?.PersistID ?? uint.MaxValue, uint.MaxValue, value, (bool success, int transferAmount, uint uid1, uint budget1, uint uid2, uint budget2) => { if (success) { Verified = true; vm.ForwardCommand(this); } }); return(false); }
public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); GUID = reader.ReadUInt32(); x = reader.ReadInt16(); y = reader.ReadInt16(); level = reader.ReadSByte(); dir = (Direction)reader.ReadByte(); value = reader.ReadInt32(); Mode = (PurchaseMode)reader.ReadByte(); }
private void ListInternal(PurchaseMode mode, bool filterRandom = true) { var buyables = FindFilteredBuyables(mode, filterRandom); List <string> lines = new List <string>(); foreach (var entry in buyables) { long identityId = entry.Key; List <PurchaseMode> modes = entry.Value.ToList(); modes.Sort(); IMyFaction faction = FactionUtils.GetPlayerFaction(identityId); string factionString = ""; if (faction != null) { factionString = "[" + faction.Tag + "]"; } string name = PlayerUtils.GetPlayerNameById(identityId) + " " + factionString; string modesStr = string.Join(", ", modes); lines.Add(name + " -- " + modesStr); } lines.Sort(); StringBuilder sb = new StringBuilder(); foreach (var line in lines) { sb.AppendLine(line); } if (Context.Player == null) { Context.Respond($"Buyable GPS for " + mode); Context.Respond(sb.ToString()); } else { ModCommunication.SendMessageTo(new DialogMessage("Buyable GPS", "For " + mode, sb.ToString()), Context.Player.SteamUserId); } }
public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); ObjectPID = reader.ReadUInt32(); x = reader.ReadInt16(); y = reader.ReadInt16(); level = reader.ReadSByte(); dir = (Direction)reader.ReadByte(); GUID = reader.ReadUInt32(); var length = reader.ReadInt32(); if (length > 4096) { throw new Exception("Object data cannot be this large!"); } Data = reader.ReadBytes(length); Mode = (PurchaseMode)reader.ReadByte(); }
private Dictionary <long, PurchaseCollection> FindFilteredBuyablesForPlayer(PurchaseMode mode) { /* We dont want to have filtering for random in here to not filter twice. */ var buyables = FindFilteredBuyables(mode, false); var filtered = new Dictionary <long, PurchaseCollection>(); var player = Context.Player; var identityId = player.IdentityId; HashSet <long> factionMembers = new HashSet <long>(); if (Plugin.Config.FilterFactionMembers) { try { var faction = FactionUtils.GetPlayerFaction(identityId); factionMembers = new HashSet <long>(faction.Members.Keys); } catch (Exception e) { Log.Error(e, "Faction could not be checked, it potentially has no founder!"); } } factionMembers.Add(identityId); foreach (var entry in buyables) { long id = entry.Key; if (factionMembers.Contains(id)) { continue; } filtered.Add(entry.Key, entry.Value); } /* After removing Faction Members and the player himself we may have to check again if NPCs needs to go */ return(FilterBuyablesForNpcRatio(filtered, mode)); }
private bool CheckConformation(ICooldownKey cooldownKey, PurchaseMode mode, long price) { var cooldownManager = Plugin.ConfirmationManager; var commandKey = mode.ToString() + price; if (!cooldownManager.CheckCooldown(cooldownKey, commandKey, out _)) { cooldownManager.StopCooldown(cooldownKey); return(true); } Context.Respond("Are you sure you want to buy a gps for '" + mode + "' for " + price.ToString("#,##0") + " SC? Make sure you read the information in !gps help. Enter the command again within 30 seconds to confirm!"); if (mode == PurchaseMode.RANDOM) { Context.Respond("Make sure to check up your chances with '!gps list chances' to not get disappointed!"); } cooldownManager.StartCooldown(cooldownKey, commandKey, 30 * 1000); return(false); }
public override PurchaseMode GetPurchaseMode(PurchaseMode desired, VMAvatar ava, uint guid, bool fromInventory) { if (desired > PurchaseMode.Donate) { return(PurchaseMode.Disallowed); } if (ava == null) { return(PurchaseMode.Disallowed); } if (ava.AvatarState.Permissions < TSOPlatform.VMTSOAvatarPermissions.Roommate) { return(PurchaseMode.Disallowed); } if (base.GetPurchaseMode(desired, ava, guid, fromInventory) == PurchaseMode.Disallowed) { return(PurchaseMode.Disallowed); } if (desired == PurchaseMode.Normal && ava.AvatarState.Permissions < TSOPlatform.VMTSOAvatarPermissions.Owner) { return(PurchaseMode.Donate); } return(desired); }
private void BuyInternal(long price, PurchaseMode mode) { if (Context.Player == null) { Context.Respond("Only an actual player can buy GPS!"); return; } if (price < 0) { Context.Respond("This command was disabled in the settings. Use '!gps list commands' to see which commands are active!"); return; } var player = Context.Player; var identity = PlayerUtils.GetIdentityById(player.IdentityId); price = GetAdjustedPriceForPlayer(price, identity); if (Plugin.Config.MustBeInFactionToBuy) { var faction = FactionUtils.GetPlayerFaction(identity.IdentityId); if (faction == null) { Context.Respond("You must be part of a Faction to buy GPS!"); return; } } if (mode == PurchaseMode.ONLINE || mode == PurchaseMode.RANDOM) { var minPlayersOnline = Plugin.Config.MinPlayerOnlineToBuy; int onlineCount = MySession.Static.Players.GetOnlinePlayerCount(); if (onlineCount < minPlayersOnline) { Context.Respond("For Online/Random gps at least " + minPlayersOnline + " players must be online!"); return; } } var minOnlineTime = Plugin.Config.MinOnlineMinutesToBuy; if (minOnlineTime > 0) { var lastSeen = Plugin.getLastLoginDate(identity.IdentityId); var minOnlineDate = DateTime.Now.AddMinutes(-Plugin.Config.MinOnlineMinutesToBuy); if (lastSeen > minOnlineDate) { int differenceSeconds = (int)lastSeen.Subtract(minOnlineDate).TotalSeconds; int minutes = (differenceSeconds / 60); int seconds = differenceSeconds % 60; Context.Respond("You are not online for long enough! You must be online for at least " + minutes.ToString("00") + ":" + seconds.ToString("00") + " more minutes!"); return; } } var minPCUToBuy = Plugin.Config.MinPCUToBuy; if (minPCUToBuy > 0) { var pcuBuilt = identity.BlockLimits.PCUBuilt; var neededPcu = Plugin.Config.MinPCUToBuy; if (neededPcu > pcuBuilt) { Context.Respond("You dont have enough PCU to buy! You need at least " + (neededPcu - pcuBuilt) + " more!"); return; } } var cooldownManager = Plugin.CooldownManager; var cooldownManagerFaction = Plugin.CooldownManagerFactionChange; var steamId = new SteamIdCooldownKey(player.SteamUserId); long currentBalance = MyBankingSystem.GetBalance(player.IdentityId); if (currentBalance < price) { Context.Respond("You dont have enough credits to effort a GPS! You need at least " + price.ToString("#,##0") + " SC."); return; } if (!cooldownManagerFaction.CheckCooldown(steamId, GpsRoulettePlugin.COOLDOWN_COMMAND, out long remainingSecondsFaction)) { Log.Info("Faction Cooldown for Player " + player.DisplayName + " still running! " + remainingSecondsFaction + " seconds remaining!"); Context.Respond("Command is still on cooldown after you changed Factions for " + remainingSecondsFaction + " seconds."); return; } if (!cooldownManager.CheckCooldown(steamId, GpsRoulettePlugin.COOLDOWN_COMMAND, out long remainingSeconds)) { Log.Info("Cooldown for Player " + player.DisplayName + " still running! " + remainingSeconds + " seconds remaining!"); Context.Respond("Command is still on cooldown for " + remainingSeconds + " seconds."); return; } var buyables = FindFilteredBuyablesForPlayer(mode); if (buyables.Count == 0) { Context.Respond("Currently there is no GPS available for purchase. Please try again later!"); return; } if (!CheckConformation(steamId, mode, price)) { return; } if (BuyRandomFromDict(buyables)) { var config = Plugin.Config; var cooldownMs = config.CooldownMinutes * 60 * 1000L; MyBankingSystem.ChangeBalance(player.IdentityId, -price); cooldownManager.StartCooldown(steamId, GpsRoulettePlugin.COOLDOWN_COMMAND, cooldownMs); Context.Respond("Purchase successful!"); } else { Context.Respond("The location of the selected player could not be retrieved. The purchase was cancelled. Please try again."); } }
private Dictionary <long, PurchaseCollection> FilterBuyablesForNpcRatio(Dictionary <long, PurchaseCollection> buyables, PurchaseMode mode) { /* If not Random no checks need to be done. */ if (mode != PurchaseMode.RANDOM) { return(buyables); } int ratio = Plugin.Config.MaxPercentageNPCInRandomSelection; /* When all is NPC no checks needed */ if (ratio >= 100) { return(buyables); } var returnDictionary = new Dictionary <long, PurchaseCollection>(); var npcs = new Dictionary <long, PurchaseCollection>(); /* Filter whats NPC and what is not. */ foreach (var buyable in buyables) { if (buyable.Value.Contains(PurchaseMode.NPC)) { npcs.Add(buyable.Key, buyable.Value); } else { returnDictionary.Add(buyable.Key, buyable.Value); } } int numberOfEntries = returnDictionary.Count; /* If theres no Other entry available just return all buyables. Should only be NPC in there. */ if (numberOfEntries == 0) { return(buyables); } /* We must not round up because the percentage is a MAX value. */ int numberOfNpcsNeeded = (int)((100.0F / (100.0F - ratio) * numberOfEntries) - numberOfEntries); /* However the Min may override that, because its an absolute value compared to the dynamic percentage. */ numberOfNpcsNeeded = Math.Max(Plugin.Config.MinNpcsInRandomSelection, numberOfNpcsNeeded); int numberOfNpcsAdded = 0; /* Shuffle the NPCs to not always get the same ones. While a Dictionary is not "sorted" it still has a deterministic order. */ var npcList = npcs.ToList(); npcList.ShuffleList(); foreach (var npc in npcList) { /* If no NPC is needed we break out immediately. Otherwise when we have the desired amount */ if (numberOfNpcsAdded >= numberOfNpcsNeeded) { break; } returnDictionary.Add(npc.Key, npc.Value); numberOfNpcsAdded++; } return(returnDictionary); }
public abstract PurchaseMode GetPurchaseMode(PurchaseMode desired, VMAvatar ava, uint guid, bool fromInventory);