任务系统
1 任务系统设计思路
(1) 玩家对任务的操作有:接受、提交、放弃任务。他们的逻辑入口函数都是UBI_CAIHuman类成员函数,主要处理逻辑都放在了UBI_CAIQuest类的成员函数里。
(a) 接受任务:UBI_CAIHuman::DoAcceptQuest()->UBI_CAIQuest::DoAccept()
(b) 提交任务:UBI_CAIHuman::DoCompleteQuest()->UBI_CAIQuest::DoComplete()
(c) 放弃任务:UBI_CAIHuman::DoAbandonQuest()->UBI_CAIQuest::DoAbandon()
(2) 按照任务内容,来更新任务状态。
(a) 杀怪:UBI_CAIHuman::OnQuestKillObject()->UBI_CAIQuest::DoQuestKillObject()
(b) 采集:UBI_CAIHuman::OnQuestGather()->UBI_CAIQuest::DoQuestGather()
(c) 进入区域:UBI_CAIHuman::OnQuestEnterArea()->UBI_CAIQuest::DoQuestEnterArea()
(d) 使用物品:UBI_CAIHuman::OnQuestUseItem()->UBI_CAIQuest::DoQuestUseItem()
(e) 点击面板:UBI_CAIHuman::OnQuestClickPanel()->UBI_CAIQuest::DoQuestClickPanel()
(f) 物品改变:UBI_CAIHuman::OnQuestItemCountChange()->UBI_CAIQuest::DoQuestItem _CountChange()
2任务系统关注点
(1) 任务的实现方式有两种,脚本和表格类。
因此在任务的每个逻辑流程中都实现了脚本和表格分支,它们是通过任务基本表里的任务绑定脚本ID来区分的,若ID大于0则走脚本流程,否则走表格流程。具体如下:
(a) 点击NPC显示任务选项:在Lua_DispatchEventList函数里对任务是否显示的判断做了脚本和表格的分支。
(b) 接受任务:在接受任务的入口函数UBI_CAIHuman::DoAcceptQuest()里做了脚本和表格的分支。
(c) 提交任务:在提交任务的入口函数UBI_CAIHuman::DoCompleteQuest()里做了脚本和表格的分支。
(d) 放弃任务:在放弃任务的入口函数UBI_CAIHuman::DoAbandonQuest()里做了脚本和表格的分支。
(e) 杀怪:在函数UBI_CAIHuman::OnQuestKillObject()里做了脚本和表格的分支。只要是人为杀死的怪都会走任务逻辑。表格:先遍历玩家身上的任务,再在任务基本表中找到他的杀怪触发点。脚本:调用该玩家所有已接任务脚本里的OnKillObject()接口。如果多个任务杀的是同种怪,则杀死一个这类怪就会更新所有这些相关任务参数。
(f) 采集:在函数UBI_CAIHuman::OnQuestGather()里做了脚本和表格的分支。每次采集生长点都会走任务流程。表格:先遍历玩家身上的任务,再在任务基本表中找到他的采集触发点。脚本:调用该玩家所有已接任务脚本里的OnGather ()接口。
(g) 进入区域:在函数UBI_CObjectHuman::OnEnterArea()里做了脚本和表格的分支。脚本:玩家进入绑定了脚本的区域里的时候,程序会调用到脚本函数OnEnterArea(),在里面判断该玩家是否接了该区域相关任务,如果是则调用相应的任务脚本函数。表格:玩家进入任何区域都会走进区域任务函数UBI_CAIHuman::OnQuestEnterArea(),在这里先遍历玩家身上的任务,再在任务基本表中找到他的区域触发点。
(h) 使用物品:在CSUseQuestItemHandler()函数里做了脚本和表格的分支。物品是否能使用的判断逻辑和物品的使用逻辑做了分离,这两个逻辑均做了脚本和表格的分支。
(i) 点击面板:有两种面板,一个是与NPC关联的,一个是非关联的,它们分别在CSAskNpcEventInfoHandler()函数和CSAskTriggerPanelInfoHandler()函数里做了脚本和表格的分支。表格:通过配表可实现对话以及其他一些功能,比如生成NPC,特效等。脚本:可实现对话,另外可灵活添加其它功能。复杂的对话任务最好用脚本去实现。
(j) 物品改变:在UBI_CAIHuman::OnQuestItemCountChange()函数里做了脚本和表格的分支。表格:先遍历玩家身上的任务,再在任务基本表中找到他的物品改变触发点。脚本:调用该玩家所有已接任务脚本里的OnItemChanged ()接口。
(2) 脚本触发点和效率优化。
(a) 在NPC对话框里是否显示任务:OnEnumerate(),比如判断前序任务是否做完,判断玩家等级是否达到接受任务的要求等。
(b) 接受任务:OnAccept(),将任务挂靠在玩家身上写死在了C++代码里,脚本只需关注接受任务后的一些特殊需求,比如切相位,给物品等。
(c) 放弃任务:OnAbandon(),删除玩家身上的任务也写死在了C++代码里,脚本只需关注放弃任务后的一些特殊需求,比如删物品,删buff等。
(d) 提交任务:OnSubmit()。给玩家的普通奖励写死在了C++代码里,脚本只需关注提交任务后的特殊需求,比如给buff,给技能等。
(e) 杀怪:OnKillObject(),怪物被玩家杀死之后只要玩家身上有已接的脚本类任务,就会走到这些任务的这个接口里,当然只要这些任务的目标不是杀这个怪,那么只是走进去而已,不会有别的逻辑。效率上是否能再优化呢?
(f) 采集:OnGather(),玩家采集完生长点之后只要玩家身上有已接的脚本类任务,就会走到这些任务的这个接口里。同上,只要这些任务的目标不是采集这个生长点,那么也只是走进去而已,不会有别的逻辑。采集事件不多,个人感觉效率上没有必要再去优化。如果需要优化的话,可以通过将生长点绑定脚本,然后调用该脚本来实现。
(g) 进入区域:Area:OnEnterArea()->Quest:OnEnterArea(),玩家进入绑定了脚本的区域后,调用该区域脚本里的Area:OnEnterArea()接口,在该接口里再调用所有与该区域相关的脚本类任务的Quest:OnEnterArea()接口。这个效率应该还行,没什么可优化的。
(h) 使用物品:CanUseQuestItem()和OnQuestUseItem(),玩家在使用任务物品时先通过CanUseQuestItem()接口来判断是否能使用,比如使用位置,场景是否匹配等,然后再通过OnQuestUseItem()接口来做一些使用完物品的逻辑。目前实现有问题,需要修改。需要将任务物品绑定脚本来实现。
(i) 点击面板:即对话任务,OnDialog()和OnDialogRequest(),目前能满足策划的对话任务的所有需求,效率上没什么可优化的了。
(j) 物品改变:OnItemChanged()。背包里添加和删除物品都会走到这,影响服务器效率。优化的方式就是通过绑定物品与脚本的方式来做。
(k) 上线:OnOnLine(),主要针对计时类或者一些特殊任务的上线处理。每个脚本任务都会有这个接口,所以玩家每次上线只要身上挂了任务,就会走到这里,不过一般没什么处理逻辑。
(l) 下线:OnOffLine(),同上。
(m) 其它:策划有时候会有一些特殊任务需求,比如切相位触发任务完成,穿装备触发任务完成等,我们目前的做法都是尽量让它只触发到某个特定脚本,而不是去走一遍玩家身上挂靠的所有任务的相关接口,以节省服务器执行时间。
(3) 网络包流量。
任务对白,任务奖励数据客户端和服务器都存了一份,因此服务端发任务相关包的时候无需加上这些数据,节省了包流量。客户端所存的任务数据只做显示用,不做逻辑处理,所有任务逻辑都在服务端做。
3天龙任务系统流程
(1) NPC身上的任务的挂接
表格任务,需要先把任务挂接到NPC身上;脚本任务,一般不需要事先挂接好,任务脚本号(即事件ID列表)会写在NPC的Obj脚本中,执行函数时,会轮询那些事件ID列表中的脚本号,执行NPC的Event脚本。
(2) 客户端点击NPC弹出对话面板
à Client: 点击Npc时候,发CGCharDefaultEvent包。
CGCharDefaultEvent msg;
msg.setObjID(idTargetObj);
CNetManager::GetMe()->SendPacket(&msg );
à Server: 根据Npc的ObjID号,PushCommand。
pHuman->GetHumanAI()->PushCommand_DefaultEvent(idTarget),此函数里面做了:
pCharacter->getScene()->GetLuaInterface()->ExeScript_DDD(
idScript,
DEF_EVENT_ENTRY_FUNC_NAME, //脚本进入函数 "OnDefaultEvent"
(INT)pCharacter->getScene()->SceneID(),
(INT)pCharacter->GetID(),
(INT)pNPC->GetID()) ;
NPC的Obj脚本函数OnDefaultEvent里面一般是:
BeginEvent(sceneId)
local PlayerName=GetName(sceneId,selfId)
AddText(sceneId,"你们宋人都是我的俘虏,都要听我的")
for i, eventId in x022008_g_eventList do ---遍历事件列表中的Event脚本号
CallScriptFunction(eventId, "OnEnumerate",sceneId,selfId, targetId)
End
EndEvent(sceneId)
DispatchEventList(sceneId,selfId,targetId)
Npc的Event脚本中的OnEnumerate函数里,也是类似一些AddNumText、AddText函数,他们被BeginEvent…EndEvent…DispatchEventList包了起来,其作用是:
BeginEvent是把一块任务缓冲MisBuf清空,为传输做好准备;
typedef struct_MisBuf
{
enum
{
MISSIONBUFLEN= 64,
MISSIONCHARBUFLEN= MISSIONBUFLEN * 4 * 8,//因为客户端的bug,所以放大buf长度
};
typedefstruct _MisItemBuf
{
//这个buf存储了发送给客户端的字符信息串,客户端需要注意字符解析问题
CHARbuf[MISSIONCHARBUFLEN];
enumBufType
{
BufType_Int,
BufType_Str,
BufType_IntStr,
BufType_Money,
BufType_Item,
BufType_RandItem,
BufType_RadioItem,
BufType_Skill
}mType;
INTm_BufUse;
VOIDSetBufUse(INTNewBufUse){
m_BufUse += NewBufUse;
}
INTGetBufUse(void){
return m_BufUse;
}
INTGetBufMaxSize(){
return MISSIONCHARBUFLEN;
}
INTAddBuf(VOID*vpNewBuf, INTnNewBufLen){
if (m_BufUse+nNewBufLen > MISSIONCHARBUFLEN)
{
return 0;
}
memcpy(buf+m_BufUse, (VOID*)(vpNewBuf), nNewBufLen);
m_BufUse += nNewBufLen;
return m_BufUse;
}
}MisItemBuf[MISSIONBUFLEN];
MisItemBufBuff;
INTmUseNum;
}MisBuf;
而DispatchEventList则是解析这块MisBuf,根据每个缓冲块的类型(缓冲块内容是在AddText,AddNumText时加进去的),填充到一个ScriptParam_EventList包中:
ScriptParam_EventList paramEventList;
paramEventList.Reset();
paramEventList.m_idNPC = targetId;
paramEventList.AddItem(&itemEvent ); //填充
之后发给客户端:
Packets::GCScriptCommand Msg;
Msg.SetEventListResponseCmd( ¶mEventList );
Player* pPlayer = pHuman->GetPlayer();
pPlayer->SendPacket( &Msg ) ;
该包定义如下:
struct ScriptParam_EventList
{
ObjID_t m_idNPC;
BYTE m_yItemCount;
ScriptEventItem m_seiItem[MAX_EVENT_LIST_ITEM_COUNT];
VOID AddItem( const ScriptEventItem*pItem ){
if ( m_yItemCount< MAX_EVENT_LIST_ITEM_COUNT )
{
m_seiItem[m_yItemCount]= *pItem;
m_yItemCount++;
}
}
};
VOID SetEventListResponseCmd(const ScriptParam_EventList*pEventList ){
m_nCmdID =SCRIPT_COMMAND_EVENT_LIST_RESPONSE;
m_paramEnentList =*pEventList;
}
à Client: 解析Server发过来的这块MisBuf,生成NPC对话框。
GCScriptCommandHandler中:
CUIDataPool*pDataPool = (CUIDataPool*)(CGameProcedure::s_pDataPool);
SCommand_DPCcmdTemp;
cmdTemp.m_wID= DPC_SCRIPT_COMMAND;
cmdTemp.m_anParam[0]= pPacket->getCmdID();
cmdTemp.m_apParam[1]= pPacket->getBuf();
pDataPool->OnCommand_(&cmdTemp );
// DPC_SCRIPT_COMMAND是脚本指令
// D0 : script command id
// P1 : param list
// #define DPC_SCRIPT_COMMAND (6)
上面的OnCommand_ ( const SCommand_DPC*pCmd )中主要做了:
switch ( pCmd->m_wID)
{
case DPC_SCRIPT_COMMAND: //客户端自己填充的
{
INT nCmdID = pCmd->m_anParam[0]; //服务器设置过来的
VOID *pBuf = pCmd->m_apParam[1];
switch ( nCmdID )
{
case SCRIPT_COMMAND_EVENT_LIST_RESPONSE: // 事件列表返回
*m_pEventList = *((ScriptParam_EventList*)(pBuf));
OnEventListResponse();
break;
case SCRIPT_COMMAND_MISSION_RESPONSE: // 打开任务信息
*m_pMissionInfo = *((ScriptParam_MissionInfo*)(pBuf));
OnMissionInfoResponse();
break;
//任务需求信息(完成任务的文字描述,完成需要的物品,奖励的物品)
case SCRIPT_COMMAND_MISSION_DEMAND_RESPONSE:// 任务需求的查询返回
*m_pMissionDemandInfo = *((ScriptParam_MissionDemandInfo*)(pBuf));
OnMissionDemandInfoResponse();
break;
//任务继续信息(包含了奖励信息,在点击continue之后再去显示)
case SCRIPT_COMMAND_MISSION_CONTINUE_RESPONSE:// 任务的继续按钮事件返回
*m_pMissionContinueInfo = *((ScriptParam_MissionContinueInfo*)(pBuf));
OnMissionContinueInfoResponse();
break;
//任务完成情况提示信息
case SCRIPT_COMMAND_MISSION_TIPS:// 任务提示
*m_pMissionTips = *((ScriptParam_MissionTips*)(pBuf));
OnMissionTips();
break;
//技能学习信息
case SCRIPT_COMMAND_SKILL_STUDY:// 技能信息
m_pSkillStudy->Reset();
*m_pSkillStudy = *((ScriptParam_SkillStudy*)(pBuf));
OnSkillStudy();
break;
default:
break;
}
}
break;
case DPC_UPDATE_MISSION_LIST://名字似乎不再合适了
{
UINT dwObjID = pCmd->m_adwParam[0];
UINT dwModifyFlags= pCmd->m_adwParam[1];
_OWN_MISSION *paMissionBuf = (_OWN_MISSION*)(pCmd->m_apParam[2]);
UINT i;
CDetailAttrib_Player* playData= const_cast<CDetailAttrib_Player*>(CUIDataPool::GetMe()->GetMySelfDetailAttrib());
_OWN_MISSION *pMission;
for ( i = 0; i < MAX_CHAR_MISSION_NUM;i++ )
{
if ( dwModifyFlags& (0x00000001 << i) )
{
pMission = &(playData->m_listMission.m_aMission[i]);
memcpy( (void*)pMission, &paMissionBuf[i], sizeof(_OWN_MISSION) );
CGAskMissionDesc msg;
msg.setMissionIndex(pMission->m_idMission);
CNetManager::GetMe()->SendPacket( &msg);
}
}
}
break;
case DPC_UPDATE_MISSION_ADD:
{
_OWN_MISSION *pMission = (_OWN_MISSION*)(pCmd->m_apParam[0]);
m_pPlayerData->AddMission(pMission );
CGAskMissionDesc msg;
msg.setMissionIndex(pMission->m_idMission);
CNetManager::GetMe()->SendPacket( &msg);
rcResult = RC_OK;
}
break;
case DPC_UPDATE_MISSION_REMOVE:
{
MissionID_t idMission= (MissionID_t)(pCmd->m_adwParam[0]);
m_pPlayerData->RemoveMission(idMission );
rcResult = RC_OK;
}
break;
这时候就会执行OnEventListResponse,里面会调用:
CEventSystem::GetMe()->PushEvent(GE_QUEST_EVENTLIST, pNPC? pNPC->GetID() : -1);
客户端会执行相应的脚本:
--=========================================================
-- 事件处理
--=========================================================
functionQuest_OnEvent(event)
local objCared = tonumber(arg0);
--第一次和npc对话,得到npc所能激活的操作
if(event == "QUEST_EVENTLIST") then
--关心NPC
BeginCareObject_Quest(objCared)
this:Show();
QuestGreeting_Desc:ClearAllElement();
Quest_EventListUpdate();
--在接任务时,看到的任务信息
elseif(event == "QUEST_INFO")then
--关心NPC
BeginCareObject_Quest(objCared)
this:Show();
QuestGreeting_Desc:ClearAllElement();
Quest_QuestInfoUpdate()
--接受任务后,再次和npc对话,所得到的任务需求信息,(任务完成)
elseif(event =="QUEST_CONTINUE_DONE") then
QuestGreeting_Desc:ClearAllElement();
Quest_MissionContinueUpdate(1);
--接受任务后,再次和npc对话,所得到的任务需求信息,(任务未完成)
elseif(event =="QUEST_CONTINUE_NOTDONE") then
QuestGreeting_Desc:ClearAllElement();
Quest_MissionContinueUpdate(0);
--关心NPC
BeginCareObject_Quest(objCared)
--点击“继续之后”,奖品选择界面
elseif(event =="QUEST_AFTER_CONTINUE") then
--关心NPC
this:Show();
QuestGreeting_Desc:ClearAllElement();
Quest_MissionRewardUpdate();
end
这里的Quest_EventListUpdate负责显示任务列表:
for i=1,nEventListNum do
local strType,strState,strScriptId,strExtra,strTemp= DataPool:GetNPCEventList_Item(i-1);
if(strType == "text") then
QuestGreeting_Desc:AddTextElement(strTemp);
elseif(strType == "id") then
strTemp = strTemp .. "#"
strTemp = strTemp .. strScriptId
strTemp = strTemp .. ","
strTemp = strTemp .. strExtra
strTemp = strTemp .. "$"
strTemp = strTemp .. strState
if(tonumber(strState) == 1 ortonumber(strState) == -1) then
canacceptArr[k] = strTemp;
k = k+1;
elseif(tonumber(strState) == 2)then
cansubmitArr[j] = strTemp;
j = j+1;
else
QuestGreeting_Desc:AddOptionElement(strTemp);
end
end
end
上面就是根据具体类型(是普通文本还是按钮(按钮上绑定脚本号))添加到NPC对话面板中去。ComplexWindow::AddOptionElement(strTmep)对strTmep按照"#"、","、"$"解析完毕后,内部又调用了:
void FalagardComplexWindow::AddChildElement_Option(String strText,StringstrExtra1,String strExtra2, String strText3, FontBase* font)
{
ChildElement_Option* pNewChild = new ChildElement_Option;//动态创建窗口
pNewChild->d_Type= CT_Option;
pNewChild->d_Text= strText;
if(font) pNewChild->d_Font= font;
else pNewChild->d_Font = getFont();
char szTempName[32];
int nIndex = 0;
for(int i=0; i<(int)d_ChildVector.size(); i++)
{
if(d_ChildVector[i]->d_Name.substr(0, 10) == (utf8*)"__option__")
{
nIndex++;
}
}
_snprintf(szTempName, 32, "__option__%02d#%d,%d", nIndex,atoi(strExtra1.c_str()),atoi(strExtra2.c_str()));
pNewChild->d_Name= (utf8*)szTempName; //作为窗口名字
pNewChild->d_State= atoi(strText3.c_str());
switch( pNewChild->d_State)
{
case 1:
pNewChild->d_Button= static_cast<FalagardButton*>(WindowManager::getSingleton().createWindow((utf8*)"TLBB_QuestButton_1", getName() + szTempName));//
break;
case 2:
pNewChild->d_Button= static_cast<FalagardButton*>(WindowManager::getSingleton().createWindow((utf8*)"TLBB_QuestButton_2", getName() + szTempName));//
break;
default:
pNewChild->d_Button= static_cast<FalagardButton*>(WindowManager::getSingleton().createWindow((utf8*)"TLBB_QuestButton_1", getName() + szTempName));//
break;
}
pNewChild->d_Button->setText(" "+strText);
pNewChild->d_Button->show();
pNewChild->d_Button->subscribeEvent(PushButton::EventClicked, //注册事件响应函数Event::Subscriber(&FalagardComplexWindow::handleOptionClicked, this));
//设为子窗口
d_ParentWindow->addChildWindow(pNewChild->d_Button);
d_ChildVector.push_back(pNewChild);
performWindowLayout();
}
所以点击对话框面板上的按钮时,就会调用:
bool FalagardComplexWindow::handleOptionClicked(constEventArgs& e)
{
WindowEventArgs arg( ((constWindowEventArgs&) e).window);
fireEvent(EventOptionClicked, arg,EventNamespace);
return true;
}
/*************************************************************************
Fire / Trigger anevent
*************************************************************************/
voidEventSet::fireEvent(const String& name, EventArgs& args, constString& eventNamespace)
{
// handle globalevents
GlobalEventSet::getSingleton().fireEvent(name, args, eventNamespace);
// handle localevent
fireEvent_impl(name, args);
}
voidEventSet::fireEvent_impl(const String& name, EventArgs& args)
{
// find eventobject
Event* ev = getEventObject(name);
// fire the event if present and set is notmuted
if ((ev != 0) && !d_muted)
(*ev)(args);
}
除了AddChildElement_Option,还有AddChildElement_Item、AddChildElement_Action、AddChildElement_HyperLink、AddChildElement_Money等函数。
(3) 客户端点击按钮选项
à Client:客户端点击对话面板中按钮,就会执行:
FalagardComplexWindow::handleOptionClicked,最终执行(*ev)(args),见上面分析。
即:boolScriptFunctor::operator()(const EventArgs& e) const
{
ScriptModule* scriptModule = System::getSingleton().getScriptingModule();
if (scriptModule)
{
return scriptModule->executeScriptedEventHandler(scriptFunctionName,e);
}
}
天龙里已经对虚的加以下面实现:
bool CGameUIScript::executeScriptedEventHandler(const String&strHandle, const EventArgs& e)
{
const WindowEventArgs& eWindow =(const WindowEventArgs&)e;
Window* pWindow = eWindow.window;
g_theUIEventArg.m_pEventArg = &e;
do
{
if(!pWindow) break;
void* pUserData =pWindow->getUserData();
if(pUserData)
{
((CUIWindowItem*)pUserData)->FireUIEvent(strHandle.c_str(), eWindow.window);
break;
}
…
}
VOID CUIWindowItem::FireUIEvent(LPCTSTR szEventHandle, CEGUI::Window*pWindow)
{
//设置当前全局参数arg0为当前选择的窗口名字
g_pScriptSys->GetLuaState()->GetGlobals().SetString("arg0",pWindow->getName().c_str());
//即:执行脚本函数:QuestOption_Clicked
m_pScriptEnv->DoString(szEventHandle);
}
那么上面的scriptFunctionName是什么时候被赋值成QuestOption_Clicked的呢?
是在解析XML文件时候被赋值的:
// handle event subscription element
else if(element == EventElement)
{
String eventName(attributes.getValueAsString(EventNameAttribute));
String functionName(attributes.getValueAsString(EventFunctionAttribute));
// attempt tosubscribe property on window
try
{
if (!d_stack.empty())
{
d_stack.back()->subscribeEvent(eventName,ScriptFunctor(functionName));
}
}
… …
}
上面的FalagardComplexWindow类型控件:QuestGreeting_Desc已经注册了Click事件的响应函数QuestOption_Clicked了,这样上面的functionName就是QuestOption_Clicked啦。
--=========================================================
-- 选择一个任务
--=========================================================
function QuestOption_Clicked()
-- arg0的格式是
-- QuestGreeting_option_03#211207,0
pos1,pos2 =string.find(arg0,"#");
pos3,pos4 =string.find(arg0,",");
local strOptionID = -1;
local strOptionExtra1 = string.sub(arg0,pos2+1,pos3-1 );
local strOptionExtra2 = string.sub(arg0,pos4+1);
QuestFrameOptionClicked(tonumber(strOptionID),tonumber(strOptionExtra1),tonumber(strOptionExtra2));
end
QuestFrameOptionClicked函数里面会调用SendSelectEvent(nIndex,nExIndex1,nExIndex2); SendSelectEvent会根据其参数去客户端的事件列表中查询,返回对应的事件,然后发包:
CGEventRequestmsg;
msg.setExIndex( pItem->m_index);
msg.setScriptID(pItem->m_idScript );
msg.setNPCID(m_pEventList->m_idNPC );
CNetManager::GetMe()->SendPacket(&msg );
à Server:根据NpcID,脚本号,还有按钮索引,PushCommand。
ObjID_t idObj = pHuman->GetID() ;
ObjID_t idNPC = pPacket->getNPCID(); // NPC ID
ScriptID_t idScript =pPacket->getScriptID(); //脚本号
INT exIndex = pPacket->getExIndex(); //按钮索引
pHuman->GetHumanAI()->PushCommand_EventRequest(idNPC, idScript,exIndex),此函数中主要调用了:
pCharacter->getScene()->GetLuaInterface()->OnEventRequest(
idScript, pCharacter->GetID(), pNPC->GetID(),idEvent, exIndex ) ;
其中就会调用到脚本函数,例如:
--**********************************
--事件列表选中一项(NPC的Obj脚本)
--**********************************
functionx002045_OnEventRequest(sceneId, selfId, targetId, eventId )
if GetNumText()==0 then
ifIsHaveMission(sceneId,selfId,4021) > 0 then
BeginEvent(sceneId)
AddText(sceneId,"你有漕运货舱在身,我们驿站不能为你提供传送服务。");
EndEvent(sceneId)
DispatchEventList(sceneId,selfId,targetId)
else
CallScriptFunction((400900),"TransferFunc",sceneId, selfId, 17,94,149)
end
else
for i, findId inx002045_g_eventList do ---遍历NPC的Event脚本
if eventId == findId then
CallScriptFunction(eventId, "OnDefaultEvent",sceneId, selfId, targetId )
return
end
end
end
end
(4) 点击“接受任务”按钮
à Client:Lua_QuestFrameAcceptClicked
VOIDCUIDataPool::SendAcceptEvent()
{
CGMissionAccept msg;
msg.setScriptID(m_pMissionInfo->m_idScript );
msg.setNPCID( m_pMissionInfo->m_idNPC);
CNetManager::GetMe()->SendPacket(&msg );
}
4永生任务系统流程
(1) 客户端点击NPC
à Client:
UBI_CCSDefaultDialogPacket
à Server:
CSDefaultDialogHandler -> 调用脚本
--sceneId表示场景号,selfid玩家,targetId为NPC
function x000001_OnDefaultEvent(sceneId, selfHighId, selfLowId, targeHighId, targeLowId )
BeginAddEvent(sceneId)
AddEvent(sceneId, -1, 2, string4)
AddEvent(sceneId, -1, 13, string5)
EndAddEvent(sceneId)
DispatchEventList( sceneId, selfHighId,selfLowId, targeHighId, targeLowId )
return 1
end
其中DispatchEventList中又会发包:SCNPCEventListPacket
à Client:
SCNPCEventListExecute,
pNPCDialogData->SetEventList(pSCNPCEventListPacket->GetNPCEvent(i),i);
pEventSystem->PushEvent(GAME_EVENT_SHOW_NPCDIALOG, vParam);
(2) 客户端点击按钮选项
客户端点击按钮后执行:UBI_CNPCDialogData::ClickEventButton。
(a) 打孔镶嵌等
à Client:
CSAskNpcEvent
à Server:
SCNpcEventInfo
à Client:
SCNpcEventInfoExecute
(b) 接受任务等
à Client:
CSAskNpcEvent
à Server:
NPCQuestData
à Client:
NPCQuestDataExecute
pEventSystem->PushEvent(GAME_EVENT_SHOW_QUEST, vParam);