Ejemplo n.º 1
0
        public void GenerateTerrain()
        {
            Terrain = new VMTSOSurroundingTerrain();
            var coords = MapCoordinates.Unpack(LotPersist.location);
            var map    = Realestate.GetMap();

            for (int y = 0; y < 3; y++)
            {
                for (int x = 0; x < 3; x++)
                {
                    Terrain.BlendN[x, y] = map.GetBlend((coords.X - 1) + x, (coords.Y - 1) + y);
                }
            }

            for (int y = 0; y < 3; y++)
            {
                for (int x = 0; x < 3; x++)
                {
                    Terrain.Roads[x, y] = map.GetRoad((coords.X - 1) + x, (coords.Y - 1) + y);
                }
            }

            for (int y = 0; y < 4; y++)
            {
                for (int x = 0; x < 4; x++)
                {
                    Terrain.Height[x, y] = map.GetElevation((coords.X - 1) + x, (coords.Y - 1) + y);
                }
            }
        }
        protected override void PreLoad(Callback <uint, Neighborhood> appender)
        {
            using (var db = DAFactory.Get())
            {
                var nhoods = db.Neighborhoods.All(ShardId);

                var midnight          = LotVisitUtils.Midnight(); //gets this morning's midnight (basically current date, with day reset)
                var activityBeginning = midnight - new TimeSpan(7, 0, 0, 0);

                var visits     = db.LotVisits.StreamBetweenPlusNhood(ShardId, activityBeginning, midnight).ToList();
                var enumerator = visits.GetEnumerator();
                var nhoodHours = new Dictionary <uint, double>();
                while (enumerator.MoveNext())
                {
                    var visit = enumerator.Current;
                    var span  = LotVisitUtils.CalculateDateOverlap(activityBeginning, midnight, visit.time_created, visit.time_closed.Value);
                    if (nhoodHours.ContainsKey(visit.neighborhood_id))
                    {
                        nhoodHours[visit.neighborhood_id] += span.TotalMinutes;
                    }
                    else
                    {
                        nhoodHours.Add(visit.neighborhood_id, span.TotalMinutes);
                    }
                }

                var order = nhoodHours.OrderByDescending(x => x.Value).ToList();
                WorstRating = nhoods.Count;
                foreach (var item in nhoods)
                {
                    var lots      = db.Lots.GetLocationsInNhood((uint)item.neighborhood_id);
                    var avatars   = db.Avatars.GetLivingInNhood((uint)item.neighborhood_id);
                    var townHall  = db.Lots.Get(item.town_hall_id ?? 0)?.location ?? 0;
                    var cycle     = (item.election_cycle_id == null) ? null : db.Elections.GetCycle(item.election_cycle_id.Value);
                    var converted = HydrateOne(item, avatars, lots, townHall, cycle, visits, order);
                    var intId     = (uint)item.neighborhood_id;
                    appender(intId, converted);
                }

                //
                var neighObj = nhoods.Select(x =>
                {
                    var loc    = MapCoordinates.Unpack(x.location);
                    var result = new CityNeighbourhood()
                    {
                        Name        = x.name,
                        Color       = new Color(x.color),
                        Description = x.description,
                        GUID        = x.guid,
                        Location    = new Point(loc.X, loc.Y),
                        ID          = x.neighborhood_id
                    };
                    return(result);
                }).ToList();

                Lots.CityRepresentation.City_NeighJSON = JsonConvert.SerializeObject(neighObj);
            }
        }
Ejemplo n.º 3
0
        protected override Lot CreateInstance(uint key)
        {
            var coords = MapCoordinates.Unpack(key);

            var lot = base.CreateInstance(key);

            lot.Id           = key;
            lot.Lot_Location = new Location()
            {
                Location_X = coords.X,
                Location_Y = coords.Y
            };
            //TODO: Use the string tables
            lot.Lot_Name = "Retrieving...";
            return(lot);
        }
Ejemplo n.º 4
0
        //Should only get here for non-occupied lots that just need a price, we can avoid caching these
        protected override Lot LazyLoad(uint key)
        {
            var location = MapCoordinates.Unpack(key);

            //Empty lot
            return(new Lot
            {
                Id = key,

                Lot_IsOnline = false,
                Lot_Location = new Location {
                    Location_X = location.X, Location_Y = location.Y
                },
                //Lot_Price = 0,
                Lot_Price = (uint)Realestate.GetPurchasePrice(location.X, location.Y),
                Lot_OwnerVec = ImmutableList.Create <uint>(),
                Lot_RoommateVec = ImmutableList.Create <uint>(),

                Lot_Thumbnail = new cTSOGenericData(new byte[0]),
                Lot_ThumbnailCheckSum = key
            });
        }
Ejemplo n.º 5
0
        public void LoadAdj()
        {
            LOG.Info("Loading adj lots for lot with dbid = " + Context.DbId);
            HollowLots = new byte[9][];
            var myPos = MapCoordinates.Unpack(LotPersist.location);

            foreach (var lot in LotAdj)
            {
                try
                {
                    var adjLotStr = lot.lot_id.ToString("x8");
                    var path      = Path.Combine(Config.SimNFS, "Lots/" + adjLotStr + "/hollow.fsoh");

                    var pos = MapCoordinates.Unpack(lot.location);
                    var x   = (pos.X - myPos.X) + 1;
                    var y   = (pos.Y - myPos.Y) + 1;
                    if (x < 0 || x > 2 || y < 0 || y > 2)
                    {
                        continue;                                   //out of range (why does this happen?)
                    }
                    using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    {
                        int numBytesToRead = Convert.ToInt32(fs.Length);
                        var file           = new byte[(numBytesToRead)];
                        fs.Read(file, 0, numBytesToRead);
                        HollowLots[y * 3 + x] = file;
                    }
                }
                catch (Exception e)
                {
                    LOG.Warn("Failed to load adjacent lot :(");
                    LOG.Warn(e.ToString());
                    //don't bother
                }
            }
        }
Ejemplo n.º 6
0
        protected Lot HydrateOne(DbLot lot, List <DbRoommate> roommates, List <DbLotAdmit> admit)
        {
            var location = MapCoordinates.Unpack(lot.location);

            /* **experimental** we're leaving this up to ASP.NET api for now (behind cloudflare)
             * //attempt to load the lot's thumbnail.
             * var path = Path.Combine(NFS.GetBaseDirectory(), "Lots/" + lot.lot_id.ToString("x8") + "/thumb.png");
             * cTSOGenericData thumb = null;
             * try
             * {
             *  using (FileStream fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
             *  {
             *      int numBytesToRead = Convert.ToInt32(fs.Length);
             *      var file = new byte[(numBytesToRead)];
             *      fs.Read(file, 0, numBytesToRead);
             *      thumb = new cTSOGenericData(file);
             *  }
             * }
             * catch (Exception) {
             *  thumb = new cTSOGenericData(new byte[0]);
             * }
             */

            var result = new Lot
            {
                DbId = lot.lot_id,
                Id   = lot.location,

                Lot_Name     = lot.name,
                Lot_IsOnline = false,
                Lot_Location = new Location {
                    Location_X = location.X, Location_Y = location.Y
                },
                Lot_Price        = (uint)Realestate.GetPurchasePrice(location.X, location.Y),
                Lot_LeaderID     = lot.owner_id ?? 0,
                Lot_OwnerVec     = ImmutableList.Create(lot.owner_id ?? 0),
                Lot_RoommateVec  = ImmutableList.Create <uint>(),
                Lot_LotAdmitInfo = new LotAdmitInfo()
                {
                    LotAdmitInfo_AdmitMode = lot.admit_mode
                },
                Lot_NumOccupants  = 0,
                Lot_Category      = (byte)lot.category,
                Lot_SkillGamemode = lot.skill_mode,
                Lot_LastCatChange = lot.category_change_date,
                Lot_Description   = lot.description,
                Lot_Thumbnail     = new cTSOGenericData(new byte[0]),
            };

            foreach (var roomie in roommates)
            {
                if (roomie.is_pending == 0)
                {
                    result.Lot_RoommateVec = result.Lot_RoommateVec.Add(roomie.avatar_id);
                }
            }

            var admitL = new List <uint>();
            var banL   = new List <uint>();

            foreach (var item in admit)
            {
                if (item.admit_type == 0)
                {
                    admitL.Add(item.avatar_id);
                }
                else
                {
                    banL.Add(item.avatar_id);
                }
            }
            result.Lot_LotAdmitInfo.LotAdmitInfo_AdmitList = ImmutableList.ToImmutableList(admitL);
            result.Lot_LotAdmitInfo.LotAdmitInfo_BanList   = ImmutableList.ToImmutableList(banL);

            return(result);
        }
Ejemplo n.º 7
0
        public async void Handle(IVoltronSession session, PurchaseLotRequest packet)
        {
            if (session.IsAnonymous) //CAS users can't do this.
            {
                return;
            }

            var isPurchasable = Realestate.IsPurchasable(packet.LotLocation_X, packet.LotLocation_Y);

            if (!isPurchasable)
            {
                session.Write(new PurchaseLotResponse()
                {
                    Status = PurchaseLotStatus.FAILED,
                    Reason = PurchaseLotFailureReason.LOT_NOT_PURCHASABLE
                });
                return;
            }

            var packedLocation = MapCoordinates.Pack(packet.LotLocation_X, packet.LotLocation_Y);
            var price          = Realestate.GetPurchasePrice(packet.LotLocation_X, packet.LotLocation_Y);
            int resultFunds;

            uint lotId = 0;

            using (var db = DA.Get())
            {
                var ownedLot = db.Lots.GetByOwner(session.AvatarId);
                if (ownedLot != null)
                {
                    //we own the lot we are roomie of.
                    var roommates = db.Roommates.GetLotRoommates(ownedLot.lot_id);
                    var ds        = await DataService.Get <FSO.Common.DataService.Model.Lot>(ownedLot.location);

                    if (ds.Lot_IsOnline)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.NOT_OFFLINE_FOR_MOVE //TODO: race condition might make this possible?
                        });
                        return;
                    }

                    if (roommates.Count > 1)
                    {
                        //cannot start fresh with roommates for now.
                        packet.StartFresh = false;
                    }

                    var oldLoc   = MapCoordinates.Unpack(ownedLot.location);
                    var moveCost = price - Realestate.GetPurchasePrice(oldLoc.X, oldLoc.Y);
                    moveCost += 2000;                                                                             //flat rate for moving location

                    var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, moveCost, 5); //expenses misc... maybe add specific for lot
                    resultFunds = transactionResult.source_budget;
                    if (!transactionResult.success)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status   = PurchaseLotStatus.FAILED,
                            Reason   = PurchaseLotFailureReason.INSUFFICIENT_FUNDS,
                            NewFunds = resultFunds
                        });
                        return;
                    }
                    db.Lots.UpdateLocation(ownedLot.lot_id, packedLocation, packet.StartFresh);

                    DataService.Invalidate <FSO.Common.DataService.Model.Lot>(ownedLot.location); //nullify old lot
                    DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation);    //update new lot
                }
                else
                {
                    //we may still be roomie in a lot. If we are, we must be removed from that lot.
                    var myLots = db.Roommates.GetAvatarsLots(session.AvatarId);
                    if (myLots.Count > 0)
                    {
                        //we can't be in the lot when this happens. Make sure city owns avatar.
                        bool canEvict = session.AvatarClaimId != 0;
                        if (!canEvict)
                        {
                            var claim = db.AvatarClaims.Get(session.AvatarClaimId);
                            if (claim.owner == Context.Config.Call_Sign)
                            {
                                db.Roommates.RemoveRoommate(session.AvatarId, myLots[0].lot_id);
                                canEvict = true;
                            }
                            else
                            {
                                canEvict = false;
                            }
                        }

                        if (!canEvict)
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status = PurchaseLotStatus.FAILED,
                                Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT
                            });
                            return;
                        }
                    }

                    var name = packet.Name;
                    if (!GlobalRealestate.ValidateLotName(name))
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.NAME_VALIDATION_ERROR
                        });
                        return;
                    }

                    var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, price, 5); //expenses misc... maybe add specific for lot
                    resultFunds = transactionResult.source_budget;
                    if (!transactionResult.success)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status   = PurchaseLotStatus.FAILED,
                            Reason   = PurchaseLotFailureReason.INSUFFICIENT_FUNDS,
                            NewFunds = resultFunds
                        });
                        return;
                    }

                    try
                    {
                        lotId = db.Lots.Create(new DbLot
                        {
                            name     = name,
                            shard_id = Context.ShardId,

                            location             = packedLocation,
                            owner_id             = session.AvatarId,
                            created_date         = Epoch.Now,
                            category_change_date = Epoch.Default,
                            category             = LotCategory.none,

                            buildable_area = 1,
                            description    = ""
                        });

                        DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation);
                    }
                    catch (Exception ex)
                    {
                        var returnMoney = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, price, 5); //refund
                        //Name taken
                        if (ex.Message == "NAME")
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status   = PurchaseLotStatus.FAILED,
                                Reason   = PurchaseLotFailureReason.NAME_TAKEN, //TODO: this can also happen if the location was taken. (location is a UNIQUE row)
                                NewFunds = returnMoney.dest_budget
                            });
                        }
                        else
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status   = PurchaseLotStatus.FAILED,
                                Reason   = PurchaseLotFailureReason.UNKNOWN, //likely already roommate somewhere else, or we got race condition'd by another roomie request
                                NewFunds = returnMoney.dest_budget
                            });
                        }
                        return;
                    }
                }
            }

            //lot init happens on first join, as part of the loading process. If the lot somehow crashes before first save, it'll just be a blank slate again.

            //TODO: Broadcast to the world a new lot exists. i think we do this?

            //Update my sim's lot
            var avatar = await DataService.Get <Avatar>(session.AvatarId);

            if (avatar != null)
            {
                avatar.Avatar_LotGridXY = packedLocation;
            }

            session.Write(new PurchaseLotResponse()
            {
                Status   = PurchaseLotStatus.SUCCESS,
                NewLotId = lotId,
                NewFunds = resultFunds
            });
        }
Ejemplo n.º 8
0
        public async void Handle(IVoltronSession session, PurchaseLotRequest packet)
        {
            if (session.IsAnonymous) //CAS users can't do this.
            {
                return;
            }

            var isPurchasable = Realestate.IsPurchasable(packet.LotLocation_X, packet.LotLocation_Y);

            if (!isPurchasable)
            {
                session.Write(new PurchaseLotResponse()
                {
                    Status = PurchaseLotStatus.FAILED,
                    Reason = PurchaseLotFailureReason.LOT_NOT_PURCHASABLE
                });
                return;
            }

            var packedLocation = MapCoordinates.Pack(packet.LotLocation_X, packet.LotLocation_Y);
            var price          = Realestate.GetPurchasePrice(packet.LotLocation_X, packet.LotLocation_Y);

            if (packet.MayorMode)
            {
                price = 2000;
            }
            int resultFunds;

            uint lotId = 0;

            using (var db = DA.Get())
            {
                if (db.Lots.GetByLocation(Context.ShardId, packedLocation) != null)
                {
                    session.Write(new PurchaseLotResponse()
                    {
                        Status = PurchaseLotStatus.FAILED,
                        Reason = PurchaseLotFailureReason.LOT_TAKEN,
                    });
                    return;
                }

                var targNhood = db.Neighborhoods.GetByLocation(packedLocation);
                var nhoodDS   = await DataService.Get <Neighborhood>((uint)targNhood.neighborhood_id);

                DbLot ownedLot;
                if (packet.MayorMode)
                {
                    // we want to move or place the town hall lot for the neighbourhood we're mayor of.
                    // 1. find what neighbourhood we are mayor of. fail if none.
                    // 2. verify the chosen location is in the correct neighborhood. fail if not
                    // 3. get the town hall lot for our nhood. exists = move, doesn't = new
                    var nhood = db.Neighborhoods.GetByMayor(session.AvatarId);
                    if (nhood == null)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.TH_NOT_MAYOR
                        });
                        return;
                    }

                    //verify the neighbourhood makes sense
                    if (nhood.neighborhood_id != (targNhood?.neighborhood_id ?? 0))
                    {
                        //we can't put this neighbourhood's town hall in another neighbourhood.
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.TH_INCORRECT_NHOOD
                        });
                        return;
                    }

                    //find the town hall property
                    if (nhood.town_hall_id == null)
                    {
                        ownedLot = null;
                    }
                    else
                    {
                        ownedLot = db.Lots.Get(nhood.town_hall_id.Value);
                    }
                }
                else
                {
                    if ((targNhood.flag & 1) > 0)
                    {
                        var me = db.Avatars.Get(session.AvatarId);
                        if (me == null || me.moderation_level == 0)
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status = PurchaseLotStatus.FAILED,
                                Reason = PurchaseLotFailureReason.NHOOD_RESERVED,
                            });
                            return;
                        }
                    }

                    ownedLot = db.Lots.GetByOwner(session.AvatarId);
                }

                if (ownedLot != null)
                {
                    //we own the lot we are roomie of.
                    var roommates = db.Roommates.GetLotRoommates(ownedLot.lot_id);
                    var ds        = await DataService.Get <FSO.Common.DataService.Model.Lot>(ownedLot.location);

                    if (ds.Lot_IsOnline)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.NOT_OFFLINE_FOR_MOVE //TODO: race condition might make this possible?
                        });
                        return;
                    }

                    if (roommates.Count > 1)
                    {
                        //cannot start fresh with roommates for now.
                        packet.StartFresh = false;
                    }

                    var oldLoc   = MapCoordinates.Unpack(ownedLot.location);
                    var moveCost = price - Realestate.GetPurchasePrice(oldLoc.X, oldLoc.Y);
                    moveCost += 2000; //flat rate for moving location
                    if (packet.MayorMode)
                    {
                        moveCost = 2000;
                    }

                    var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, moveCost, 5); //expenses misc... maybe add specific for lot
                    resultFunds = transactionResult.source_budget;
                    if (!transactionResult.success)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status   = PurchaseLotStatus.FAILED,
                            Reason   = PurchaseLotFailureReason.INSUFFICIENT_FUNDS,
                            NewFunds = resultFunds
                        });
                        return;
                    }

                    if (!db.Lots.UpdateLocation(ownedLot.lot_id, packedLocation, packet.StartFresh))
                    {
                        //needs to refund the player.
                        var transactionResult2 = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, moveCost, 5);
                        session.Write(new PurchaseLotResponse()
                        {
                            Status   = PurchaseLotStatus.FAILED,
                            Reason   = PurchaseLotFailureReason.LOT_TAKEN,
                            NewFunds = transactionResult2.source_budget
                        });
                        return;
                    }

                    DataService.Invalidate <FSO.Common.DataService.Model.Lot>(ownedLot.location); //nullify old lot
                    DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation);    //update new lot

                    if (packet.MayorMode)
                    {
                        if (nhoodDS != null)
                        {
                            nhoodDS.Neighborhood_TownHallXY = packedLocation;
                        }
                    }
                    if (nhoodDS != null)
                    {
                        nhoodDS.Neighborhood_LotCount    = (uint)db.Lots.GetLocationsInNhood(nhoodDS.Id).Count;
                        nhoodDS.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(nhoodDS.Id).Count;
                    }

                    var oldNhood = await DataService.Get <Neighborhood>(ownedLot.neighborhood_id);

                    if (oldNhood != null)
                    {
                        oldNhood.Neighborhood_LotCount    = (uint)db.Lots.GetLocationsInNhood(oldNhood.Id).Count;
                        oldNhood.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(oldNhood.Id).Count;
                    }

                    //update nhood move date for all roommates
                    if (ownedLot.neighborhood_id != targNhood.neighborhood_id)
                    {
                        foreach (var roomie in roommates)
                        {
                            db.Avatars.UpdateMoveDate(roomie.avatar_id, Epoch.Now);
                        }
                    }
                }
                else
                {
                    //we may still be roomie in a lot. If we are, we must be removed from that lot.
                    if (!packet.MayorMode)
                    {
                        var myLots = db.Roommates.GetAvatarsLots(session.AvatarId);
                        if (myLots.Count > 0)
                        {
                            if (myLots[0].permissions_level > 1)
                            {
                                //owner should not be able to move out of a lot implicitly
                                session.Write(new PurchaseLotResponse()
                                {
                                    Status = PurchaseLotStatus.FAILED,
                                    Reason = PurchaseLotFailureReason.UNKNOWN
                                });
                                return;
                            }
                            var lot = db.Lots.Get(myLots[0].lot_id);
                            if (lot != null)
                            {
                                var kickResult = await Kernel.Get <ChangeRoommateHandler>().TryKick(lot.location, session.AvatarId, session.AvatarId);

                                if (kickResult != Protocol.Electron.Model.ChangeRoommateResponseStatus.SELFKICK_SUCCESS)
                                {
                                    session.Write(new PurchaseLotResponse()
                                    {
                                        Status = PurchaseLotStatus.FAILED,
                                        Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT
                                    });
                                    return;
                                }
                            }

                            /*
                             * //we can't be in the lot when this happens. Make sure city owns avatar.
                             * bool canEvict = session.AvatarClaimId != 0;
                             * if (!canEvict)
                             * {
                             *  var claim = db.AvatarClaims.Get(session.AvatarClaimId);
                             *  if (claim.owner == Context.Config.Call_Sign)
                             *  {
                             *      db.Roommates.RemoveRoommate(session.AvatarId, myLots[0].lot_id);
                             *      canEvict = true;
                             *  }
                             *  else canEvict = false;
                             * }
                             *
                             * if (!canEvict)
                             * {
                             *  session.Write(new PurchaseLotResponse()
                             *  {
                             *      Status = PurchaseLotStatus.FAILED,
                             *      Reason = PurchaseLotFailureReason.IN_LOT_CANT_EVICT
                             *  });
                             *  return;
                             * }
                             */
                        }
                    }

                    var name = packet.Name;
                    if (!GlobalRealestate.ValidateLotName(name))
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status = PurchaseLotStatus.FAILED,
                            Reason = PurchaseLotFailureReason.NAME_VALIDATION_ERROR
                        });
                        return;
                    }

                    var transactionResult = db.Avatars.Transaction(session.AvatarId, uint.MaxValue, price, 5); //expenses misc... maybe add specific for lot
                    resultFunds = transactionResult.source_budget;
                    if (!transactionResult.success)
                    {
                        session.Write(new PurchaseLotResponse()
                        {
                            Status   = PurchaseLotStatus.FAILED,
                            Reason   = PurchaseLotFailureReason.INSUFFICIENT_FUNDS,
                            NewFunds = resultFunds
                        });
                        return;
                    }

                    try
                    {
                        lotId = db.Lots.Create(new DbLot
                        {
                            name     = name,
                            shard_id = Context.ShardId,

                            location             = packedLocation,
                            owner_id             = session.AvatarId,
                            created_date         = Epoch.Now,
                            category_change_date = Epoch.Default,
                            category             = (packet.MayorMode) ? LotCategory.community : LotCategory.none,
                            neighborhood_id      = (uint)targNhood.neighborhood_id,

                            buildable_area = 1,
                            description    = ""
                        });

                        DataService.Invalidate <FSO.Common.DataService.Model.Lot>(packedLocation);

                        if (packet.MayorMode)
                        {
                            db.Neighborhoods.UpdateTownHall((uint)targNhood.neighborhood_id, lotId);
                            if (nhoodDS != null)
                            {
                                nhoodDS.Neighborhood_TownHallXY = packedLocation;
                            }
                        }
                        if (nhoodDS != null)
                        {
                            nhoodDS.Neighborhood_LotCount    = (uint)db.Lots.GetLocationsInNhood(nhoodDS.Id).Count;
                            nhoodDS.Neighborhood_AvatarCount = (uint)db.Avatars.GetLivingInNhood(nhoodDS.Id).Count;
                            db.Avatars.UpdateMoveDate(session.AvatarId, Epoch.Now);
                        }
                    }
                    catch (Exception ex)
                    {
                        var returnMoney = db.Avatars.Transaction(uint.MaxValue, session.AvatarId, price, 5); //refund
                        //Name taken
                        if (ex.Message == "NAME")
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status   = PurchaseLotStatus.FAILED,
                                Reason   = PurchaseLotFailureReason.NAME_TAKEN, //TODO: this can also happen if the location was taken. (location is a UNIQUE row)
                                NewFunds = returnMoney.dest_budget
                            });
                        }
                        else
                        {
                            session.Write(new PurchaseLotResponse()
                            {
                                Status   = PurchaseLotStatus.FAILED,
                                Reason   = PurchaseLotFailureReason.UNKNOWN, //likely already roommate somewhere else, or we got race condition'd by another roomie request
                                NewFunds = returnMoney.dest_budget
                            });
                        }
                        return;
                    }
                }
            }

            //lot init happens on first join, as part of the loading process. If the lot somehow crashes before first save, it'll just be a blank slate again.

            //Update my sim's lot
            var avatar = await DataService.Get <Avatar>(session.AvatarId);

            if (avatar != null)
            {
                avatar.Avatar_LotGridXY = packedLocation;
            }

            session.Write(new PurchaseLotResponse()
            {
                Status   = PurchaseLotStatus.SUCCESS,
                NewLotId = lotId,
                NewFunds = resultFunds
            });
        }