API:Gossip Menus

From WCell Wiki
Jump to: navigation, search

This article is related to development using the WCell API.

  • Gossip menus are very useful for administration/commands and/or user-given options.
  • They allow the server to query the user for custom feedback in a form of a dialog
  • And they are very easy to create

Contents

Defining Gossips

Every GossipMenu consists of the following parts:

  • A text to be displayed (which is either never modified and cached client-side or dynamically generated on a case-by-case basis)
  • A set of GossipMenuItem (representing clickable buttons in the menu)

GossipMenuItems have:

  • An optional icon (indicated by the GossipMenuIcon Enum)
  • An optional Text
  • An optional ConfirmText which will be displayed to the user within an Ok/Cancel dialog when the item is clicked (to prevent users from accidently selecting an option with serious consequences)
  • An optional Action that defines what to do when the item is selected
  • An optional SubMenu (also of type GossipMenu) that will be opened when the item is clicked
  • GossipMenus can be unclosable in which case they will only be closed if the User selects the "Quit" option
  • The SubMenus give you the power of nesting Gossip Menus as deep as you like
  • You can add "Back" (to the parent menu) and "Quit" buttons automatically

Using Gossips

  • You can either attach GossipMenus to NPCs, by assigning:
    • NPCEntry.DefaultGossipMenu or
    • NPC.GossipMenu
  • Or you can start a gossip on any player by calling:

Features

  • Simple and powerful
  • Fully dynamic - Even the menu's text can be generated dynamically, using the DynamicTextGossipMenu class
  • Fully localizable
  • Items and text can be made fully conditional - Show different things in different situations


Examples

Assign a Menu to NPCs

[Initialization]
[DependentInitialization(typeof(NPCMgr))]
public static void InitMyNPCs
{
	var entry = NPCMgr.GetEntry(NPCId.HeraldOfTheAlliance);
	entry.DefaultGossipMenu = new GossipMenu(
				new GossipMenuItem("Click Me!", convo => {
					convo.Speaker.SendSystemMessage("You clicked me!");
				});

	var entry = NPCMgr.GetEntry(NPCId.FallenHeroOfTheHorde);
	entry.BeforeDeath += hero => {
                var killer = hero.FirstAttacker as Character;
                if (killer == null)                                          // do nothing if not killed by a player
                        return true;
                if (!(killer is Character))
                        killer = killer.MasterChar;
                if (killer == null || !(killer is Character))
                        return true;

                hero.FactionId = FactionId.Friendly;                          // become friendly to everyone and idle
                hero.Brain.State = BrainState.Idle;

		hero.GossipMenu =  new GossipMenu(                            // give the NPC a new Gossip right when he is about to die
		new GossipMenuItem("I forgive you!", convo => {               // give player the option to forgive the hero
			convo.Speaker.Say("Thank you so much!");              // say something
			convo.Speaker.CallDelayed(2000, obj => obj.Delete()); // delete after 2 seconds
			convo.Character.GainXp(1000);                         // give xp
		});
                killer.StartGossip(hero.GossipMenu);                          // start gossip automatically
		return false;                                                 // don't die
	}
}

Teleport Menu

From the TeleportCommand:

		public static GossipMenu CreateTeleMenu(Unit user, List<WorldZoneLocation> locations)
		{
			// create gossip of all options
			var menu = new GossipMenu();
			foreach (var location in locations)
			{
				var loc = location;		// allocate a local copy
				menu.AddItem(new GossipMenuItem(loc.Name, convo =>
				{
					user.TeleportTo(loc);
				}));
			}
			return menu;
		}

Sample Menu

As found in MixedSamples:

			return new GossipMenu(
				new GossipMenuItem("Click Me!", convo => {
					convo.Speaker.SendSystemMessage("You clicked me!");
				}),
				new GossipMenuItem("Icon List", convo => {
					convo.Speaker.SendSystemMessage("A list of all available Gossip Icons");
				},
								   new GossipMenu(
									new GossipMenuItem(GossipMenuIcon.Talk, "Talk"),
									new GossipMenuItem(GossipMenuIcon.Trade, "Trade"),
									new GossipMenuItem(GossipMenuIcon.Taxi, "Taxi"),
									new GossipMenuItem(GossipMenuIcon.Train, "Train"),
									new GossipMenuItem(GossipMenuIcon.Resurrect, "Resurrect"),
									new GossipMenuItem(GossipMenuIcon.Bind, "Bind"),
									new GossipMenuItem(GossipMenuIcon.Bank, "Bank"),
									new GossipMenuItem(GossipMenuIcon.Guild, "Guild"),
									new GossipMenuItem(GossipMenuIcon.Tabard, "Tabard"),
									new GossipMenuItem(GossipMenuIcon.Battlefield, "Battlefield")
									) {
										BodyTextId = sampleGossipTextId
									}
					),
				// we need this to close the Gossip again
				new QuitGossipMenuItem("Done")) {
					KeepOpen = true
				};


Advanced Examples

Spawn Gossip Menu

  • The Map Editor Menu can be used to customize NPC Spawn information and waypoints ingame (not fully done yet)
  • It demonstrates the advanced features of the Gossip Menu system:
using System;
using WCell.RealmServer.Entities;
using WCell.RealmServer.GameObjects;
using WCell.RealmServer.Gossips;
using WCell.RealmServer.Lang;
using WCell.RealmServer.NPCs;

namespace WCell.RealmServer.Editor.Menus
{
	public class MapEditorMenu : DynamicTextGossipMenu
	{
		/// <summary>
		/// Generate the menu's text dynamically
		/// </summary>
		public override string GetText(GossipConversation convo)
		{
			var text = RealmLocalizer.Instance.Translate(convo.Character.Locale, RealmLangKey.EditorMapMenuText) + GossipTextHelper.Newline;

			if (!GOMgr.Loaded || !NPCMgr.Loaded)
			{
				if (!convo.Speaker.HasUpdateAction(action => action is PeriodicLoadMapTimer))
				{
					// already loading
					text += RealmLocalizer.Instance.Translate(convo.Character.Locale, RealmLangKey.EditorMapMenuStatusNoData);
				}
				else
				{
					// not loading yet
					text += RealmLocalizer.Instance.Translate(convo.Character.Locale, RealmLangKey.EditorMapMenuStatusDataLoading);
				}
			}
			else if (!Editor.Map.IsSpawned)
			{
				if (Editor.Map.IsSpawning)
				{
					// already spawning
					text += RealmLocalizer.Instance.Translate(convo.Character.Locale, RealmLangKey.EditorMapMenuStatusSpawning);
				}
				else
				{
					// not spawning yet
					text += RealmLocalizer.Instance.Translate(convo.Character.Locale, RealmLangKey.EditorMapMenuStatusNotSpawned);
				}
			}
			return text;
		}

		public MapEditorMenu(MapEditor editor)
		{
			Editor = editor;
			KeepOpen = true;

			AddItem(new LocalizedGossipMenuItem(OnLoadClicked,
				convo => (!GOMgr.Loaded || !NPCMgr.Loaded) &&		// timer not running yet
					!convo.Speaker.HasUpdateAction(action => action is PeriodicLoadMapTimer),
				RealmLangKey.EditorMapMenuLoadData));

			AddItem(new LocalizedGossipMenuItem(convo =>
				{
					Editor.Map.SpawnMapLater();
					convo.Character.AddMessage(convo.Invalidate);		// show menu again, when done spawning
				},
				convo => GOMgr.Loaded && NPCMgr.Loaded && !Editor.Map.IsSpawned && !Editor.Map.IsSpawning,
				RealmLangKey.EditorMapMenuSpawnMap));

			AddItem(new LocalizedGossipMenuItem(convo =>
				{
					Editor.Map.ClearLater();
					convo.Character.AddMessage(convo.Invalidate);		// show menu again, when done clearing
				},
				convo => Editor.Map.IsSpawned,
				RealmLangKey.AreYouSure,
				RealmLangKey.EditorMapMenuClearMap));

			AddItem(new LocalizedGossipMenuItem(convo => Editor.IsVisible = true,
				convo => Editor.Map.IsSpawned && !Editor.IsVisible,
				RealmLangKey.EditorMapMenuShow));

			AddItem(new LocalizedGossipMenuItem(convo => Editor.IsVisible = false,
				convo => Editor.Map.IsSpawned && Editor.IsVisible,
				RealmLangKey.EditorMapMenuHide));

			AddItem(new LocalizedGossipMenuItem(convo => Editor.Map.SpawnPointsEnabled = true,
				convo => !Editor.Map.SpawnPointsEnabled,
				RealmLangKey.EditorMapMenuEnableAllSpawnPoints));

			AddItem(new LocalizedGossipMenuItem(convo => Editor.Map.SpawnPointsEnabled = false,
				convo => Editor.Map.SpawnPointsEnabled,
				RealmLangKey.AreYouSure,
				RealmLangKey.EditorMapMenuDisableAllSpawnPoints));


			// leave the editor
			AddQuitMenuItem(convo => Editor.Leave(convo.Character));
		}

		public MapEditor Editor { get; private set; }

		static void OnLoadClicked(GossipConversation convo)
		{
			GOMgr.LoadAllLater();
			NPCMgr.LoadAllLater();

			var chr = convo.Character;
			chr.SendSystemMessage(RealmLangKey.PleaseWait);

			convo.Character.AddUpdateAction(new PeriodicLoadMapTimer(convo));
		}

		class PeriodicLoadMapTimer : ObjectUpdateTimer
		{
			private readonly GossipConversation m_Convo;

			public PeriodicLoadMapTimer(GossipConversation convo)
			{
				m_Convo = convo;
				Delay = 1000;
				Callback = OnTick;
			}

			void OnTick(WorldObject obj)
			{
				// invalidate menu, once everything is loaded
				var chr = (Character)obj;
				if (NPCMgr.Loaded && GOMgr.Loaded)
				{
					if (chr.GossipConversation == m_Convo)
					{
						m_Convo.Invalidate();
					}
					chr.RemoveUpdateAction(this);
				}
			}
		}
	}
}
Personal tools