标签是文本描述,在这里被转意为换行符。
标签表示接受此任务的等级要求;是完成指定事件时,通知用户的消息;对应的是事件处理脚本。
下面我们再来查看一下事件相应脚本:
此脚本描述任务的分支及触发/完成条件,供服务器端的事件处理器使用。
界面设计预览
关键技术点--RichTextBox字体着色
字体着色非常简单,这里用到了一个RichTextBoxLinks的第三方控件,对于标签中的XML文档,可以直接使用以下代码进行染色:
///
/// 供MainForm中的任务描述RichTextBoxEx使用
/// 将任务描述字体染色并设置变量超链接
///
/// 要处理的任务Id
/// MainForm中的任务描述编辑框
public static void ShowRtfDescription(String missionId, RichTextBoxEx editor)
{
QuestXmlHandler.QuestInfo qi = (QuestXmlHandler.QuestInfo)QuestXmlHandler.QuestInfos[missionId];
StringBuilder description = new StringBuilder();
// 以行为解析单元
String line = "";
editor.SelectAll();
editor.Text = "";
try
{
int curPos = qi.UserDescription.Description.IndexOf("\n");
int lastPos = 0;
while (-1 != curPos)
{
line = qi.UserDescription.Description.Substring(lastPos, curPos - lastPos);
ShowRtfDescriptionImpl(line, editor);
lastPos = curPos + 1;
curPos = qi.UserDescription.Description.IndexOf("\n", lastPos);
editor.SelectedText = "\n";
}
}
catch (System.Exception ex)
{
Logger.LogFunctionError("RichEditBoxExRtfBinder.ShowRtfDescription()", ex.Message);
}
}
其中,QuestXmlHandler.QuestInfo qi = (QuestXmlHandler.QuestInfo)QuestXmlHandler.QuestInfos[missionId];是对任务的抽象,其关键定义如下:
///
/// 任务文件在程序中的抽象
///
public sealed class QuestXmlHandler
{
#region // 内嵌类
public sealed class QuestInfo
{
///
/// 新添加任务时自动生成的任务描述模版
///
public static String QuestDescriptionTemplate =
@"任务:【TODO:任务名称】
【TODO:简短描述】
任务描述:
【TODO:任务描述内容】
经验奖励:【TODO:经验】
";
///
/// 任务事件表,一种事件可能对应多个文件
///
public sealed class EventList
{
public List Events_ = new List();
public List Events
{
get { return Events_; }
}
}
public sealed class QuestDescriptionCollection
{
// 任务描述
private String Description_ = "";
public String Description
{
get { return Description_; }
set { Description_ = value; }
}
// 任务消息
private List Messages_ = new List();
public List Messages
{
get { return Messages_; }
}
public Boolean HasMessages()
{
return 0 != Messages.Count;
}
// 附加,任务等级要求
private int DiffcultLevel_ = -1;
public int DiffcultLevel
{
get { return DiffcultLevel_; }
set { DiffcultLevel_ = value; }
}
}
// 任务Id,不可重复
int Id_ = -1;
public int Id
{
get { return Id_; }
set { Id_ = value; }
}
// 任务名称,可以重复
String Name_;
public String Name
{
get { return Name_; }
set { Name_ = value; }
}
// 任务所属地图
String Map_ = "";
public String Map
{
get { return Map_; }
set { Map_ = value; }
}
// 事件表,每个键对应的值是一个QuestInfo结构,对应此键的所有事件相应文件。
private Hashtable EventsTable_ = new Hashtable();
public Hashtable EventsTable
{
get { return EventsTable_; }
}
// 标记此任务是否具有某项事件
private Hashtable HasEvents_ = new Hashtable();
public Boolean HasEvent(String eventName)
{
try
{
return (Boolean)HasEvents_[eventName];
}
catch (System.Exception)
{
Logger.Error("QuestXmlReader.QuestInfo.HasEvent()键错误:key=" + eventName + " Id=" + Id.ToString() + " Name=" + Name);
return false;
}
}
public void SetEvent(String eventName)
{
try
{
HasEvents_[eventName] = true;
}
catch (System.Exception)
{
Logger.Error("QuestXmlReader.QuestInfo.SetEvent()键错误:key=" + eventName + " Id=" + Id.ToString() + " Name=" + Name);
}
}
public void ClearEvent(String eventName)
{
try
{
HasEvents_[eventName] = false;
}
catch (System.Exception)
{
Logger.Error("QuestXmlReader.QuestInfo.ClearEvent()键错误:key=" + eventName + " Id=" + Id.ToString() + " Name=" + Name);
}
}
public void UpdateEvents()
{
foreach (AppConfig.EventInfo eventInfo in AppConfig.EventInfos)
{
if (0 != ((EventList)EventsTable[eventInfo.EventType]).Events.Count)
HasEvent(eventInfo.EventType);
else
ClearEvent(eventInfo.EventType);
}
}
private QuestDescriptionCollection QuestDescription_ = new QuestDescriptionCollection();
public QuestDescriptionCollection UserDescription
{
get { return QuestDescription_; }
}
public QuestInfo()
{
foreach (AppConfig.EventInfo ei in AppConfig.EventInfos)
{
EventsTable.Add(ei.EventType, new EventList());
HasEvents_.Add(ei.EventType, false);
}
}
}
#endregion
/// ...
}
}
对于选中的某个字符或者段落进行染色,只需使用下面代码:
private void BtnFontColor_Click(object sender, EventArgs e)
{
ColorDialog dlg = new ColorDialog();
dlg.AllowFullOpen = false; // 不允许使用自定义颜色
dlg.SolidColorOnly = true; // 只允许使用纯色
if (DialogResult.OK == dlg.ShowDialog())
UserMissionListDescription.SelectionColor = dlg.Color;
CurrentMissionEditState.UserMissionListDescriptionChanged = true;
}
其中,UserMissionListDescription为RichTextBoxLinks控件的标识符,CurrentMissionEditState是标识当前任务被更改的部分。
关键技术点--RichTextBox中带编辑功能的任务变量及自动寻路超链接
这里的关键点是我们先前提到的RichTextBoxLinks第三方控件,我们用到了其中的InsertLink(前端显示的字符串,后台超链接);方法,下面是新插入自动寻路超链接的关键代码:
///
/// 插入自动寻路超链接
///
///
///
private void BtnInsertGotoHref_Click(object sender, EventArgs e)
{
GotoHrefForm dlg = new GotoHrefForm(GotoHrefForm.DialogType.InsertNew);
dlg.ShowDialog();
if (GotoHrefForm.InsertSucceed)
{
CurrentMissionEditState.UserMissionListDescriptionChanged = true;
// 字符串格式 NPC名字(x,y)#NPC名字(x,y)
StringBuilder href = new StringBuilder();
href.AppendFormat("{2}({3},{4})",
GotoHrefForm.x, GotoHrefForm.y, GotoHrefForm.NpcName, GotoHrefForm.x, GotoHrefForm.y);
StringBuilder NpcNameHref = new StringBuilder();
NpcNameHref.AppendFormat("{0}({1},{2})", GotoHrefForm.NpcName, GotoHrefForm.x, GotoHrefForm.y);
UserMissionListDescription.InsertLink(NpcNameHref.ToString(), href.ToString());
}
}
这里用到了一个HACK,即使用格式 NPC名字(x,y)#NPC名字(x,y) 这个固定形式的字符串来标识文本框中显示的超链接内容,其符合RTF标准,此HACK为超链接编辑功能的前置条件。
任务变量的插入和自动寻路超链接类似,不过不会显示任务变量的具体名称,而统一显示为“任务变量”超链接的形式,代码如下:
///
/// 插入任务变量超链接
///
///
///
private void BtnInsertVar_Click(object sender, EventArgs e)
{
MissionVarForm dlg = new MissionVarForm(CurrentEditMission, MissionVarForm.DialogType.InsertNew);
dlg.ShowDialog();
if (MissionVarForm.InsertSucceed)
{
CurrentMissionEditState.UserMissionListDescriptionChanged = true;
//
StringBuilder href = new StringBuilder();
href.AppendFormat("", MissionVarForm.MissionVar, MissionVarForm.Task);
UserMissionListDescription.InsertLink("任务变量", href.ToString());
}
}
为了支持点击超链接弹出修改对话框的功能,我们需要相应RichTextBox控件的LinkClicked事件,代码如下:
///
/// 编辑自动寻路和任务变量
///
///
///
private void UserMissionListDescription_LinkClicked(object sender, LinkClickedEventArgs e)
{
try
{
CurrentMissionEditState.UserMissionListDescriptionChanged = true;
// text字符串的格式为 NPC名字(x,y)#NPC名字(x,y)
// 或者 任务变量#
String text = e.LinkText;
// 通过查找"#<"来定位我们硬编码的超链接数据
int pos = text.LastIndexOf("#<");
if (-1 == pos)
return;
if ('a' == text[pos + "#<".Length]) // 编辑自动寻路功能
MainFormImpl.EditNpcAutomaticPathfinding(text, pos, UserMissionListDescription);
else if ('n' == text[pos + "#<".Length]) // 编辑任务变量
MainFormImpl.EditMissionVar(text, pos, CurrentEditMission, UserMissionListDescription);
}
catch (System.Exception ex)
{
Logger.LogFunctionError("MainForm.UserMissionListDescription_LinkClicked()", ex.Message);
}
}
在LinkClicked中判断所点击的超链接是自动寻路还是任务变量,并将其传递给相应的对话框,并完成相应的逻辑工作,前面的HACK是为了全局替换所有相同的超链接,做到修改一处,自动更新其他超链接的效果。
关键技术点--RichTextBox中数据的保存
这个是本任务编辑器中最复杂的部分,由于RichTextBox的内部数据结构是RTF格式,因此我们需要自己手动解析RTF文档,并将其转换成XML格式。
这里我采用的办法是以一行为一个解析单元,将行中的超链接先提取出来,并依次将字符串分割,分割效果如下图所示:
对于上图的情况,使用贪心算法将“分组1”和“分组3”的字体颜色进行提取,并构造XML数据,这里“分组1”和“分组3”字体颜色一样,贪心算法计算的结果是将整个分组封装成一个标签。
之所以说这个算法复杂是因为实际的情况远比上面的例子要复杂,需要处理各种边界问题,下面将关键代码贴出,其复杂性大家一看便知:
// Copyright © 2012 123u. All Rights Reserved
// 作者:凝霜 博客 http://blog.csdn.net/mdl13412
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RichTextBoxLinks;
using System.Windows.Forms;
using System.Xml;
using System.Drawing;
namespace MissionEditor
{
class RichTextBoxExRtfBinder
{
#region // 通用
///
/// 获取XML标签中的颜色
///
///
///
public static Color GetRgb(String color)
{
// 解析失败则返回黑色
try
{
int pos = 0;
String r = "";
String g = "";
String b = "";
String a = "";
while ('0' <= color[pos] && color[pos] <= '9')
{
r += color[pos];
++pos;
}
++pos;
while ('0' <= color[pos] && color[pos] <= '9')
{
g += color[pos];
++pos;
}
++pos;
while ('0' <= color[pos] && color[pos] <= '9')
{
b += color[pos];
++pos;
}
++pos;
while (pos < color.Length && '0' <= color[pos] && color[pos] <= '9')
{
a += color[pos];
pos++;
}
Color c = Color.FromArgb(Convert.ToInt32(a), Convert.ToInt32(r), Convert.ToInt32(g), Convert.ToInt32(b));
return c;
}
catch (Exception)
{
return Color.Black;
}
}
///
/// 获取RTF标签中的颜色
///
///
///
private static Color GetRtfColor(String rtf)
{
// rtf字符串中的字体颜色相关字符串格式 {\colortbl ;\red255\green0\blue0;}
try
{
int colorPos = rtf.IndexOf("{\\colortbl");
if (-1 == colorPos)
return Color.Black;
colorPos += "{\\colortbl".Length;
int pos = 0;
String r = "";
String g = "";
String b = "";
while (!('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9'))
++pos;
while ('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9')
{
r += rtf[colorPos + pos];
++pos;
}
while (!('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9'))
++pos;
while ('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9')
{
g += rtf[colorPos + pos];
++pos;
}
while (!('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9'))
++pos;
while ('0' <= rtf[colorPos + pos] && rtf[colorPos + pos] <= '9')
{
b += rtf[colorPos + pos];
++pos;
}
Color c = Color.FromArgb(Convert.ToInt32(r), Convert.ToInt32(g), Convert.ToInt32(b));
return c;
}
catch (Exception)
{
return Color.Black;
}
}
#endregion
#region // 供MainForm中的任务描述RichTextBoxEx使用
///
/// 供MainForm中的任务描述RichTextBoxEx使用
/// 将任务描述字体染色并设置变量超链接
///
/// 要处理的任务Id
/// MainForm中的任务描述编辑框
public static void ShowRtfDescription(String missionId, RichTextBoxEx editor)
{
QuestXmlHandler.QuestInfo qi = (QuestXmlHandler.QuestInfo)QuestXmlHandler.QuestInfos[missionId];
StringBuilder description = new StringBuilder();
// 以行为解析单元
String line = "";
editor.SelectAll();
editor.Text = "";
try
{
int curPos = qi.UserDescription.Description.IndexOf("\n");
int lastPos = 0;
while (-1 != curPos)
{
line = qi.UserDescription.Description.Substring(lastPos, curPos - lastPos);
ShowRtfDescriptionImpl(line, editor);
lastPos = curPos + 1;
curPos = qi.UserDescription.Description.IndexOf("\n", lastPos);
editor.SelectedText = "\n";
}
}
catch (System.Exception ex)
{
Logger.LogFunctionError("RichEditBoxExRtfBinder.ShowRtfDescription()", ex.Message);
}
}
private static void ShowRtfDescriptionImpl(String line, RichTextBoxEx editor)
{
// 用户描述部分只有两种标签和
int nTagPos = line.IndexOf("
{
aTagPos = ParseRtfDescriptionATag(line, editor, aTagPos, doc);
}
else if (-1 == aTagPos) // 解析
{
nTagPos = ParseRtfDescriptionNTag(line, editor, nTagPos, doc);
}
else
{
// 同时解析和
if (nTagPos < aTagPos)
{
nTagPos = ParseRtfDescriptionNTag(line, editor, nTagPos, doc);
}
else if (nTagPos > aTagPos)
{
aTagPos = ParseRtfDescriptionATag(line, editor, aTagPos, doc);
}
else
{
throw new Exception("节点解析错误和位移相同");
}
}
}
catch (Exception ex)
{
Logger.LogFunctionError("RichEditBoxExRtfBinder.ShowRtfDescriptionImpl()", ex.Message);
}
}
}
///
/// 解析节点
///
/// 行文本
/// 绑定的编辑框
/// 标签的位移
/// xml节点
/// 下一个节点位移
private static int ParseRtfDescriptionATag(String line, RichTextBoxEx editor, int aTagPos, XmlDocument doc)
{
int endATagPos = line.IndexOf("", aTagPos);
if (-1 == endATagPos)
throw new Exception("节点解析错误" + line);
// 使用XML来简化编程
String text = line.Substring(aTagPos, endATagPos - aTagPos + "".Length);
XmlElement Root = doc.CreateElement("node");
Root.InnerXml = text;
doc.AppendChild(Root);
try
{
editor.InsertLink(doc.ChildNodes[0].ChildNodes[0].InnerText, doc.ChildNodes[0].InnerXml);
}
catch (System.Exception)
{
}
return line.IndexOf("".Length);
}
///
/// 解析节点
///
/// 行文本
/// 绑定的编辑框
/// 标签的位移
/// xml节点
/// 下一个节点位移
private static int ParseRtfDescriptionNTag(String line, RichTextBoxEx editor, int nTagPos, XmlDocument doc)
{
int endNTagPos = line.IndexOf("", nTagPos);
if (-1 == endNTagPos)
throw new Exception("节点解析错误" + line);
String text = line.Substring(nTagPos, endNTagPos - nTagPos + "".Length);
XmlElement Root = doc.CreateElement("node");
Root.InnerXml = text;
doc.AppendChild(Root);
try
{
// 如果获取颜色失败,则判断是否为任务变量
String color = doc.ChildNodes[0].ChildNodes[0].Attributes["color"].Value;
editor.SelectionColor = GetRgb(color);
editor.SelectedText = doc.ChildNodes[0].ChildNodes[0].InnerText;
}
catch (System.Exception)
{
try
{
// 获取任务变量信息
String var = doc.ChildNodes[0].ChildNodes[0].Attributes["var"].Value;
String task = doc.ChildNodes[0].ChildNodes[0].Attributes["task"].Value;
//
editor.InsertLink("任务变量", doc.ChildNodes[0].InnerXml);
}
catch (System.Exception)
{
try
{
// 默认颜色的普通文本
editor.SelectionColor = Color.Black;
editor.SelectedText = doc.ChildNodes[0].ChildNodes[0].InnerText;
}
catch (System.Exception)
{
}
}
}
return line.IndexOf("".Length);
}
///
/// 将编辑框中的RTF转换为XML文件
///
/// MainForm中的任务描述编辑框
/// XML文本
public static String ConverterRtfDescriptionToXml(RichTextBoxEx editor)
{
StringBuilder description = new StringBuilder();
try
{
// 以行为解析单元
int lineStart = 0;
int lineEnd = editor.Text.IndexOf("\n");
// 只有一行数据且没有换行
if (-1 == lineEnd && 0 != editor.Text.Length)
lineEnd = editor.Text.Length;
int length = lineEnd - lineStart;
String line = "";
while (-1 != lineEnd)
{
length = lineEnd - lineStart;
line = editor.Text.Substring(lineStart, lineEnd - lineStart);
if ("" != line) // 跳过空白行
{
try
{
List> hrefs = ConverterRtfDescriptionHrefHelper(line);
if (null == hrefs)
throw new Exception("获取链接集合错误");
ConverterRtfDescriptionTags(description, line, hrefs, editor, lineStart);
}
catch (System.Exception ex)
{
Logger.LogFunctionError("RichTextBoxExRtfBinder.ConverterRtfDescriptionToInnerXml()[Tag_解析标签]", ex.Message);
return "";
}
description.Append("\n");
}
lineStart = lineEnd + 1;
if (lineStart >= editor.Text.Length)
{
lineEnd = -1;
}
else
{
lineEnd = editor.Text.IndexOf("\n", lineStart);
if (-1 == lineEnd)
lineEnd = editor.Text.Length;
}
}
return description.ToString();
}
catch (Exception ex)
{
Logger.LogFunctionError("RichTextBoxExRtfBinder.ConverterRtfDescriptionToInnerXml()[Tag_解析一行数据]", ex.Message);
return "";
}
}
///
/// 解析出一行中的所有超链接和任务变量
///
/// 行文本
/// 所有超链接的集合
private static List> ConverterRtfDescriptionHrefHelper(String line)
{
try
{
// 所有变量链接的起始点,终点,XML数据
List> hrefs = new List>();
// 首先找出所有的变量链接
int hrefStart = 0;
int hrefEnd = 0;
int hrefTag = line.IndexOf("#");
while (-1 != hrefTag)
{
int pos1 = 0;
int pos2 = 0;
String href = "";
String info = "";
String var = "";
switch (line[hrefTag + 2])
{
case 'n':
//
pos1 = line.IndexOf("", hrefTag);
var = line.Substring(hrefTag + "#".Length, pos1 + "".Length - hrefTag - "#".Length);
hrefStart = hrefTag - "任务变量".Length;
hrefEnd = pos1 + "".Length;
hrefs.Add(new Tuple(hrefStart, hrefEnd, var));
break;
case 'a':
// 格式为 NPC(x,y)#NPC(x,y)
pos1 = line.IndexOf(">", hrefTag);
pos2 = line.IndexOf("", hrefTag);
href = line.Substring(hrefTag + "#".Length, pos2 + "