Reorganizes Project (#41)

This commit is contained in:
Kamron Batman 2019-08-02 18:13:40 -07:00 committed by GitHub
parent 08bf44af9a
commit 3614a66aee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3499 changed files with 79 additions and 55 deletions

View file

@ -0,0 +1,334 @@
using System;
using Server.Engines.ConPVP;
using Server.Factions;
using Server.Gumps;
using Server.Menus.Questions;
using Server.Mobiles;
using Server.Multis;
using Server.Network;
using Server.Regions;
namespace Server.Engines.Help
{
public class ContainedMenu : QuestionMenu
{
private Mobile m_From;
public ContainedMenu(Mobile from) : base(
"You already have an open help request. We will have someone assist you as soon as possible. What would you like to do?",
new[] { "Leave my old help request like it is.", "Remove my help request from the queue." })
{
m_From = from;
}
public override void OnCancel(NetState state)
{
m_From.SendLocalizedMessage(1005306, "", 0x35); // Help request unchanged.
}
public override void OnResponse(NetState state, int index)
{
if (index == 0)
{
m_From.SendLocalizedMessage(1005306, "", 0x35); // Help request unchanged.
}
else if (index == 1)
{
PageEntry entry = PageQueue.GetEntry(m_From);
if (entry != null && entry.Handler == null)
{
m_From.SendLocalizedMessage(1005307, "", 0x35); // Removed help request.
// entry.AddResponse(entry.Sender, "[Canceled]");
PageQueue.Remove(entry);
}
else
{
m_From.SendLocalizedMessage(1005306, "", 0x35); // Help request unchanged.
}
}
}
}
public class HelpGump : Gump
{
public HelpGump(Mobile from) : base(0, 0)
{
from.CloseGump<HelpGump>();
bool isYoung = IsYoung(from);
AddBackground(50, 25, 540, 430, 2600);
AddPage(0);
AddHtmlLocalized(150, 50, 360, 40, 1001002); // <CENTER><U>Ultima Online Help Menu</U></CENTER>
AddButton(425, 415, 2073, 2072, 0); // Close
AddPage(1);
if (isYoung)
{
AddButton(80, 75, 5540, 5541, 9, GumpButtonType.Reply, 2);
AddHtml(110, 75, 450, 58,
@"<BODY><BASEFONT COLOR=BLACK><u>Young Player Haven Transport.</u> Select this option if you want to be transported to Haven.</BODY>",
true, true);
AddButton(80, 140, 5540, 5541, 1, GumpButtonType.Reply, 2);
AddHtml(110, 140, 450, 58,
@"<u>General question about Ultima Online.</u> Select this option if you have a general gameplay question, need help learning to use a skill, or if you would like to search the UO Knowledge Base.",
true, true);
AddButton(80, 205, 5540, 5541, 2);
AddHtml(110, 205, 450, 58,
@"<u>My character is physically stuck in the game.</u> This choice only covers cases where your character is physically stuck in a location they cannot move out of. This option will only work two times in 24 hours.",
true, true);
AddButton(80, 270, 5540, 5541, 0, GumpButtonType.Page, 3);
AddHtml(110, 270, 450, 58,
@"<u>Another player is harassing me.</u> Another player is verbally harassing your character. When you select this option you will be sending a text log to Origin Systems. To see what constitutes harassment please visit http://support.uo.com/gm_9.html.",
true, true);
AddButton(80, 335, 5540, 5541, 0, GumpButtonType.Page, 2);
AddHtml(110, 335, 450, 58,
@"<u>Other.</u> If you are experiencing a problem in the game that does not fall into one of the other categories or is not addressed on the Support web page (located at http://support.uo.com), please use this option.",
true, true);
}
else
{
AddButton(80, 90, 5540, 5541, 1, GumpButtonType.Reply, 2);
AddHtml(110, 90, 450, 74,
@"<u>General question about Ultima Online.</u> Select this option if you have a general gameplay question, need help learning to use a skill, or if you would like to search the UO Knowledge Base.",
true, true);
AddButton(80, 170, 5540, 5541, 2);
AddHtml(110, 170, 450, 74,
@"<u>My character is physically stuck in the game.</u> This choice only covers cases where your character is physically stuck in a location they cannot move out of. This option will only work two times in 24 hours.",
true, true);
AddButton(80, 250, 5540, 5541, 0, GumpButtonType.Page, 3);
AddHtml(110, 250, 450, 74,
@"<u>Another player is harassing me.</u> Another player is verbally harassing your character. When you select this option you will be sending a text log to Origin Systems. To see what constitutes harassment please visit http://support.uo.com/gm_9.html.",
true, true);
AddButton(80, 330, 5540, 5541, 0, GumpButtonType.Page, 2);
AddHtml(110, 330, 450, 74,
@"<u>Other.</u> If you are experiencing a problem in the game that does not fall into one of the other categories or is not addressed on the Support web page (located at http://support.uo.com), please use this option.",
true, true);
}
AddPage(2);
AddButton(80, 90, 5540, 5541, 3);
AddHtml(110, 90, 450, 74,
@"<u>Report a bug or contact Origin.</u> Use this option to launch your web browser and mail in a bug report. Your report will be read by our Quality Assurance Staff. We apologize for not being able to reply to individual reports. ",
true, true);
AddButton(80, 170, 5540, 5541, 4);
AddHtml(110, 170, 450, 74,
@"<u>Suggestion for the Game.</u> If you'd like to make a suggestion for the game, it should be directed to the Development Team Members who participate in the discussion forums on the UO.Com web site. Choosing this option will take you to the Discussion Forums. ",
true, true);
AddButton(80, 250, 5540, 5541, 5);
AddHtml(110, 250, 450, 74,
@"<u>Account Management</u> For questions regarding your account such as forgotten passwords, payment options, account activation, and account transfer, please choose this option.",
true, true);
AddButton(80, 330, 5540, 5541, 6);
AddHtml(110, 330, 450, 74,
@"<u>Other.</u> If you are experiencing a problem in the game that does not fall into one of the other categories or is not addressed on the Support web page (located at http://support.uo.com), and requires in-game assistance, use this option. ",
true, true);
AddPage(3);
AddButton(80, 90, 5540, 5541, 7);
AddHtmlLocalized(110, 90, 450, 145, 1062572, true,
true); /* <U><CENTER>Another player is harassing me (or Exploiting).</CENTER></U><BR>
* VERBAL HARASSMENT<BR>
* Use this option when another player is verbally harassing your character.
* Verbal harassment behaviors include but are not limited to, using bad language, threats etc..
* Before you submit a complaint be sure you understand what constitutes harassment
* <A HREF="http://uo.custhelp.com/cgi-bin/uo.cfg/php/enduser/std_adp.php?p_faqid=40"><EFBFBD> what is verbal harassment? -</A>
* and that you have followed these steps:<BR>
* 1. You have asked the player to stop and they have continued.<BR>
* 2. You have tried to remove yourself from the situation.<BR>
* 3. You have done nothing to instigate or further encourage the harassment.<BR>
* 4. You have added the player to your ignore list.
* <A HREF="http://uo.custhelp.com/cgi-bin/uo.cfg/php/enduser/std_adp.php?p_faqid=138">- How do I ignore a player?</A><BR>
* 5. You have read and understand Origin<EFBFBD>s definition of harassment.<BR>
* 6. Your account information is up to date. (Including a current email address)<BR>
* *If these steps have not been taken, GMs may be unable to take action against the offending player.<BR>
* **A chat log will be review by a GM to assess the validity of this complaint.
* Abuse of this system is a violation of the Rules of Conduct.<BR>
* EXPLOITING<BR>
* Use this option to report someone who may be exploiting or cheating.
* <A HREF="http://uo.custhelp.com/cgi-bin/uo.cfg/php/enduser/std_adp.php?p_faqid=41"><EFBFBD> What constitutes an exploit?</a>
*/
AddButton(80, 240, 5540, 5541, 8);
AddHtmlLocalized(110, 240, 450, 145, 1062573, true,
true); /* <U><CENTER>Another player is harassing me using game mechanics.</CENTER></U><BR>
* <BR>
* PHYSICAL HARASSMENT<BR>
* Use this option when another player is harassing your character using game mechanics.
* Physical harassment includes but is not limited to luring, Kill Stealing, and any act that causes a players death in Trammel.
* Before you submit a complaint be sure you understand what constitutes harassment
* <A HREF="http://uo.custhelp.com/cgi-bin/uo.cfg/php/enduser/std_adp.php?p_faqid=59"> <EFBFBD> what is physical harassment?</A>
* and that you have followed these steps:<BR>
* 1. You have asked the player to stop and they have continued.<BR>
* 2. You have tried to remove yourself from the situation.<BR>
* 3. You have done nothing to instigate or further encourage the harassment.<BR>
* 4. You have added the player to your ignore list.
* <A HREF="http://uo.custhelp.com/cgi-bin/uo.cfg/php/enduser/std_adp.php?p_faqid=138"> - how do I ignore a player?</A><BR>
* 5. You have read and understand Origin<EFBFBD>s definition of harassment.<BR>
* 6. Your account information is up to date. (Including a current email address)<BR>
* *If these steps have not been taken, GMs may be unable to take action against the offending player.<BR>
* **This issue will be reviewed by a GM to assess the validity of this complaint.
* Abuse of this system is a violation of the Rules of Conduct.
*/
AddButton(150, 390, 5540, 5541, 0, GumpButtonType.Page, 1);
AddHtmlLocalized(180, 390, 335, 40, 1001015); // NO - I meant to ask for help with another matter.
}
public static void Initialize()
{
EventSink.HelpRequest += EventSink_HelpRequest;
}
private static void EventSink_HelpRequest(HelpRequestEventArgs e)
{
foreach (Gump g in e.Mobile.NetState.Gumps)
if (g is HelpGump)
return;
if (!PageQueue.CheckAllowedToPage(e.Mobile))
return;
if (PageQueue.Contains(e.Mobile))
e.Mobile.SendMenu(new ContainedMenu(e.Mobile));
else
e.Mobile.SendGump(new HelpGump(e.Mobile));
}
private static bool IsYoung(Mobile m)
{
return m is PlayerMobile mobile && mobile.Young;
}
public static bool CheckCombat(Mobile m)
{
for (int i = 0; i < m.Aggressed.Count; ++i)
{
AggressorInfo info = m.Aggressed[i];
if (DateTime.UtcNow - info.LastCombatTime < TimeSpan.FromSeconds(30.0))
return true;
}
return false;
}
public override void OnResponse(NetState state, RelayInfo info)
{
Mobile from = state.Mobile;
PageType type = (PageType)(-1);
switch (info.ButtonID)
{
case 0: // Close/Cancel
{
from.SendLocalizedMessage(501235, "", 0x35); // Help request aborted.
break;
}
case 1: // General question
{
type = PageType.Question;
break;
}
case 2: // Stuck
{
BaseHouse house = BaseHouse.FindHouseAt(from);
if (house?.IsAosRules == true && !from.Region.IsPartOf<SafeZone>()) // Dueling
{
from.Location = house.BanLocation;
}
else if (from.Region.IsPartOf<Jail>())
{
from.SendLocalizedMessage(1114345, "", 0x35); // You'll need a better jailbreak plan than that!
}
else if (Sigil.ExistsOn(from))
{
from.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (from is PlayerMobile mobile && mobile.CanUseStuckMenu() &&
mobile.Region.CanUseStuckMenu(mobile) && !CheckCombat(mobile) && !mobile.Frozen &&
!mobile.Criminal && (Core.AOS || mobile.Kills < 5))
{
StuckMenu menu = new StuckMenu(mobile, mobile, true);
menu.BeginClose();
mobile.SendGump(menu);
}
else
{
type = PageType.Stuck;
}
break;
}
case 3: // Report bug or contact Origin
{
type = PageType.Bug;
break;
}
case 4: // Game suggestion
{
type = PageType.Suggestion;
break;
}
case 5: // Account management
{
type = PageType.Account;
break;
}
case 6: // Other
{
type = PageType.Other;
break;
}
case 7: // Harassment: verbal/exploit
{
type = PageType.VerbalHarassment;
break;
}
case 8: // Harassment: physical
{
type = PageType.PhysicalHarassment;
break;
}
case 9: // Young player transport
{
if (IsYoung(from))
{
if (from.Region.IsPartOf<Jail>())
from.SendLocalizedMessage(1114345, "", 0x35); // You'll need a better jailbreak plan than that!
else if (from.Region.IsPartOf("Haven Island"))
from.SendLocalizedMessage(1041529); // You're already in Haven
else
from.MoveToWorld(new Point3D(3503, 2574, 14), Map.Trammel);
}
break;
}
}
if (type != (PageType)(-1) && PageQueue.CheckAllowedToPage(from))
from.SendGump(new PagePromptGump(from, type));
}
}
}

View file

@ -0,0 +1,29 @@
using Server.Prompts;
namespace Server.Engines.Help
{
public class PagePrompt : Prompt
{
private PageType m_Type;
public PagePrompt(PageType type)
{
m_Type = type;
}
public override void OnCancel(Mobile from)
{
from.SendLocalizedMessage(501235, "", 0x35); // Help request aborted.
}
public override void OnResponse(Mobile from, string text)
{
from.SendLocalizedMessage(501234, "",
0x35); /* The next available Counselor/Game Master will respond as soon as possible.
* Please check your Journal for messages every few minutes.
*/
PageQueue.Enqueue(new PageEntry(from, text, m_Type));
}
}
}

View file

@ -0,0 +1,60 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.Help
{
public class PagePromptGump : Gump
{
private Mobile m_From;
private PageType m_Type;
public PagePromptGump(Mobile from, PageType type) : base(0, 0)
{
m_From = from;
m_Type = type;
from.CloseGump<PagePromptGump>();
AddBackground(50, 50, 540, 350, 2600);
AddPage(0);
AddHtmlLocalized(264, 80, 200, 24, 1062524); // Enter Description
AddHtmlLocalized(120, 108, 420, 48, 1062638); // Please enter a brief description (up to 200 characters) of your problem:
AddBackground(100, 148, 440, 200, 3500);
AddTextEntry(120, 168, 400, 200, 1153, 0, "");
AddButton(175, 355, 2074, 2075, 1); // Okay
AddButton(405, 355, 2073, 2072, 0); // Cancel
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID == 0)
{
m_From.SendLocalizedMessage(501235, "", 0x35); // Help request aborted.
}
else
{
TextRelay entry = info.GetTextEntry(0);
string text = entry == null ? "" : entry.Text.Trim();
if (text.Length == 0)
{
m_From.SendMessage(0x35, "You must enter a description.");
m_From.SendGump(new PagePromptGump(m_From, m_Type));
}
else
{
m_From.SendLocalizedMessage(501234, "",
0x35); /* The next available Counselor/Game Master will respond as soon as possible.
* Please check your Journal for messages every few minutes.
*/
PageQueue.Enqueue(new PageEntry(m_From, text, m_Type));
}
}
}
}
}

View file

@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Mail;
using Server.Accounting;
using Server.Commands;
using Server.Misc;
using Server.Mobiles;
using Server.Network;
namespace Server.Engines.Help
{
public enum PageType
{
Bug,
Stuck,
Account,
Question,
Suggestion,
Other,
VerbalHarassment,
PhysicalHarassment
}
public class PageEntry
{
// What page types should have a speech log as attachment?
public static readonly PageType[] SpeechLogAttachment =
{
PageType.VerbalHarassment
};
private Mobile m_Handler;
private Timer m_Timer;
public PageEntry(Mobile sender, string message, PageType type)
{
Sender = sender;
Sent = DateTime.UtcNow;
Message = Utility.FixHtml(message);
Type = type;
PageLocation = sender.Location;
PageMap = sender.Map;
if (sender is PlayerMobile pm && pm.SpeechLog != null && Array.IndexOf(SpeechLogAttachment, type) >= 0)
SpeechLog = new List<SpeechLogEntry>(pm.SpeechLog);
m_Timer = new InternalTimer(this);
m_Timer.Start();
}
public Mobile Sender{ get; }
public Mobile Handler
{
get => m_Handler;
set
{
PageQueue.OnHandlerChanged(m_Handler, value, this);
m_Handler = value;
}
}
public DateTime Sent{ get; }
public string Message{ get; }
public PageType Type{ get; }
public Point3D PageLocation{ get; }
public Map PageMap{ get; }
public List<SpeechLogEntry> SpeechLog{ get; }
public void Stop()
{
m_Timer?.Stop();
m_Timer = null;
}
private class InternalTimer : Timer
{
private static TimeSpan StatusDelay = TimeSpan.FromMinutes(2.0);
private PageEntry m_Entry;
public InternalTimer(PageEntry entry) : base(TimeSpan.FromSeconds(1.0), StatusDelay)
{
m_Entry = entry;
}
protected override void OnTick()
{
int index = PageQueue.IndexOf(m_Entry);
if (m_Entry.Sender.NetState != null && index != -1)
{
m_Entry.Sender.SendLocalizedMessage(1008077, true,
(index + 1).ToString()); // Thank you for paging. Queue status :
m_Entry.Sender
.SendLocalizedMessage(
1008084); // You can reference our website at www.uo.com or contact us at support@uo.com. To cancel your page, please select the help button again and select cancel.
if (m_Entry.Handler != null && m_Entry.Handler.NetState == null) m_Entry.Handler = null;
}
else
{
if (index != -1)
// m_Entry.AddResponse(m_Entry.Sender, "[Logout]");
PageQueue.Remove(m_Entry);
}
}
}
}
public class PageQueue
{
private static Dictionary<Mobile, PageEntry> m_KeyedByHandler = new Dictionary<Mobile, PageEntry>();
private static Dictionary<Mobile, PageEntry> m_KeyedBySender = new Dictionary<Mobile, PageEntry>();
public static List<PageEntry> List{ get; } = new List<PageEntry>();
public static void Initialize()
{
CommandSystem.Register("Pages", AccessLevel.Counselor, Pages_OnCommand);
}
public static bool CheckAllowedToPage(Mobile from)
{
if (!(from is PlayerMobile pm))
return true;
if (pm.DesignContext != null)
{
from.SendLocalizedMessage(
500182); // You cannot request help while customizing a house or transferring a character.
return false;
}
if (pm.PagingSquelched)
{
from.SendMessage("You cannot request help, sorry.");
return false;
}
return true;
}
public static string GetPageTypeName(PageType type)
{
if (type == PageType.VerbalHarassment)
return "Verbal Harassment";
if (type == PageType.PhysicalHarassment)
return "Physical Harassment";
return type.ToString();
}
public static void OnHandlerChanged(Mobile old, Mobile value, PageEntry entry)
{
if (old != null)
m_KeyedByHandler.Remove(old);
if (value != null)
m_KeyedByHandler[value] = entry;
}
[Usage("Pages")]
[Description("Opens the page queue menu.")]
private static void Pages_OnCommand(CommandEventArgs e)
{
if (m_KeyedByHandler.TryGetValue(e.Mobile, out PageEntry entry))
e.Mobile.SendGump(new PageEntryGump(e.Mobile, entry));
else if (List.Count > 0)
e.Mobile.SendGump(new PageQueueGump());
else
e.Mobile.SendMessage("The page queue is empty.");
}
public static bool IsHandling(Mobile check)
{
return m_KeyedByHandler.ContainsKey(check);
}
public static bool Contains(Mobile sender)
{
return m_KeyedBySender.ContainsKey(sender);
}
public static int IndexOf(PageEntry e)
{
return List.IndexOf(e);
}
public static void Remove(PageEntry e)
{
if (e == null)
return;
e.Stop();
List.Remove(e);
m_KeyedBySender.Remove(e.Sender);
if (e.Handler != null)
m_KeyedByHandler.Remove(e.Handler);
}
public static PageEntry GetEntry(Mobile sender)
{
m_KeyedBySender.TryGetValue(sender, out PageEntry entry);
return entry;
}
public static void Remove(Mobile sender)
{
Remove(GetEntry(sender));
}
public static void Enqueue(PageEntry entry)
{
List.Add(entry);
m_KeyedBySender[entry.Sender] = entry;
bool isStaffOnline = false;
foreach (NetState ns in NetState.Instances)
{
Mobile m = ns.Mobile;
if (m?.AccessLevel >= AccessLevel.Counselor && m.AutoPageNotify && !IsHandling(m))
m.SendMessage("A new page has been placed in the queue.");
if (m?.AccessLevel >= AccessLevel.Counselor && m.AutoPageNotify &&
Core.TickCount - m.LastMoveTime < 600000)
isStaffOnline = true;
}
if (!isStaffOnline)
entry.Sender.SendMessage(
"We are sorry, but no staff members are currently available to assist you. Your page will remain in the queue until one becomes available, or until you cancel it manually.");
if (Email.FromAddress != null && Email.SpeechLogPageAddresses != null && entry.SpeechLog != null)
SendEmail(entry);
}
private static void SendEmail(PageEntry entry)
{
Mobile sender = entry.Sender;
DateTime time = DateTime.UtcNow;
MailMessage mail = new MailMessage(Email.FromAddress, Email.SpeechLogPageAddresses)
{
Subject = "RunUO Speech Log Page Forwarding"
};
using (StringWriter writer = new StringWriter())
{
writer.WriteLine("RunUO Speech Log Page - {0}", GetPageTypeName(entry.Type));
writer.WriteLine();
writer.WriteLine("From: '{0}', Account: '{1}'", sender.RawName,
sender.Account is Account ? sender.Account.Username : "???");
writer.WriteLine("Location: {0} [{1}]", sender.Location, sender.Map);
writer.WriteLine("Sent on: {0}/{1:00}/{2:00} {3}:{4:00}:{5:00}", time.Year, time.Month, time.Day, time.Hour,
time.Minute, time.Second);
writer.WriteLine();
writer.WriteLine("Message:");
writer.WriteLine("'{0}'", entry.Message);
writer.WriteLine();
writer.WriteLine("Speech Log");
writer.WriteLine("==========");
foreach (SpeechLogEntry logEntry in entry.SpeechLog)
{
Mobile from = logEntry.From;
string fromName = from.RawName;
string fromAccount = from.Account is Account ? from.Account.Username : "???";
DateTime created = logEntry.Created;
string speech = logEntry.Speech;
writer.WriteLine("{0}:{1:00}:{2:00} - {3} ({4}): '{5}'", created.Hour, created.Minute, created.Second,
fromName, fromAccount, speech);
}
mail.Body = writer.ToString();
}
Email.AsyncSend(mail);
}
}
}

View file

@ -0,0 +1,801 @@
using System;
using System.Collections.Generic;
using System.IO;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.Help
{
public class MessageSentGump : Gump
{
private Mobile m_Mobile;
private string m_Name, m_Text;
public MessageSentGump(Mobile mobile, string name, string text) : base(30, 30)
{
m_Name = name;
m_Text = text;
m_Mobile = mobile;
Closable = false;
AddPage(0);
AddBackground(0, 0, 92, 75, 0xA3C);
AddImageTiled(5, 7, 82, 61, 0xA40);
AddAlphaRegion(5, 7, 82, 61);
AddImageTiled(9, 11, 21, 53, 0xBBC);
AddButton(10, 12, 0x7D2, 0x7D2, 0);
AddHtmlLocalized(34, 28, 65, 24, 3001002, 0xFFFFFF); // Message
}
public override void OnResponse(NetState state, RelayInfo info)
{
m_Mobile.SendGump(new PageResponseGump(m_Mobile, m_Name, m_Text));
//m_Mobile.SendMessage( 0x482, "{0} tells you:", m_Name );
//m_Mobile.SendMessage( 0x482, m_Text );
}
}
public class PageQueueGump : Gump
{
private PageEntry[] m_List;
public PageQueueGump() : base(30, 30)
{
Add(new GumpPage(0));
//Add( new GumpBackground( 0, 0, 410, 448, 9200 ) );
Add(new GumpImageTiled(0, 0, 410, 448, 0xA40));
Add(new GumpAlphaRegion(1, 1, 408, 446));
Add(new GumpLabel(180, 12, 2100, "Page Queue"));
List<PageEntry> list = PageQueue.List;
for (int i = 0;i < list.Count;)
{
PageEntry e = list[i];
if (e.Sender.Deleted || e.Sender.NetState == null)
{
// e.AddResponse(e.Sender, "[Logout]");
PageQueue.Remove(e);
}
else
++i;
}
m_List = list.ToArray();
if (m_List.Length <= 0)
{
Add(new GumpLabel(12, 44, 2100, "The page queue is empty."));
return;
}
Add(new GumpPage(1));
for (int i = 0; i < m_List.Length; ++i)
{
PageEntry e = m_List[i];
if (i >= 5 && i % 5 == 0)
{
Add(new GumpButton(368, 12, 0xFA5, 0xFA7, 0, GumpButtonType.Page, i / 5 + 1));
Add(new GumpLabel(298, 12, 2100, "Next Page"));
Add(new GumpPage(i / 5 + 1));
Add(new GumpButton(12, 12, 0xFAE, 0xFB0, 0, GumpButtonType.Page, i / 5));
Add(new GumpLabel(48, 12, 2100, "Previous Page"));
}
string typeString = PageQueue.GetPageTypeName(e.Type);
string html =
$"[{typeString}] {e.Message} <basefont color=#{(e.Handler == null ? 0xFF0000 : 0xFF):X6}>[<u>{(e.Handler == null ? "Unhandled" : "Handling")}</u>]</basefont>";
Add(new GumpHtml(12, 44 + i % 5 * 80, 350, 70, html, true, true));
Add(new GumpButton(370, 44 + i % 5 * 80 + 24, 0xFA5, 0xFA7, i + 1));
}
}
public override void OnResponse(NetState state, RelayInfo info)
{
if (info.ButtonID >= 1 && info.ButtonID <= m_List.Length)
{
if (PageQueue.List.IndexOf(m_List[info.ButtonID - 1]) >= 0)
{
PageEntryGump g = new PageEntryGump(state.Mobile, m_List[info.ButtonID - 1]);
g.SendTo(state);
}
else
{
state.Mobile.SendGump(new PageQueueGump());
state.Mobile.SendMessage("That page has been removed.");
}
}
}
}
public class PredefinedResponse
{
public PredefinedResponse(string title, string message)
{
Title = title;
Message = message;
}
public string Title{ get; set; }
public string Message{ get; set; }
public static List<PredefinedResponse> List{ get; private set; } = Load();
public static PredefinedResponse Add(string title, string message)
{
PredefinedResponse resp = new PredefinedResponse(title, message);
List.Add(resp);
Save();
return resp;
}
public static void Save()
{
if (List == null)
List = Load();
try
{
string path = Path.Combine(Core.BaseDirectory, "Data/pageresponse.cfg");
using (StreamWriter op = new StreamWriter(path))
{
for (int i = 0; i < List.Count; ++i)
{
PredefinedResponse resp = List[i];
op.WriteLine("{0}\t{1}", resp.Title, resp.Message);
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
public static List<PredefinedResponse> Load()
{
string path = Path.Combine(Core.BaseDirectory, "Data/pageresponse.cfg");
if (!File.Exists(path))
return new List<PredefinedResponse>();
List<PredefinedResponse> list = new List<PredefinedResponse>();
try
{
using (StreamReader ip = new StreamReader(path))
{
string line;
while ((line = ip.ReadLine()?.Trim()) != null)
{
if (line.Length == 0 || line.StartsWith("#"))
continue;
string[] split = line.Split('\t');
if (split.Length == 2)
list.Add(new PredefinedResponse(split[0], split[1]));
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
return list;
}
}
public class PredefGump : Gump
{
private const int LabelColor32 = 0xFFFFFF;
private Mobile m_From;
private PredefinedResponse m_Response;
public PredefGump(Mobile from, PredefinedResponse response) : base(30, 30)
{
m_From = from;
m_Response = response;
from.CloseGump<PredefGump>();
bool canEdit = from.AccessLevel >= AccessLevel.GameMaster;
AddPage(0);
if (response == null)
{
AddImageTiled(0, 0, 410, 448, 0xA40);
AddAlphaRegion(1, 1, 408, 446);
AddHtml(10, 10, 390, 20, Color(Center("Predefined Responses"), LabelColor32));
List<PredefinedResponse> list = PredefinedResponse.List;
AddPage(1);
int i;
for (i = 0; i < list.Count; ++i)
{
if (i >= 5 && i % 5 == 0)
{
AddButton(368, 10, 0xFA5, 0xFA7, 0, GumpButtonType.Page, i / 5 + 1);
AddLabel(298, 10, 2100, "Next Page");
AddPage(i / 5 + 1);
AddButton(12, 10, 0xFAE, 0xFB0, 0, GumpButtonType.Page, i / 5);
AddLabel(48, 10, 2100, "Previous Page");
}
PredefinedResponse resp = list[i];
string html = $"<u>{resp.Title}</u><br>{resp.Message}";
AddHtml(12, 44 + i % 5 * 80, 350, 70, html, true, true);
if (canEdit)
{
AddButton(370, 44 + i % 5 * 80 + 24, 0xFA5, 0xFA7, 2 + i * 3);
if (i > 0)
AddButton(377, 44 + i % 5 * 80 + 2, 0x15E0, 0x15E4, 3 + i * 3);
else
AddImage(377, 44 + i % 5 * 80 + 2, 0x25E4);
if (i < list.Count - 1)
AddButton(377, 44 + i % 5 * 80 + 70 - 2 - 16, 0x15E2, 0x15E6, 4 + i * 3);
else
AddImage(377, 44 + i % 5 * 80 + 70 - 2 - 16, 0x25E8);
}
}
if (canEdit)
{
if (i >= 5 && i % 5 == 0)
{
AddButton(368, 10, 0xFA5, 0xFA7, 0, GumpButtonType.Page, i / 5 + 1);
AddLabel(298, 10, 2100, "Next Page");
AddPage(i / 5 + 1);
AddButton(12, 10, 0xFAE, 0xFB0, 0, GumpButtonType.Page, i / 5);
AddLabel(48, 10, 2100, "Previous Page");
}
AddButton(12, 44 + i % 5 * 80, 0xFAB, 0xFAD, 1);
AddHtml(45, 44 + i % 5 * 80, 200, 20, Color("New Response", LabelColor32));
}
}
else if (canEdit)
{
AddImageTiled(0, 0, 410, 250, 0xA40);
AddAlphaRegion(1, 1, 408, 248);
AddHtml(10, 10, 390, 20, Color(Center("Predefined Response Editor"), LabelColor32));
AddButton(10, 40, 0xFB1, 0xFB3, 1);
AddHtml(45, 40, 200, 20, Color("Remove", LabelColor32));
AddButton(10, 70, 0xFA5, 0xFA7, 2);
AddHtml(45, 70, 200, 20, Color("Title:", LabelColor32));
AddTextInput(10, 90, 300, 20, 0, response.Title);
AddButton(10, 120, 0xFA5, 0xFA7, 3);
AddHtml(45, 120, 200, 20, Color("Message:", LabelColor32));
AddTextInput(10, 140, 390, 100, 1, response.Message);
}
}
public string Center(string text)
{
return $"<CENTER>{text}</CENTER>";
}
public string Color(string text, int color)
{
return $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
}
public void AddTextInput(int x, int y, int w, int h, int id, string def)
{
AddImageTiled(x, y, w, h, 0xA40);
AddImageTiled(x + 1, y + 1, w - 2, h - 2, 0xBBC);
AddTextEntry(x + 3, y + 1, w - 4, h - 2, 0x480, id, def);
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (m_From.AccessLevel < AccessLevel.Administrator)
return;
if (m_Response == null)
{
int index = info.ButtonID - 1;
if (index == 0)
{
PredefinedResponse resp = new PredefinedResponse("", "");
List<PredefinedResponse> list = PredefinedResponse.List;
list.Add(resp);
m_From.SendGump(new PredefGump(m_From, resp));
}
else
{
--index;
int type = index % 3;
index /= 3;
List<PredefinedResponse> list = PredefinedResponse.List;
if (index >= 0 && index < list.Count)
{
PredefinedResponse resp = list[index];
switch (type)
{
case 0: // edit
{
m_From.SendGump(new PredefGump(m_From, resp));
break;
}
case 1: // move up
{
if (index > 0)
{
list.RemoveAt(index);
list.Insert(index - 1, resp);
PredefinedResponse.Save();
m_From.SendGump(new PredefGump(m_From, null));
}
break;
}
case 2: // move down
{
if (index < list.Count - 1)
{
list.RemoveAt(index);
list.Insert(index + 1, resp);
PredefinedResponse.Save();
m_From.SendGump(new PredefGump(m_From, null));
}
break;
}
}
}
}
}
else
{
List<PredefinedResponse> list = PredefinedResponse.List;
switch (info.ButtonID)
{
case 1:
{
list.Remove(m_Response);
PredefinedResponse.Save();
m_From.SendGump(new PredefGump(m_From, null));
break;
}
case 2:
{
TextRelay te = info.GetTextEntry(0);
if (te != null)
m_Response.Title = te.Text;
PredefinedResponse.Save();
m_From.SendGump(new PredefGump(m_From, m_Response));
break;
}
case 3:
{
TextRelay te = info.GetTextEntry(1);
if (te != null)
m_Response.Message = te.Text;
PredefinedResponse.Save();
m_From.SendGump(new PredefGump(m_From, m_Response));
break;
}
}
}
}
}
public class PageEntryGump : Gump
{
private static int[] m_AccessLevelHues =
{
2100,
2122,
2117,
2129,
2415,
2415,
2415
};
private PageEntry m_Entry;
private Mobile m_Mobile;
public PageEntryGump(Mobile m, PageEntry entry) : base(30, 30)
{
m_Mobile = m;
m_Entry = entry;
int buttons = 0;
int bottom = 356;
AddPage(0);
AddImageTiled(0, 0, 410, 456, 0xA40);
AddAlphaRegion(1, 1, 408, 454);
AddPage(1);
AddLabel(18, 18, 2100, "Sent:");
AddLabelCropped(128, 18, 264, 20, 2100, entry.Sent.ToString());
AddLabel(18, 38, 2100, "Sender:");
AddLabelCropped(128, 38, 264, 20, 2100,
$"{entry.Sender.RawName} {entry.Sender.Location} [{entry.Sender.Map}]");
AddButton(18, bottom - buttons * 22, 0xFAB, 0xFAD, 8);
AddImageTiled(52, bottom - buttons * 22 + 1, 340, 80, 0xA40 /*0xBBC*/ /*0x2458*/);
AddImageTiled(53, bottom - buttons * 22 + 2, 338, 78, 0xBBC /*0x2426*/);
AddTextEntry(55, bottom - buttons++ * 22 + 2, 336, 78, 0x480, 0, "");
AddButton(18, bottom - buttons * 22, 0xFA5, 0xFA7, 0, GumpButtonType.Page, 2);
AddLabel(52, bottom - buttons++ * 22, 2100, "Predefined Response");
if (entry.Sender != m)
{
AddButton(18, bottom - buttons * 22, 0xFA5, 0xFA7, 1);
AddLabel(52, bottom - buttons++ * 22, 2100, "Go to Sender");
}
AddLabel(18, 58, 2100, "Handler:");
if (entry.Handler == null)
{
AddLabelCropped(128, 58, 264, 20, 2100, "Unhandled");
AddButton(18, bottom - buttons * 22, 0xFB1, 0xFB3, 5);
AddLabel(52, bottom - buttons++ * 22, 2100, "Delete Page");
AddButton(18, bottom - buttons * 22, 0xFB7, 0xFB9, 4);
AddLabel(52, bottom - buttons++ * 22, 2100, "Handle Page");
}
else
{
AddLabelCropped(128, 58, 264, 20, m_AccessLevelHues[(int)entry.Handler.AccessLevel], entry.Handler.Name);
if (entry.Handler != m)
{
AddButton(18, bottom - buttons * 22, 0xFA5, 0xFA7, 2);
AddLabel(52, bottom - buttons++ * 22, 2100, "Go to Handler");
}
else
{
AddButton(18, bottom - buttons * 22, 0xFA2, 0xFA4, 6);
AddLabel(52, bottom - buttons++ * 22, 2100, "Abandon Page");
AddButton(18, bottom - buttons * 22, 0xFB7, 0xFB9, 7);
AddLabel(52, bottom - buttons++ * 22, 2100, "Page Handled");
}
}
AddLabel(18, 78, 2100, "Page Location:");
AddLabelCropped(128, 78, 264, 20, 2100, $"{entry.PageLocation} [{entry.PageMap}]");
AddButton(18, bottom - buttons * 22, 0xFA5, 0xFA7, 3);
AddLabel(52, bottom - buttons++ * 22, 2100, "Go to Page Location");
if (entry.SpeechLog != null)
{
AddButton(18, bottom - buttons * 22, 0xFA5, 0xFA7, 10);
AddLabel(52, bottom - buttons * 22, 2100, "View Speech Log");
}
AddLabel(18, 98, 2100, "Page Type:");
AddLabelCropped(128, 98, 264, 20, 2100, PageQueue.GetPageTypeName(entry.Type));
AddLabel(18, 118, 2100, "Message:");
AddHtml(128, 118, 250, 100, entry.Message, true, true);
AddPage(2);
List<PredefinedResponse> preresp = PredefinedResponse.List;
AddButton(18, 18, 0xFAE, 0xFB0, 0, GumpButtonType.Page, 1);
AddButton(410 - 18 - 32, 18, 0xFAB, 0xFAC, 9);
if (preresp.Count == 0)
{
AddLabel(52, 18, 2100, "There are no predefined responses.");
}
else
{
AddLabel(52, 18, 2100, "Back");
for (int i = 0; i < preresp.Count; ++i)
{
AddButton(18, 40 + i * 22, 0xFA5, 0xFA7, 100 + i);
AddLabel(52, 40 + i * 22, 2100, preresp[i].Title);
}
}
}
public void Resend(NetState state)
{
PageEntryGump g = new PageEntryGump(m_Mobile, m_Entry);
g.SendTo(state);
}
public override void OnResponse(NetState state, RelayInfo info)
{
if (info.ButtonID != 0 && PageQueue.List.IndexOf(m_Entry) < 0)
{
state.Mobile.SendGump(new PageQueueGump());
state.Mobile.SendMessage("That page has been removed.");
return;
}
switch (info.ButtonID)
{
case 0: // close
{
if (m_Entry.Handler != state.Mobile)
{
PageQueueGump g = new PageQueueGump();
g.SendTo(state);
}
break;
}
case 1: // go to sender
{
Mobile m = state.Mobile;
if (m_Entry.Sender.Deleted)
{
m.SendMessage("That character no longer exists.");
}
else if (m_Entry.Sender.Map == null || m_Entry.Sender.Map == Map.Internal)
{
m.SendMessage("That character is not in the world.");
}
else
{
// m_Entry.AddResponse(state.Mobile, "[Go Sender]");
m.MoveToWorld(m_Entry.Sender.Location, m_Entry.Sender.Map);
m.SendMessage("You have been teleported to that page's sender.");
Resend(state);
}
break;
}
case 2: // go to handler
{
Mobile m = state.Mobile;
Mobile h = m_Entry.Handler;
if (h != null)
{
if (h.Deleted)
{
m.SendMessage("That character no longer exists.");
}
else if (h.Map == null || h.Map == Map.Internal)
{
m.SendMessage("That character is not in the world.");
}
else
{
// m_Entry.AddResponse(state.Mobile, "[Go Handler]");
m.MoveToWorld(h.Location, h.Map);
m.SendMessage("You have been teleported to that page's handler.");
Resend(state);
}
}
else
{
m.SendMessage("Nobody is handling that page.");
Resend(state);
}
break;
}
case 3: // go to page location
{
Mobile m = state.Mobile;
if (m_Entry.PageMap == null || m_Entry.PageMap == Map.Internal)
{
m.SendMessage("That location is not in the world.");
}
else
{
// m_Entry.AddResponse(state.Mobile, "[Go PageLoc]");
m.MoveToWorld(m_Entry.PageLocation, m_Entry.PageMap);
state.Mobile.SendMessage("You have been teleported to the original page location.");
Resend(state);
}
break;
}
case 4: // handle page
{
if (m_Entry.Handler == null)
{
// m_Entry.AddResponse(state.Mobile, "[Handling]");
m_Entry.Handler = state.Mobile;
state.Mobile.SendMessage("You are now handling the page.");
}
else
{
state.Mobile.SendMessage("Someone is already handling that page.");
}
Resend(state);
break;
}
case 5: // delete page
{
if (m_Entry.Handler == null)
{
// m_Entry.AddResponse(state.Mobile, "[Deleting]");
PageQueue.Remove(m_Entry);
state.Mobile.SendMessage("You delete the page.");
PageQueueGump g = new PageQueueGump();
g.SendTo(state);
}
else
{
state.Mobile.SendMessage("Someone is handling that page, it can not be deleted.");
Resend(state);
}
break;
}
case 6: // abandon page
{
if (m_Entry.Handler == state.Mobile)
{
// m_Entry.AddResponse(state.Mobile, "[Abandoning]");
state.Mobile.SendMessage("You abandon the page.");
m_Entry.Handler = null;
}
else
{
state.Mobile.SendMessage("You are not handling that page.");
}
Resend(state);
break;
}
case 7: // page handled
{
if (m_Entry.Handler == state.Mobile)
{
// m_Entry.AddResponse(state.Mobile, "[Handled]");
PageQueue.Remove(m_Entry);
m_Entry.Handler = null;
state.Mobile.SendMessage("You mark the page as handled, and remove it from the queue.");
PageQueueGump g = new PageQueueGump();
g.SendTo(state);
}
else
{
state.Mobile.SendMessage("You are not handling that page.");
Resend(state);
}
break;
}
case 8: // Send message
{
TextRelay text = info.GetTextEntry(0);
if (text != null)
{
// m_Entry.AddResponse(state.Mobile, "[Response] " + text.Text);
m_Entry.Sender.SendGump(new MessageSentGump(m_Entry.Sender, state.Mobile.Name, text.Text));
//m_Entry.Sender.SendMessage( 0x482, "{0} tells you:", state.Mobile.Name );
//m_Entry.Sender.SendMessage( 0x482, text.Text );
}
Resend(state);
break;
}
case 9: // predef overview
{
Resend(state);
state.Mobile.SendGump(new PredefGump(state.Mobile, null));
break;
}
case 10: // View Speech Log
{
Resend(state);
if (m_Entry.SpeechLog != null)
{
state.Mobile.SendGump(new SpeechLogGump(m_Entry.Sender, m_Entry.SpeechLog));
}
break;
}
default:
{
int index = info.ButtonID - 100;
List<PredefinedResponse> preresp = PredefinedResponse.List;
if (index >= 0 && index < preresp.Count)
{
// m_Entry.AddResponse(state.Mobile, "[PreDef] " + preresp[index].Title);
m_Entry.Sender.SendGump(new MessageSentGump(m_Entry.Sender, state.Mobile.Name,
preresp[index].Message));
}
Resend(state);
break;
}
}
}
}
}

View file

@ -0,0 +1,37 @@
using Server.Gumps;
using Server.Network;
namespace Server.Engines.Help
{
public class PageResponseGump : Gump
{
private Mobile m_From;
private string m_Name, m_Text;
public PageResponseGump(Mobile from, string name, string text) : base(0, 0)
{
m_From = from;
m_Name = name;
m_Text = text;
AddBackground(50, 25, 540, 430, 2600);
AddPage(0);
AddHtmlLocalized(150, 40, 360, 40, 1062610); // <CENTER><U>Ultima Online Help Response</U></CENTER>
AddHtml(80, 90, 480, 290, $"{name} tells {from.Name}: {text}", true, true);
AddHtmlLocalized(80, 390, 480, 40, 1062611); // Clicking the OKAY button will remove the reponse you have received.
AddButton(400, 417, 2074, 2075, 1); // OKAY
AddButton(475, 417, 2073, 2072, 0); // CANCEL
}
public override void OnResponse(NetState sender, RelayInfo info)
{
if (info.ButtonID != 1)
m_From.SendGump(new MessageSentGump(m_From, m_Name, m_Text));
}
}
}

View file

@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Server.Commands;
using Server.Gumps;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Engines.Help
{
public class SpeechLog : IEnumerable<SpeechLogEntry>
{
// Are speech logs enabled?
public static readonly bool Enabled = true;
// How long should we maintain each speech entry?
public static readonly TimeSpan EntryDuration = TimeSpan.FromMinutes(20.0);
// What is the maximum number of entries a log can contain? (0 -> no limit)
public static readonly int MaxLength = 0;
private Queue<SpeechLogEntry> m_Queue;
public SpeechLog()
{
m_Queue = new Queue<SpeechLogEntry>();
}
public int Count => m_Queue.Count;
#region IEnumerable<SpeechLogEntry> Members
IEnumerator<SpeechLogEntry> IEnumerable<SpeechLogEntry>.GetEnumerator()
{
return m_Queue.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return m_Queue.GetEnumerator();
}
#endregion
public static void Initialize()
{
CommandSystem.Register("SpeechLog", AccessLevel.Counselor, SpeechLog_OnCommand);
}
[Usage("SpeechLog")]
[Description("Opens the speech log of a given target.")]
private static void SpeechLog_OnCommand(CommandEventArgs e)
{
Mobile from = e.Mobile;
from.SendMessage("Target a player to view his speech log.");
e.Mobile.Target = new SpeechLogTarget();
}
public void Add(Mobile from, string speech)
{
Add(new SpeechLogEntry(from, speech));
}
public void Add(SpeechLogEntry entry)
{
if (MaxLength > 0 && m_Queue.Count >= MaxLength)
m_Queue.Dequeue();
Clean();
m_Queue.Enqueue(entry);
}
public void Clean()
{
while (m_Queue.Count > 0)
{
SpeechLogEntry entry = m_Queue.Peek();
if (DateTime.UtcNow - entry.Created > EntryDuration)
m_Queue.Dequeue();
else
break;
}
}
public void CopyTo(SpeechLogEntry[] array, int index)
{
m_Queue.CopyTo(array, index);
}
private class SpeechLogTarget : Target
{
public SpeechLogTarget() : base(-1, false, TargetFlags.None)
{
}
protected override void OnTarget(Mobile from, object targeted)
{
if (!(targeted is PlayerMobile pm))
{
from.SendMessage("Speech logs aren't supported on that target.");
}
else if (from != targeted && from.AccessLevel <= pm.AccessLevel && from.AccessLevel != AccessLevel.Owner)
{
from.SendMessage("You don't have the required access level to view {0} speech log.",
pm.Female ? "her" : "his");
}
else if (pm.SpeechLog == null)
{
from.SendMessage("{0} has no speech log.", pm.Female ? "She" : "He");
}
else
{
CommandLogging.WriteLine(from, "{0} {1} viewing speech log of {2}", from.AccessLevel,
CommandLogging.Format(from), CommandLogging.Format(targeted));
Gump gump = new SpeechLogGump(pm, pm.SpeechLog);
from.SendGump(gump);
}
}
}
}
public class SpeechLogEntry
{
public SpeechLogEntry(Mobile from, string speech)
{
From = from;
Speech = speech;
Created = DateTime.UtcNow;
}
public Mobile From{ get; }
public string Speech{ get; }
public DateTime Created{ get; }
}
}

View file

@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Accounting;
using Server.Gumps;
using Server.Network;
namespace Server.Engines.Help
{
public class SpeechLogGump : Gump
{
public static readonly int MaxEntriesPerPage = 30;
private List<SpeechLogEntry> m_Log;
private int m_Page;
private Mobile m_Player;
public SpeechLogGump(Mobile player, SpeechLog log)
: this(player, new List<SpeechLogEntry>(log))
{
}
public SpeechLogGump(Mobile player, List<SpeechLogEntry> log) : this(player, log,
(log.Count - 1) / MaxEntriesPerPage)
{
}
public SpeechLogGump(Mobile player, List<SpeechLogEntry> log, int page)
: base(500, 30)
{
m_Player = player;
m_Log = log;
m_Page = page;
AddImageTiled(0, 0, 300, 425, 0xA40);
AddAlphaRegion(1, 1, 298, 423);
string playerName = player.Name;
string playerAccount = player.Account is Account ? player.Account.Username : "???";
AddHtml(10, 10, 280, 20,
$"<basefont color=#A0A0FF><center>SPEECH LOG - {playerName} (<i>{Utility.FixHtml(playerAccount)}</i>)</center></basefont>");
int lastPage = (log.Count - 1) / MaxEntriesPerPage;
string sLog;
if (page < 0 || page > lastPage)
{
sLog = "";
}
else
{
int max = log.Count - (lastPage - page) * MaxEntriesPerPage;
int min = Math.Max(max - MaxEntriesPerPage, 0);
StringBuilder builder = new StringBuilder();
for (int i = min; i < max; i++)
{
SpeechLogEntry entry = log[i];
Mobile m = entry.From;
string name = m.Name;
string account = m.Account is Account ? m.Account.Username : "???";
string speech = entry.Speech;
if (i != min)
builder.Append("<br>");
builder.AppendFormat("<u>{0}</u> (<i>{1}</i>): {2}", name, Utility.FixHtml(account),
Utility.FixHtml(speech));
}
sLog = builder.ToString();
}
AddHtml(10, 40, 280, 350, sLog, false, true);
if (page > 0)
AddButton(10, 395, 0xFAE, 0xFB0, 1); // Previous page
AddLabel(45, 395, 0x481, $"Current page: {page + 1}/{lastPage + 1}");
if (page < lastPage)
AddButton(261, 395, 0xFA5, 0xFA7, 2); // Next page
}
public override void OnResponse(NetState sender, RelayInfo info)
{
Mobile from = sender.Mobile;
switch (info.ButtonID)
{
case 1: // Previous page
{
if (m_Page - 1 >= 0)
from.SendGump(new SpeechLogGump(m_Player, m_Log, m_Page - 1));
break;
}
case 2: // Next page
{
if ((m_Page + 1) * MaxEntriesPerPage < m_Log.Count)
from.SendGump(new SpeechLogGump(m_Player, m_Log, m_Page + 1));
break;
}
}
}
}
}

View file

@ -0,0 +1,284 @@
using System;
using Server.Factions;
using Server.Gumps;
using Server.Mobiles;
using Server.Network;
namespace Server.Menus.Questions
{
public class StuckMenuEntry
{
public StuckMenuEntry(int name, Point3D[] locations)
{
Name = name;
Locations = locations;
}
public int Name{ get; }
public Point3D[] Locations{ get; }
}
public class StuckMenu : Gump
{
private static StuckMenuEntry[] m_Entries =
{
// Britain
new StuckMenuEntry(1011028, new[]
{
new Point3D(1522, 1757, 28),
new Point3D(1519, 1619, 10),
new Point3D(1457, 1538, 30),
new Point3D(1607, 1568, 20),
new Point3D(1643, 1680, 18)
}),
// Trinsic
new StuckMenuEntry(1011029, new[]
{
new Point3D(2005, 2754, 30),
new Point3D(1993, 2827, 0),
new Point3D(2044, 2883, 0),
new Point3D(1876, 2859, 20),
new Point3D(1865, 2687, 0)
}),
// Vesper
new StuckMenuEntry(1011030, new[]
{
new Point3D(2973, 891, 0),
new Point3D(3003, 776, 0),
new Point3D(2910, 727, 0),
new Point3D(2865, 804, 0),
new Point3D(2832, 927, 0)
}),
// Minoc
new StuckMenuEntry(1011031, new[]
{
new Point3D(2498, 392, 0),
new Point3D(2433, 541, 0),
new Point3D(2445, 501, 15),
new Point3D(2501, 469, 15),
new Point3D(2444, 420, 15)
}),
// Yew
new StuckMenuEntry(1011032, new[]
{
new Point3D(490, 1166, 0),
new Point3D(652, 1098, 0),
new Point3D(650, 1013, 0),
new Point3D(536, 979, 0),
new Point3D(464, 970, 0)
}),
// Cove
new StuckMenuEntry(1011033, new[]
{
new Point3D(2230, 1159, 0),
new Point3D(2218, 1203, 0),
new Point3D(2247, 1194, 0),
new Point3D(2236, 1224, 0),
new Point3D(2273, 1231, 0)
})
};
private static StuckMenuEntry[] m_T2AEntries =
{
// Papua
new StuckMenuEntry(1011057, new[]
{
new Point3D(5720, 3109, -1),
new Point3D(5677, 3176, -3),
new Point3D(5678, 3227, 0),
new Point3D(5769, 3206, -2),
new Point3D(5777, 3270, -1)
}),
// Delucia
new StuckMenuEntry(1011058, new[]
{
new Point3D(5216, 4033, 37),
new Point3D(5262, 4049, 37),
new Point3D(5284, 4006, 37),
new Point3D(5189, 3971, 39),
new Point3D(5243, 3960, 37)
})
};
private bool m_MarkUse;
private Mobile m_Mobile, m_Sender;
private Timer m_Timer;
public StuckMenu(Mobile beholder, Mobile beheld, bool markUse) : base(150, 50)
{
m_Sender = beholder;
m_Mobile = beheld;
m_MarkUse = markUse;
Closable = false;
Draggable = false;
Disposable = false;
AddBackground(0, 0, 270, 320, 2600);
AddHtmlLocalized(50, 20, 250, 35, 1011027); // Chose a town:
StuckMenuEntry[] entries = IsInSecondAgeArea(beheld) ? m_T2AEntries : m_Entries;
for (int i = 0; i < entries.Length; i++)
{
StuckMenuEntry entry = entries[i];
AddButton(50, 55 + 35 * i, 208, 209, i + 1);
AddHtmlLocalized(75, 55 + 35 * i, 335, 40, entry.Name);
}
AddButton(55, 263, 4005, 4007, 0);
AddHtmlLocalized(90, 265, 200, 35, 1011012); // CANCEL
}
private static bool IsInSecondAgeArea(Mobile m)
{
return (m.Map == Map.Trammel || m.Map == Map.Felucca) &&
(m.X >= 5120 && m.Y >= 2304 || m.Region.IsPartOf("Terathan Keep"));
}
public void BeginClose()
{
StopClose();
m_Timer = new CloseTimer(m_Mobile);
m_Timer.Start();
m_Mobile.Frozen = true;
}
public void StopClose()
{
m_Timer?.Stop();
m_Mobile.Frozen = false;
}
public override void OnResponse(NetState state, RelayInfo info)
{
StopClose();
if (Sigil.ExistsOn(m_Mobile))
{
m_Mobile.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
}
else if (info.ButtonID == 0)
{
if (m_Mobile == m_Sender)
m_Mobile.SendLocalizedMessage(1010588); // You choose not to go to any city.
}
else
{
int index = info.ButtonID - 1;
StuckMenuEntry[] entries = IsInSecondAgeArea(m_Mobile) ? m_T2AEntries : m_Entries;
if (index >= 0 && index < entries.Length)
Teleport(entries[index]);
}
}
private void Teleport(StuckMenuEntry entry)
{
if (m_MarkUse)
{
m_Mobile.SendLocalizedMessage(1010589); // You will be teleported within the next two minutes.
new TeleportTimer(m_Mobile, entry, TimeSpan.FromSeconds(10.0 + Utility.RandomDouble() * 110.0)).Start();
if (m_Mobile is PlayerMobile mobile)
mobile.UsedStuckMenu();
}
else
{
new TeleportTimer(m_Mobile, entry, TimeSpan.Zero).Start();
}
}
private class CloseTimer : Timer
{
private DateTime m_End;
private Mobile m_Mobile;
public CloseTimer(Mobile m) : base(TimeSpan.Zero, TimeSpan.FromSeconds(1.0))
{
m_Mobile = m;
m_End = DateTime.UtcNow + TimeSpan.FromMinutes(3.0);
}
protected override void OnTick()
{
if (m_Mobile.NetState == null || DateTime.UtcNow > m_End)
{
m_Mobile.Frozen = false;
m_Mobile.CloseGump<StuckMenu>();
Stop();
}
else
{
m_Mobile.Frozen = true;
}
}
}
private class TeleportTimer : Timer
{
private StuckMenuEntry m_Destination;
private DateTime m_End;
private Mobile m_Mobile;
public TeleportTimer(Mobile mobile, StuckMenuEntry destination, TimeSpan delay) : base(TimeSpan.Zero,
TimeSpan.FromSeconds(1.0))
{
Priority = TimerPriority.TwoFiftyMS;
m_Mobile = mobile;
m_Destination = destination;
m_End = DateTime.UtcNow + delay;
}
protected override void OnTick()
{
if (DateTime.UtcNow < m_End)
{
m_Mobile.Frozen = true;
}
else
{
m_Mobile.Frozen = false;
Stop();
if (Sigil.ExistsOn(m_Mobile))
{
m_Mobile.SendLocalizedMessage(1061632); // You can't do that while carrying the sigil.
return;
}
int idx = Utility.Random(m_Destination.Locations.Length);
Point3D dest = m_Destination.Locations[idx];
Map destMap;
if (m_Mobile.Map == Map.Trammel)
destMap = Map.Trammel;
else if (m_Mobile.Map == Map.Felucca)
destMap = Map.Felucca;
else
destMap = m_Mobile.Kills >= 5 ? Map.Felucca : Map.Trammel;
BaseCreature.TeleportPets(m_Mobile, dest, destMap);
m_Mobile.MoveToWorld(dest, destMap);
}
}
}
}
}