HowTo Add a NPC Operation: Difference between revisions
|  Created page with "== Summary ==  # Create new Operation Class in npcclient/npcoperations # Create new Queue-Command in npcclient/networkmgr # Process Command in npcclient/npcmanager and execute..." | No edit summary | ||
| (13 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
| == Summary == | == Summary == | ||
| # Create new Operation Class in  | NOTE: In Work | ||
| # Create new Queue-Command in npcclient/networkmgr | |||
| # Create new Operation Class in npcoperations | |||
| # Add Operation to npcbehave | |||
| # Create new Queue-Command in networkmgr | |||
| # Modify npcmessages | |||
| # Process Command in npcmanager and execute desired action | |||
| == New Operation Class == | |||
| Example: LootOperation | |||
| src/npcclient/npcoperations.h | |||
| <code> | |||
|  <nowiki> | |||
| /** Loot will make the NPC loot specified items | |||
|  * | |||
|  *  This class is the implementation of the loot operations | |||
|  *  used in behavior scripts for NPCS. | |||
|  * | |||
|  *  Examples: <pre> | |||
|  *  \<loot type="all"     /\> | |||
|  *  \<loot type="weapons" /\> </pre> | |||
|  */ | |||
| class LootOperation : public ScriptOperation | |||
| { | |||
| protected: | |||
|     csString type; ///< Type of items to loot | |||
| public: | |||
|     LootOperation(): ScriptOperation("Loot") {}; | |||
|     virtual ~LootOperation() {}; | |||
|     virtual OperationResult Run(NPC* npc,bool interrupted); | |||
|     virtual bool Load(iDocumentNode* node); | |||
|     virtual ScriptOperation* MakeCopy(); | |||
| };</nowiki></code> | |||
| src/npcclient/npcoperations.cpp | |||
| <code> | |||
|  <nowiki> | |||
| bool LootOperation::Load(iDocumentNode *node) | |||
| { | |||
|     type = node->GetAttributeValue("type"); | |||
|     if(type.IsEmpty()) | |||
|         type = "all"; | |||
|     return true; | |||
| } | |||
| ScriptOperation* LootOperation::MakeCopy() | |||
| { | |||
|     LootOperation* op = new LootOperation; | |||
|     op->type = type; | |||
|     return op; | |||
| } | |||
| ScriptOperation::OperationResult LootOperation::Run(NPC *npc, bool interrupted) | |||
| { | |||
|     npcclient->GetNetworkMgr()->QueueLootCommand(npc->GetActor(), type); | |||
|     return OPERATION_COMPLETED; // Nothing more to do for this op. | |||
| }</nowiki></code> | |||
| == Add Operation to npcbehave == | |||
| Add new operation to Behavior::LoadScript(): | |||
| src/npcclient/npcbehave.cpp | |||
| <code> | |||
|  <nowiki> | |||
| bool Behavior::LoadScript(iDocumentNode *node,bool top_level) | |||
| { | |||
|     // [...] | |||
|         else if ( strcmp( node->GetValue(), "loot" ) == 0 ) | |||
|         { | |||
|             op = new LootOperation; | |||
|         } | |||
|    // [...] | |||
| }</nowiki></code> | |||
| == New Queue-Command == | |||
| src/npcclient/networkmgr.h | |||
| <code> | |||
|  <nowiki> | |||
|     /** | |||
|      * Send a command to loot selected target. | |||
|      */ | |||
|     void QueueLootCommand(gemNPCActor *entity, const csString& type);</nowiki></code> | |||
| src/npcclient/networkmgr.cpp | |||
| <code> | |||
|  <nowiki> | |||
| void NetworkManager::QueueLootCommand(gemNPCActor *entity, const csString& type) | |||
| { | |||
|     CheckCommandsOverrun(sizeof(uint8_t) + sizeof(uint32_t) + (type.Length()+1)); | |||
|     outbound->msg->Add((int8_t) psNPCCommandsMessage::CMD_LOOT); | |||
|     outbound->msg->Add(entity->GetEID().Unbox()); | |||
|     outbound->msg->Add(type); | |||
|     if(outbound->msg->overrun) | |||
|     { | |||
|         CS_ASSERT(!"NetworkManager::QueueLootCommand put message in overrun state!\n"); | |||
|     } | |||
|     cmd_count++; | |||
| }</nowiki></code> | |||
| == Modify npcmessages == | |||
| Add new CMD to PerceptionType: | |||
| src/common/net/npcmessages.h | |||
| <code> | |||
|  <nowiki> | |||
|     enum PerceptionType | |||
|     { | |||
|         // Commands go from superclient to server | |||
|         // [...] | |||
|         CMD_CONTROL, | |||
| 	CMD_LOOT, // new CMD for looting | |||
|         // Perceptions go from server to superclient | |||
|         PCPT_ANYRANGEPLAYER, | |||
|         // [...] | |||
|     };</nowiki></code> | |||
| Add new case to psNPCCOmmandsMessage::ToString(): | |||
| src/common/net/npcmessages.cpp | |||
| <code> | |||
|  <nowiki> | |||
| csString psNPCCommandsMessage::ToString(NetBase::AccessPointers * accessPointers) | |||
| { | |||
|     // [...] | |||
| 	    case psNPCCommandsMessage::CMD_LOOT: | |||
|             { | |||
|                 msgtext.Append("CMD_LOOT: "); | |||
|                 // Extract the data | |||
|                 EID entity_id = EID(msg->GetUInt32()); | |||
|                 csString type = msg->GetStr(); | |||
|                 // Make sure we haven't run past the end of the buffer | |||
|                 if(msg->overrun) | |||
|                 { | |||
|                     Debug2(LOG_SUPERCLIENT,msg->clientnum,"Received incomplete CMD_LOOT from NPC client %u.\n",msg->clientnum); | |||
|                     break; | |||
|                 } | |||
|                 msgtext.AppendFmt("EID: %u Type: %s", entity_id.Unbox(), type.GetData()); | |||
|                 break; | |||
|             } | |||
|             // [...] | |||
| }</nowiki></code> | |||
| == Process Command == | |||
| Add case for new Command in NPCManager::HandleCommandList(): | |||
| src/server/npcmanager.cpp | |||
| <code> | |||
|  <nowiki> | |||
| void NPCManager::HandleCommandList(MsgEntry* me,Client* client) | |||
| { | |||
|     // [...] | |||
|             case psNPCCommandsMessage::CMD_LOOT: | |||
|             { | |||
|                 EID entity_id = EID(list.msg->GetUInt32()); | |||
|                 csString type = list.msg->GetStr(); | |||
|                 Debug3(LOG_SUPERCLIENT, entity_id.Unbox(), "-->Got loot cmd: Entity %s to loot for %s\n", | |||
|                        ShowID(entity_id), type.GetData()); | |||
|                 // Make sure we haven't run past the end of the buffer | |||
|                 if(list.msg->overrun) | |||
|                 { | |||
|                     Debug2(LOG_SUPERCLIENT, entity_id.Unbox(), "Received incomplete CMD_LOOT from NPC client %u.\n", me->clientnum); | |||
|                     break; | |||
|                 } | |||
|                 gemActor* actor = dynamic_cast<gemActor*>(gemSupervisor->FindObject(entity_id)); | |||
|                 if(actor) | |||
|                 { | |||
|                     if(psserver->GetUserManager()->CheckTargetLootable(actor, NULL)) | |||
|                     { | |||
|                         psserver->GetUserManager()->LootMoney(actor, NULL); | |||
|                         psserver->GetUserManager()->LootItems(actor, NULL, type); | |||
|                     } | |||
|                     // TODO: Add inventory change perception to make npc put items into tribe's resources | |||
|                 } | |||
|                 else | |||
|                      Error1("NPC Client try to loot with no existing npc"); | |||
|                 break; | |||
|             } | |||
|         // [...] | |||
| }</nowiki></code> | |||
| == Test == | |||
| Create a behavior to test with: | |||
| Add new <behavior> and <react event> to AbstractTribesman in sc_npctypes.sql. | |||
| <code> | |||
|  <nowiki> | |||
| INSERT INTO sc_npctypes VALUES("109","AbstractTribesman","DoNothing,Move",0,"","","","","","1", | |||
| '<!-- Abstract base npc type for tribes --> | |||
| <!-- [...] --> | |||
| <behavior name="test_loot" > | |||
|   <loot type="all"/> | |||
| </behavior> | |||
| <react event="test_loot" behavior="test_loot" /> | |||
| <!-- [...] --> | |||
| ');</nowiki></code> | |||
| Reload the table: | |||
| <code>mysql -u planeshift -pplaneshift planeshift | |||
| mysql> source sc_npctypes.sql;</code> | |||
| On the  psclient click on a NPC Hunter or Miner (has to be one of these due to "AbstractTribesman") and execute these commands: | |||
| <code> | |||
| /debugnpc | |||
| /percept test_loot test_loot</code> | |||
| Depending on where you sent DEBUG Messages, you can either see them in psnpcclient or psserver. Make sure LOG_SUPERCLIENT is active (setlog LOG_SUPERCLIENT). | |||
| [[Category:Engine documents]] [[Category:NPCClient Design]] | [[Category:Engine documents]] [[Category:NPCClient Design]] | ||
Latest revision as of 16:35, 4 April 2013
Summary
NOTE: In Work
- Create new Operation Class in npcoperations
- Add Operation to npcbehave
- Create new Queue-Command in networkmgr
- Modify npcmessages
- Process Command in npcmanager and execute desired action
New Operation Class
Example: LootOperation
src/npcclient/npcoperations.h
/** Loot will make the NPC loot specified items
 *
 *  This class is the implementation of the loot operations
 *  used in behavior scripts for NPCS.
 *
 *  Examples: <pre>
 *  \<loot type="all"     /\>
 *  \<loot type="weapons" /\> </pre>
 */
class LootOperation : public ScriptOperation
{
protected:
    csString type; ///< Type of items to loot
public:
    LootOperation(): ScriptOperation("Loot") {};
    virtual ~LootOperation() {};
    virtual OperationResult Run(NPC* npc,bool interrupted);
    virtual bool Load(iDocumentNode* node);
    virtual ScriptOperation* MakeCopy();
};
src/npcclient/npcoperations.cpp
bool LootOperation::Load(iDocumentNode *node)
{
    type = node->GetAttributeValue("type");
    
    if(type.IsEmpty())
        type = "all";
    
    return true;
}
ScriptOperation* LootOperation::MakeCopy()
{
    LootOperation* op = new LootOperation;
    op->type = type;
    return op;
}
ScriptOperation::OperationResult LootOperation::Run(NPC *npc, bool interrupted)
{
    npcclient->GetNetworkMgr()->QueueLootCommand(npc->GetActor(), type);
    
    return OPERATION_COMPLETED; // Nothing more to do for this op.
}
Add Operation to npcbehave
Add new operation to Behavior::LoadScript():
src/npcclient/npcbehave.cpp
bool Behavior::LoadScript(iDocumentNode *node,bool top_level)
{
    // [...]
        else if ( strcmp( node->GetValue(), "loot" ) == 0 )
        {
            op = new LootOperation;
        }
   // [...]
}
New Queue-Command
src/npcclient/networkmgr.h
    /**
     * Send a command to loot selected target.
     */
    void QueueLootCommand(gemNPCActor *entity, const csString& type);
src/npcclient/networkmgr.cpp
void NetworkManager::QueueLootCommand(gemNPCActor *entity, const csString& type)
{
    CheckCommandsOverrun(sizeof(uint8_t) + sizeof(uint32_t) + (type.Length()+1));
    outbound->msg->Add((int8_t) psNPCCommandsMessage::CMD_LOOT);
    outbound->msg->Add(entity->GetEID().Unbox());
    outbound->msg->Add(type);
    if(outbound->msg->overrun)
    {
        CS_ASSERT(!"NetworkManager::QueueLootCommand put message in overrun state!\n");
    }
    cmd_count++;
}
Modify npcmessages
Add new CMD to PerceptionType:
src/common/net/npcmessages.h
    enum PerceptionType
    {
        // Commands go from superclient to server
        // [...]
        CMD_CONTROL,
	CMD_LOOT, // new CMD for looting
        // Perceptions go from server to superclient
        PCPT_ANYRANGEPLAYER,
        // [...]
    };
Add new case to psNPCCOmmandsMessage::ToString():
src/common/net/npcmessages.cpp
csString psNPCCommandsMessage::ToString(NetBase::AccessPointers * accessPointers)
{
    // [...]
	    case psNPCCommandsMessage::CMD_LOOT:
            {
                msgtext.Append("CMD_LOOT: ");
                // Extract the data
                EID entity_id = EID(msg->GetUInt32());
                csString type = msg->GetStr();
                // Make sure we haven't run past the end of the buffer
                if(msg->overrun)
                {
                    Debug2(LOG_SUPERCLIENT,msg->clientnum,"Received incomplete CMD_LOOT from NPC client %u.\n",msg->clientnum);
                    break;
                }
                
                msgtext.AppendFmt("EID: %u Type: %s", entity_id.Unbox(), type.GetData());
                break;
            }
            // [...]
}
Process Command
Add case for new Command in NPCManager::HandleCommandList():
src/server/npcmanager.cpp
void NPCManager::HandleCommandList(MsgEntry* me,Client* client)
{
    // [...]
            case psNPCCommandsMessage::CMD_LOOT:
            {
                EID entity_id = EID(list.msg->GetUInt32());
                csString type = list.msg->GetStr();
                Debug3(LOG_SUPERCLIENT, entity_id.Unbox(), "-->Got loot cmd: Entity %s to loot for %s\n",
                       ShowID(entity_id), type.GetData());
                // Make sure we haven't run past the end of the buffer
                if(list.msg->overrun)
                {
                    Debug2(LOG_SUPERCLIENT, entity_id.Unbox(), "Received incomplete CMD_LOOT from NPC client %u.\n", me->clientnum);
                    break;
                }
                
                gemActor* actor = dynamic_cast<gemActor*>(gemSupervisor->FindObject(entity_id));
                
                if(actor)
                {
                    if(psserver->GetUserManager()->CheckTargetLootable(actor, NULL))
                    {
                        psserver->GetUserManager()->LootMoney(actor, NULL);
                        psserver->GetUserManager()->LootItems(actor, NULL, type);
                    }
                    // TODO: Add inventory change perception to make npc put items into tribe's resources
                }
                else
                     Error1("NPC Client try to loot with no existing npc");
                
                break;
            }
        // [...]
}
Test
Create a behavior to test with:
Add new <behavior> and <react event> to AbstractTribesman in sc_npctypes.sql.
INSERT INTO sc_npctypes VALUES("109","AbstractTribesman","DoNothing,Move",0,"","","","","","1",
'<!-- Abstract base npc type for tribes -->
<!-- [...] -->
<behavior name="test_loot" >
  <loot type="all"/>
</behavior>
<react event="test_loot" behavior="test_loot" />
<!-- [...] -->
');
Reload the table:
mysql -u planeshift -pplaneshift planeshift
mysql> source sc_npctypes.sql;
On the psclient click on a NPC Hunter or Miner (has to be one of these due to "AbstractTribesman") and execute these commands:
/debugnpc
/percept test_loot test_loot
Depending on where you sent DEBUG Messages, you can either see them in psnpcclient or psserver. Make sure LOG_SUPERCLIENT is active (setlog LOG_SUPERCLIENT).