#W# Reworking Inn rooms.

This commit is contained in:
WarrentyExpired 2026-08-22 18:14:14 -04:00
parent 7104f56ebf
commit 66eafd3451
15 changed files with 918 additions and 576 deletions

View file

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Mobiles;
using Server.Multis;
using Server.ContextMenus;
namespace Server.Custom.InnRooms
{
public class InnExitDoor : Item
{
[Constructable]
public InnExitDoor() : this(0x6E5)
{
}
[Constructable]
public InnExitDoor(int itemID) : base(itemID)
{
Movable = false;
Name = "Room Exit";
}
public InnExitDoor(Serial serial) : base(serial)
{
}
[CommandProperty(AccessLevel.GameMaster)]
public bool ForceEviction
{
get { return false; }
set
{
if (value)
{
foreach (Item item in World.Items.Values)
{
if (item is InnRoomHouse)
{
InnRoomHouse house = (InnRoomHouse)item;
if (house.Map == this.Map && house.IsInside(this.Location, 16))
{
house.Evict();
break;
}
}
}
}
}
}
public override void OnDoubleClick(Mobile from)
{
if (!from.InRange(GetWorldLocation(), 2))
{
from.LocalOverheadMessage(Server.Network.MessageType.Regular, 0x3B2, 1019045);
return;
}
if (from is PlayerMobile)
{
PlayerMobile pm = (PlayerMobile)from;
if (pm.LastInnMap != null && pm.LastInnMap != Map.Internal && pm.LastInnLocation != Point3D.Zero)
{
pm.PlaySound(0x1EA);
pm.MoveToWorld(pm.LastInnLocation, pm.LastInnMap);
pm.SendMessage(68, "You step out of your room and back into the inn.");
pm.LastInnLocation = Point3D.Zero;
pm.LastInnMap = null;
}
else
{
from.SendMessage(33, "Your return location was lost. Please use a runebook or page a GameMaster.");
}
}
}
public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
{
base.GetContextMenuEntries(from, list);
if (from.Alive && from is PlayerMobile)
{
List<BaseHouse> houses = BaseHouse.GetHouses(from);
foreach (BaseHouse h in houses)
{
if (h is InnRoomHouse && h.IsInside(this.Location, 16))
{
// Cliloc 6171 displays as "Open House Menu"
list.Add(new ManageRoomEntry(from, (InnRoomHouse)h));
break;
}
}
}
}
private class ManageRoomEntry : ContextMenuEntry
{
private Mobile m_From;
private InnRoomHouse m_House;
public ManageRoomEntry(Mobile from, InnRoomHouse house) : base(1063490, 2)
{
m_From = from;
m_House = house;
}
public override void OnClick()
{
m_From.SendGump(new InnRoomManagementGump((PlayerMobile)m_From, m_House));
}
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write((int)0);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
}
}
}

View file

@ -0,0 +1,115 @@
using System;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Items;
using Server.Network;
namespace Server.Custom.InnRooms
{
public class InnRentalGump : Gump
{
private PlayerMobile m_Player;
public InnRentalGump(PlayerMobile pm) : base(150, 150)
{
m_Player = pm;
AddPage(0);
// Widened the background from 400 to 500
AddBackground(0, 0, 500, 300, 9270);
// Centered the header to match the new 500 width
AddHtml(0, 15, 500, 20, "<CENTER><BASEFONT COLOR=#FFFFFF>Inn Room Rentals</BASEFONT></CENTER>", false, false);
DrawRoomOption(1, "Small Room", InnRoomSize.Small, 60);
DrawRoomOption(2, "Medium Room", InnRoomSize.Medium, 120);
DrawRoomOption(3, "Large Room", InnRoomSize.Large, 180);
}
private void DrawRoomOption(int buttonId, string title, InnRoomSize size, int yPos)
{
int available = InnRoomManager.GetAvailableCount(size);
int cost = InnRoomManager.GetCost(size);
int total = 0;
foreach (RoomDefinition def in InnRoomManager.Rooms)
{
if (def.Size == size)
total++;
}
// Pushed the text to X: 70 to clear the button
AddHtml(70, yPos, 150, 20, $"<BASEFONT COLOR=#FCCA03>{title}</BASEFONT>", false, false);
AddHtml(70, yPos + 20, 200, 20, $"<BASEFONT COLOR=#FFFFFF>Cost: {cost} Gold</BASEFONT>", false, false);
// Pushed the availability count to X: 320 to sit nicely on the right side
AddHtml(320, yPos + 10, 150, 20, $"<BASEFONT COLOR=#FFFFFF>{available} / {total} Available</BASEFONT>", false, false);
if (available > 0)
{
// Kept the button firmly at X: 25
AddButton(25, yPos + 5, 4005, 4007, buttonId, GumpButtonType.Reply, 0);
}
else
{
AddHtml(25, yPos + 5, 20, 20, "<BASEFONT COLOR=#FF0000>X</BASEFONT>", false, false);
}
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 0 || m_Player == null) return;
InnRoomSize selectedSize = InnRoomSize.Small;
if (info.ButtonID == 2) selectedSize = InnRoomSize.Medium;
if (info.ButtonID == 3) selectedSize = InnRoomSize.Large;
int slot = InnRoomManager.FindFirstOpenSlot(selectedSize);
if (slot == -1)
{
m_Player.SendMessage(33, "Those rooms are currently sold out.");
return;
}
int cost = InnRoomManager.GetCost(selectedSize);
if (Banker.Withdraw(m_Player, cost))
{
RentRoom(selectedSize, slot);
}
else if (m_Player.Backpack != null && m_Player.Backpack.ConsumeTotal(typeof(Gold), cost))
{
RentRoom(selectedSize, slot);
}
else
{
m_Player.SendMessage(33, $"You need {cost} gold in your account or backpack to rent that room.");
}
}
private void RentRoom(InnRoomSize size, int slot)
{
m_Player.LastInnLocation = m_Player.Location;
m_Player.LastInnMap = m_Player.Map;
RoomDefinition roomDef = InnRoomManager.Rooms[slot];
Point3D voidRoomLoc = roomDef.Location;
Map voidRoomMap = roomDef.RoomMap;
InnRoomHouse newHouse = new InnRoomHouse(m_Player);
newHouse.RoomTier = size;
newHouse.RoomIndex = slot;
newHouse.RentExpires = DateTime.UtcNow + TimeSpan.FromDays(7);
newHouse.MoveToWorld(new Point3D(voidRoomLoc.X, voidRoomLoc.Y, voidRoomLoc.Z - 20), voidRoomMap);
m_Player.PlaySound(0x249);
m_Player.SendMessage(68, $"You have rented a {size} room.");
m_Player.MoveToWorld(voidRoomLoc, voidRoomMap);
}
}
}

View file

@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using Server;
using Server.Items;
using Server.Mobiles;
using Server.Multis;
namespace Server.Custom.InnRooms
{
public class InnRoomHouse : BaseHouse
{
public int RoomIndex;
private InnRoomSize m_RoomTier;
[CommandProperty(AccessLevel.GameMaster)]
public InnRoomSize RoomTier
{
get { return m_RoomTier; }
set
{
m_RoomTier = value;
MaxLockDowns = GetAosMaxLockdowns();
MaxSecures = GetAosMaxSecures();
}
}
// --- NEW EVICTION VARIABLES ---
public Mobile Renter;
public DateTime RentExpires;
private RentTimer m_Timer;
public override Rectangle2D[] Area
{
get { return new Rectangle2D[] { new Rectangle2D(-15, -15, 30, 30) }; }
}
public override Point3D BaseBanLocation
{
get { return new Point3D(this.X, this.Y - 5, this.Z + 20); }
}
public InnRoomHouse(Mobile owner) : base(0xA28, owner, 50, 2)
{
RestrictDecay = true;
Renter = owner;
// Start the internal clock!
m_Timer = new RentTimer(this);
m_Timer.Start();
}
public InnRoomHouse(Serial serial) : base(serial)
{
}
public override int GetAosMaxLockdowns()
{
switch (RoomTier)
{
case InnRoomSize.Small: return 350; // 125 Total Items
case InnRoomSize.Medium: return 375; // 250 Total Items
case InnRoomSize.Large: return 500; // 500 Total Items
default: return 125;
}
}
public override int GetAosMaxSecures()
{
switch (RoomTier)
{
case InnRoomSize.Small: return 2; // 2 Secure Chests
case InnRoomSize.Medium: return 3; // 3 Secure Chests
case InnRoomSize.Large: return 4; // 4 Secure Chests
default: return 2;
}
}
public override bool IsInside(Point3D p, int height)
{
if (Deleted) return false;
int rx = p.X - this.X;
int ry = p.Y - this.Y;
bool inArea = false;
foreach (Rectangle2D rect in Area)
{
if (rect.Contains(new Point2D(rx, ry)))
{
inArea = true;
break;
}
}
if (!inArea) return false;
if (p.Z >= this.Z && (p.Z + height) <= this.Z + 40) return true;
return false;
}
// --- EVICTION LOGIC ---
public void Evict()
{
if (Deleted) return;
// 1. Pack up items and send to bank
if (Renter != null && Renter.BankBox != null)
{
InnEvictionCrate crate = new InnEvictionCrate();
for (int i = Secures.Count - 1; i >= 0; --i)
{
SecureInfo info = Secures[i] as SecureInfo;
if (info != null && info.Item != null)
{
Container sec = info.Item;
sec.IsSecure = false;
sec.IsLockedDown = false;
sec.Movable = true;
crate.DropItem(sec);
}
}
Secures.Clear();
for (int i = LockDowns.Count - 1; i >= 0; --i)
{
Item item = LockDowns[i] as Item;
if (item != null)
{
item.IsLockedDown = false;
item.Movable = true;
crate.DropItem(item);
}
}
LockDowns.Clear();
if (crate.Items.Count > 0)
{
Renter.BankBox.DropItem(crate);
Renter.SendMessage(33, "Your inn room rent has expired. Your belongings were moved to your bank.");
}
else
{
crate.Delete();
}
}
// 2. Kick anyone currently inside the room back to town
List<Mobile> insideMobiles = GetMobiles();
foreach (Mobile m in insideMobiles)
{
if (m is PlayerMobile)
{
PlayerMobile pm = (PlayerMobile)m;
if (pm.LastInnMap != null && pm.LastInnLocation != Point3D.Zero)
{
pm.MoveToWorld(pm.LastInnLocation, pm.LastInnMap);
pm.LastInnLocation = Point3D.Zero;
pm.LastInnMap = null;
}
else
{
// Safe fallback if they don't have an anchor
pm.MoveToWorld(new Point3D(865, 605, 0), Map.Trammel); // Britain Bank
}
pm.SendMessage(33, "The rent has expired and you have been evicted from the room.");
}
}
// 3. Destroy the house (Freeing the slot for the Manager!)
Delete();
}
public override void OnDelete()
{
if (m_Timer != null)
m_Timer.Stop();
base.OnDelete();
}
public override void Serialize(GenericWriter writer)
{
base.Serialize(writer);
writer.Write((int)2); // Version bumped to 2!
writer.Write(Renter);
writer.Write(RentExpires);
writer.Write((int)RoomTier);
writer.Write(RoomIndex);
}
public override void Deserialize(GenericReader reader)
{
base.Deserialize(reader);
int version = reader.ReadInt();
switch (version)
{
case 2:
{
Renter = reader.ReadMobile();
RentExpires = reader.ReadDateTime();
goto case 1;
}
case 1:
{
RoomTier = (InnRoomSize)reader.ReadInt();
RoomIndex = reader.ReadInt();
goto case 0;
}
case 0:
{
break;
}
}
// Restart the internal clock on server boot
m_Timer = new RentTimer(this);
m_Timer.Start();
}
// --- INTERNAL CLOCK ---
private class RentTimer : Timer
{
private InnRoomHouse m_House;
// Checks the expiration date every 1 minute
public RentTimer(InnRoomHouse house) : base(TimeSpan.FromMinutes(1.0), TimeSpan.FromMinutes(1.0))
{
m_House = house;
Priority = TimerPriority.FiveSeconds;
}
protected override void OnTick()
{
if (m_House == null || m_House.Deleted)
{
Stop();
return;
}
if (DateTime.UtcNow > m_House.RentExpires)
{
m_House.Evict();
Stop();
}
}
}
}
// --- READ-ONLY EVICTION CRATE ---
public class InnEvictionCrate : WoodenBox
{
public override int DefaultMaxItems { get { return 500; } }
public override int DefaultMaxWeight { get { return 5000; } }
[Constructable]
public InnEvictionCrate()
{
Name = "Evicted Inn Room Belongings";
Hue = 33;
}
public InnEvictionCrate(Serial serial) : base(serial) { }
public override bool CheckHold(Mobile m, Item item, bool message, bool checkItems, int plusItems, int plusWeight)
{
if (m.AccessLevel < AccessLevel.GameMaster)
{
if (message) m.SendLocalizedMessage(1061145);
return false;
}
return base.CheckHold(m, item, message, checkItems, plusItems, plusWeight);
}
public override void OnItemRemoved(Item item)
{
base.OnItemRemoved(item);
if (this.TotalItems == 0) Delete();
}
public override void Serialize(GenericWriter writer) { base.Serialize(writer); writer.Write((int)0); }
public override void Deserialize(GenericReader reader) { base.Deserialize(reader); int version = reader.ReadInt(); }
}
}

View file

@ -0,0 +1,82 @@
using System;
using Server;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
using Server.Multis;
namespace Server.Custom.InnRooms
{
public class InnRoomManagementGump : Gump
{
private PlayerMobile m_Player;
private InnRoomHouse m_House;
public InnRoomManagementGump(PlayerMobile pm, InnRoomHouse house) : base(150, 150)
{
m_Player = pm;
m_House = house;
AddPage(0);
AddBackground(0, 0, 400, 350, 9270);
AddHtml(0, 15, 400, 20, "<CENTER><BASEFONT COLOR=#FFFFFF>Room Management</BASEFONT></CENTER>", false, false);
// --- STORAGE STATS ---
AddHtml(40, 60, 150, 20, "<BASEFONT COLOR=#FCCA03>Lockdowns:</BASEFONT>", false, false);
AddHtml(150, 60, 200, 20, $"<BASEFONT COLOR=#FFFFFF>{m_House.LockDownCount} / {m_House.MaxLockDowns}</BASEFONT>", false, false);
AddHtml(40, 90, 150, 20, "<BASEFONT COLOR=#FCCA03>Secures:</BASEFONT>", false, false);
AddHtml(150, 90, 200, 20, $"<BASEFONT COLOR=#FFFFFF>{m_House.SecureCount} / {m_House.MaxSecures}</BASEFONT>", false, false);
// --- RENT INFO ---
AddHtml(40, 140, 150, 20, "<BASEFONT COLOR=#FCCA03>Rent Expires:</BASEFONT>", false, false);
AddHtml(150, 140, 200, 20, $"<BASEFONT COLOR=#FFFFFF>{m_House.RentExpires.ToString("g")}</BASEFONT>", false, false);
// --- ACTIONS ---
// Auto-Renew Status Placeholder (We will wire this up next!)
AddHtml(40, 190, 250, 20, "<BASEFONT COLOR=#777777>Auto-Renew: Disabled</BASEFONT>", false, false);
// Cancel Button
AddHtml(75, 280, 250, 20, "<BASEFONT COLOR=#FF0000>Cancel Rental Contract</BASEFONT>", false, false);
AddButton(40, 280, 4005, 4007, 1, GumpButtonType.Reply, 0);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1 && m_House != null && !m_House.Deleted)
{
m_Player.SendGump(new CancelRentGump(m_House));
}
}
}
// --- CANCELLATION CONFIRMATION GUMP ---
public class CancelRentGump : Gump
{
private InnRoomHouse m_House;
public CancelRentGump(InnRoomHouse house) : base(200, 200)
{
m_House = house;
AddPage(0);
AddBackground(0, 0, 300, 150, 9270);
AddHtml(0, 15, 300, 20, "<CENTER><BASEFONT COLOR=#FFFFFF>Cancel Room Rental?</BASEFONT></CENTER>", false, false);
AddHtml(20, 50, 260, 40, "<BASEFONT COLOR=#FFFFFF>This will instantly pack your items into your bank and evict you.</BASEFONT>", false, false);
AddButton(40, 100, 4005, 4007, 1, GumpButtonType.Reply, 0);
AddHtml(75, 100, 100, 20, "<BASEFONT COLOR=#FFFFFF>Confirm</BASEFONT>", false, false);
AddButton(160, 100, 4005, 4007, 0, GumpButtonType.Reply, 0);
AddHtml(195, 100, 100, 20, "<BASEFONT COLOR=#FFFFFF>Cancel</BASEFONT>", false, false);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 1 && m_House != null && !m_House.Deleted)
{
m_House.Evict();
}
}
}
}

View file

@ -0,0 +1,133 @@
using System;
using System.IO;
using System.Collections.Generic;
using Server;
using Server.Items;
using Server.Custom.InnRooms;
namespace Server.Custom.InnRooms
{
public enum InnRoomSize { Small, Medium, Large }
// This object holds the coordinates for a single room from your config file
public class RoomDefinition
{
public InnRoomSize Size;
public Map RoomMap;
public Point3D Location;
}
public class InnRoomManager
{
// The master list of all rooms loaded from the config file
public static List<RoomDefinition> Rooms = new List<RoomDefinition>();
// This runs automatically when the server starts
public static void Initialize()
{
LoadRooms();
}
public static int GetCost(InnRoomSize size)
{
switch (size)
{
case InnRoomSize.Small: return 5000;
case InnRoomSize.Medium: return 10000;
case InnRoomSize.Large: return 20000;
default: return 5000;
}
}
public static void LoadRooms()
{
Rooms.Clear();
string filePath = Path.Combine(Core.BaseDirectory, "Data", "Config", "innrooms.cfg");
if (!File.Exists(filePath))
{
Console.WriteLine("InnRoomManager: No innrooms.cfg found!");
return;
}
using (StreamReader ip = new StreamReader(filePath))
{
string line;
while ((line = ip.ReadLine()) != null)
{
line = line.Trim();
if (line.Length == 0 || line.StartsWith("#")) continue;
string[] split = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
// Expected Format: Size Map X Y Z
// Example: Small Felucca 6000 6000 0
if (split.Length >= 5)
{
try
{
RoomDefinition def = new RoomDefinition();
def.Size = (InnRoomSize)Enum.Parse(typeof(InnRoomSize), split[0], true);
def.RoomMap = Map.Parse(split[1]);
def.Location = new Point3D(int.Parse(split[2]), int.Parse(split[3]), int.Parse(split[4]));
Rooms.Add(def);
}
catch (Exception ex)
{
Console.WriteLine($"InnRoomManager Error parsing line: {line}\n{ex.Message}");
}
}
}
}
Console.WriteLine($"InnRoomManager: Loaded {Rooms.Count} total rooms from config.");
}
// Checks all active rentals on the server to see which list indexes are currently taken
public static List<int> GetOccupiedIndexes()
{
List<int> occupied = new List<int>();
foreach (Item item in World.Items.Values)
{
if (item is InnRoomHouse)
{
InnRoomHouse house = (InnRoomHouse)item;
occupied.Add(house.RoomIndex);
}
}
return occupied;
}
// Counts how many rooms of a specific size are NOT currently rented
public static int GetAvailableCount(InnRoomSize size)
{
List<int> occupied = GetOccupiedIndexes();
int count = 0;
for (int i = 0; i < Rooms.Count; i++)
{
if (Rooms[i].Size == size && !occupied.Contains(i))
{
count++;
}
}
return count;
}
// Finds the exact list index of the first available room of the requested size
public static int FindFirstOpenSlot(InnRoomSize size)
{
List<int> occupied = GetOccupiedIndexes();
for (int i = 0; i < Rooms.Count; i++)
{
if (Rooms[i].Size == size && !occupied.Contains(i))
{
return i; // Returns the exact index in the Rooms list
}
}
return -1; // -1 means sold out
}
}
}