在单机传奇开发中,“宝宝召唤卷”是提升玩家体验的核心道具之一。通过GOM引擎的脚本与数据库协作,开发者可实现“使用卷轴召唤专属宠物”的功能。本文将从物品创建、脚本逻辑到怪物配置,手把手解析实现流程,并解决常见异常问题。
---
### 一、功能定义与实现逻辑
#### 1. **核心需求**
- 玩家右键点击“宝宝召唤卷”后,召唤指定怪物(如神兽、骷髅等)辅助战斗。
- 限制同时召唤的宝宝数量(如最多1只)。
- 宝宝死亡或玩家下线后自动消失。
#### 2. **实现链路**
```mermaid
graph LR
A[创建物品] --> B[数据库配置]
B --> C[编写使用脚本]
C --> D[设置怪物属性]
D --> E[测试与调试]
```
---
### 二、数据库配置:新增召唤卷物品
#### **步骤1:在DBC/数据库中添加物品**
以Access数据库为例,打开**Items.DB**,新增一行并配置关键字段:
| 字段名 | 值 | 说明 |
|--------------|----------------|-------------------------------------|
| Idx | 888 | 物品唯一编号(需确保不与现有物品冲突) |
| Name | 神兽召唤卷 | 物品显示名称 |
| StdMode | 31 | 表示可使用的物品类型 |
| Shape | 1 | 物品外观(需对应客户端的Weapon.wil资源) |
| Weight | 1 | 物品重量 |
| AniCount | 3 | 使用后的动作效果(如闪光) |
| Source | @SummonBaby | 关键!指定使用物品时触发的脚本标签 |
---
### 三、脚本编写:实现召唤逻辑
#### **步骤1:在QFunction-0.txt中添加脚本**
```lua
[@SummonBaby]
#IF
CHECKCALLMOB = 0 -- 检测当前无召唤物
#ACT
GiveMob 神兽 1 240 0 -- 召唤怪物名称为“神兽”,等级1,持续240分钟
SendMsg 6 召唤成功!神兽已与你并肩作战!
Break
#ELSEACT
SendMsg 6 已有宝宝在战斗,无法重复召唤!
Break
```
#### **参数详解**:
- **GiveMob**:GOM引擎的召唤命令,参数依次为怪物名称、等级、持续时间(分钟)、是否主动攻击(0=跟随,1=主动)。
- **CHECKCALLMOB**:检测当前召唤物数量,需与引擎版本匹配(部分版本需用`CHECKCALLMOBCOUNT`)。
---
### 四、怪物配置:定义宝宝属性
#### **步骤1:在Monster.DB中创建宝宝数据**
| 字段名 | 值 | 说明 |
|--------------|----------------|-------------------------------------|
| Name | 神兽 | 怪物名称(需与脚本中的名称一致) |
| Race | 81 | 怪物类型(81=宝宝类,不会主动攻击玩家) |
| Life | 5000 | 宝宝生命值 |
| AC | 50-100 | 防御力范围 |
| MAC | 30-60 | 魔法防御力 |
| DC | 100-200 | 物理攻击力 |
| Speed | 80 | 移动速度(默认80,值越小移动越快) |
| Exp | 0 | 击杀后不提供经验 |
| Undead | 1 | 1=死亡后不爆尸体 |
---
### 五、进阶功能:自定义宝宝行为
#### **1. 宝宝技能与成长**
在**QManage.txt**中添加定时器,实现宝宝升级:
```lua
[@OnTimer10]
#IF
CHECKCALLMOB = 1
#ACT
CHANGEMOBLEVEL +1
SendMsg 6 宝宝等级提升至:<$CALLMOBLEVEL>级!
```
#### **2. 死亡后触发事件**
在**QMonsterDie.txt**中绑定死亡逻辑:
```lua
[@神兽_Death]
#ACT
SendMsg 6 你的神兽已战死,60秒后自动复活!
DelayCall 60000 @ReviveBaby
[@ReviveBaby]
#ACT
GiveMob 神兽 1 240 0
```
---
### 六、高频问题与解决方案
#### **问题1:使用卷轴无反应**
- **原因**:物品的`Source`字段未正确绑定脚本标签。
- **解决**:检查数据库中的`Source`是否与脚本中的`[@SummonBaby]`一致。
#### **问题2:宝宝不跟随玩家**
- **排查**:
1. 确认怪物的`Race`字段是否为81(跟随模式)。
2. 检查`GiveMob`最后一个参数是否为0。
#### **问题3:召唤数量不受控**
- **优化脚本**:在召唤前增加更严格的检测:
```lua
#IF
CHECKCALLMOB >= 1
CHECKITEM 神兽召唤卷 1
#ACT
Take 神兽召唤卷 1
SendMsg 6 召唤失败:已有宝宝存在!
```
---
### 七、测试与调试技巧
1. **日志监控**:在M2Server控制台中输入`@ViewCallMob`,实时查看召唤物状态。
2. **GM命令辅助**:使用`@Make 神兽召唤卷 1`快速获取道具测试。
3. **客户端同步**:确保客户端的`Monster.wil`包含“神兽”的外观资源。
---
#### 结语
通过数据库、脚本与怪物属性的三方协作,GOM引擎可灵活实现“宝宝召唤卷”功能。开发者需重点关注脚本命令的兼容性(如GiveMob在不同引擎版本中的差异)与怪物行为的精细化控制。进阶玩法中,可结合AI脚本(如宝宝自动释放技能)或动态成长系统,大幅提升道具的可玩性。
#### 1. 准备工作
在开始之前,请确保你已经安装了GOM引擎,并且有一个基本的游戏框架搭建完成。此外,还需要准备好所有必要的客户端和服务器端文件。
#### 2. 理解宝宝召唤卷的功能
##### 宝宝召唤卷功能概述
宝宝召唤卷是一种特殊的道具,当玩家使用该道具时,可以在游戏中召唤出一个特定的宝宝来辅助战斗。这个宝宝通常拥有独特的技能和属性,能够帮助玩家更好地应对各种挑战。
#### 3. 数据库配置
##### 步骤一:创建宝宝数据表
首先,在数据库中创建一个新的表来存储宝宝的数据信息。
**创建宝宝表**
```sql
CREATE TABLE baby_table (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
level INT NOT NULL,
hp INT NOT NULL,
sp INT NOT NULL,
str INT NOT NULL,
dex INT NOT NULL,
intel INT NOT NULL,
con INT NOT NULL,
skill_id INT NOT NULL,
skill_level INT NOT NULL
);
```
##### 步骤二:插入示例宝宝数据
插入一些示例数据以便进行测试。
**插入宝宝数据**
```sql
INSERT INTO baby_table (name, level, hp, sp, str, dex, intel, con, skill_id, skill_level)
VALUES ('小虎', 1, 100, 50, 10, 10, 10, 10, 1, 1);
INSERT INTO baby_table (name, level, hp, sp, str, dex, intel, con, skill_id, skill_level)
VALUES ('小白龙', 1, 150, 70, 15, 15, 15, 15, 2, 1);
```
##### 步骤三:创建物品数据表
在`item_table`中添加宝宝召唤卷的相关数据。
**插入宝宝召唤卷数据**
```sql
INSERT INTO item_table (id, name, type, description, effect_type, effect_value)
VALUES (9999, '宝宝召唤卷', 1, '使用后可召唤一个小虎宝宝', 1, 1); -- 效果类型1表示召唤宝宝,效果值1对应小虎宝宝ID
INSERT INTO item_table (id, name, type, description, effect_type, effect_value)
VALUES (10000, '小白龙召唤卷', 1, '使用后可召唤一个小白龙宝宝', 1, 2); -- 效果类型1表示召唤宝宝,效果值2对应小白龙宝宝ID
```
#### 4. 修改代码实现
##### 步骤一:修改`item_handler.cpp`
在`src\item_handler.cpp`文件中添加处理宝宝召唤卷使用的逻辑。
**item_handler.cpp**
```cpp
#include "item_handler.h"
#include "character.h"
#include "baby_manager.h"
#include "packet_builder.h"
void ItemHandler::UseItem(Character* character, int itemId)
{
switch (itemId)
{
case 9999: // 小虎召唤卷
SummonBaby(character, 1);
break;
case 10000: // 小白龙召唤卷
SummonBaby(character, 2);
break;
default:
DefaultItemUsage(character, itemId);
break;
}
}
void ItemHandler::SummonBaby(Character* character, int babyId)
{
BabyManager* babyManager = BabyManager::GetInstance();
Baby* baby = babyManager->CreateBaby(babyId);
if (!baby)
{
CPacketBuilder response(PACKET_TYPE_SUMMON_BABY_RESPONSE);
response.WriteByte(SUMMON_BABY_FAILURE);
character->SendPacket(response.Build());
return;
}
character->AddBaby(baby);
CPacketBuilder response(PACKET_TYPE_SUMMON_BABY_RESPONSE);
response.WriteByte(SUMMON_BABY_SUCCESS);
baby->Serialize(response);
character->SendPacket(response.Build());
SystemLog::LogInfo("Character [%d] summoned baby [%s].", character->GetId(), baby->GetName().c_str());
}
```
##### 步骤二:创建`baby_manager.cpp`
在`src\baby_manager.cpp`文件中实现宝宝管理功能。
**baby_manager.cpp**
```cpp
#include "baby_manager.h"
#include "database_manager.h"
#include <map>
BabyManager* BabyManager::GetInstance()
{
static BabyManager instance;
return &instance;
}
BabyManager::BabyManager()
{
LoadBabiesFromDatabase();
}
Baby* BabyManager::CreateBaby(int babyId)
{
auto it = m_babies.find(babyId);
if (it != m_babies.end())
{
return new Baby(it->second);
}
return nullptr;
}
void BabyManager::LoadBabiesFromDatabase()
{
DatabaseManager* dbManager = DatabaseManager::GetInstance();
MYSQL_RES* result = dbManager->Query("SELECT * FROM baby_table");
if (!result)
{
SystemLog::LogError("Failed to load babies from database.");
return;
}
MYSQL_ROW row;
while ((row = mysql_fetch_row(result)))
{
int id = atoi(row[0]);
std::string name = row[1];
int level = atoi(row[2]);
int hp = atoi(row[3]);
int sp = atoi(row[4]);
int str = atoi(row[5]);
int dex = atoi(row[6]);
int intel = atoi(row[7]);
int con = atoi(row[8]);
int skillId = atoi(row[9]);
int skillLevel = atoi(row[10]);
Baby baby(id, name, level, hp, sp, str, dex, intel, con, skillId, skillLevel);
m_babies[id] = baby;
}
mysql_free_result(result);
SystemLog::LogInfo("Loaded %d babies from database.", m_babies.size());
}
```
##### 步骤三:创建`baby.cpp`
在`src\baby.cpp`文件中实现宝宝类的功能。
**baby.cpp**
```cpp
#include "baby.h"
Baby::Baby(int id, const std::string& name, int level, int hp, int sp, int str, int dex, int intel, int con, int skillId, int skillLevel)
{
m_id = id;
m_name = name;
m_level = level;
m_hp = hp;
m_sp = sp;
m_str = str;
m_dex = dex;
m_intel = intel;
m_con = con;
m_skillId = skillId;
m_skillLevel = skillLevel;
}
void Baby::Serialize(CPacketBuilder& packet)
{
packet.WriteInt(m_id);
packet.WriteString(m_name);
packet.WriteInt(m_level);
packet.WriteInt(m_hp);
packet.WriteInt(m_sp);
packet.WriteInt(m_str);
packet.WriteInt(m_dex);
packet.WriteInt(m_intel);
packet.WriteInt(m_con);
packet.WriteInt(m_skillId);
packet.WriteInt(m_skillLevel);
}
```
##### 步骤四:修改`character.cpp`
在`src\character.cpp`文件中添加管理宝宝的逻辑。
**character.cpp**
```cpp
#include "character.h"
#include "baby.h"
Character::Character(int id)
{
m_id = id;
}
void Character::AddBaby(Baby* baby)
{
m_babies.push_back(baby);
}
void Character::RemoveBaby(int babyId)
{
for (auto it = m_babies.begin(); it != m_babies.end(); ++it)
{
if ((*it)->GetId() == babyId)
{
delete *it;
m_babies.erase(it);
break;
}
}
}
void Character::SerializeBabies(CPacketBuilder& packet)
{
packet.WriteInt(static_cast<int>(m_babies.size()));
for (Baby* baby : m_babies)
{
baby->Serialize(packet);
}
}
```
##### 步骤五:修改`client_network.cpp`
在`src\client_network.cpp`文件中实现客户端与游戏服务器的通信。
**client_network.cpp**
```cpp
#include "client_network.h"
#include "packet_builder.h"
CClientNetwork::CClientNetwork()
{
m_authSocket = INVALID_SOCKET;
m_gameSocket = INVALID_SOCKET;
}
bool CClientNetwork::ConnectToAuthServer(const std::string& ip, int port)
{
m_authSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_authSocket == INVALID_SOCKET)
{
SystemLog::LogError("Failed to create socket: %d", WSAGetLastError());
return false;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
serverAddr.sin_port = htons(port);
if (connect(m_authSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
SystemLog::LogError("Failed to connect to auth server: %d", WSAGetLastError());
closesocket(m_authSocket);
return false;
}
SystemLog::LogInfo("Connected to auth server at %s:%d", ip.c_str(), port);
return true;
}
bool CClientNetwork::ConnectToGameServer(const std::string& ip, int port)
{
m_gameSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_gameSocket == INVALID_SOCKET)
{
SystemLog::LogError("Failed to create socket: %d", WSAGetLastError());
return false;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
serverAddr.sin_port = htons(port);
if (connect(m_gameSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{
SystemLog::LogError("Failed to connect to game server: %d", WSAGetLastError());
closesocket(m_gameSocket);
return false;
}
SystemLog::LogInfo("Connected to game server at %s:%d", ip.c_str(), port);
return true;
}
void CClientNetwork::SendLoginRequest(const std::string& username, const std::string& password)
{
CPacketBuilder packet(PACKET_TYPE_LOGIN_REQUEST);
packet.WriteString(username);
packet.WriteString(password);
SendPacketToAuthServer(packet.Build());
}
void CClientNetwork::SendPacketToAuthServer(const Packet& packet)
{
send(m_authSocket, reinterpret_cast<const char*>(packet.GetData()), packet.GetSize(), 0);
}
void CClientNetwork::SendPacketToGameServer(const Packet& packet)
{
send(m_gameSocket, reinterpret_cast<const char*>(packet.GetData()), packet.GetSize(), 0);
}
bool CClientNetwork::ReceivePacket(Packet& packet)
{
char buffer[MAX_PACKET_SIZE];
int bytesRead = recv(m_gameSocket, buffer, MAX_PACKET_SIZE, 0);
if (bytesRead <= 0)
{
SystemLog::LogWarning("Connection closed by server.");
return false;
}
packet.SetData(buffer, bytesRead);
return true;
}
void CClientNetwork::SendUseItemRequest(int itemId)
{
CPacketBuilder packet(PACKET_TYPE_USE_ITEM_REQUEST);
packet.WriteInt(itemId);
SendPacketToGameServer(packet.Build());
}
void CClientNetwork::HandleSummonBabyResponse(const Packet& packet)
{
byte status = packet.ReadByte();
if (status == SUMMON_BABY_SUCCESS)
{
Baby baby;
baby.Deserialize(packet);
AddBaby(baby);
SystemLog::LogInfo("Summoned baby [%s].", baby.GetName().c_str());
}
else
{
SystemLog::LogWarning("Failed to summon baby.");
}
}
```
##### 步骤六:编译并测试
确保所有修改后的代码都能成功编译。
```sh
g++ -o game_server src/game_server.cpp src/database_manager.cpp src/item_handler.cpp src/baby_manager.cpp src/packet_builder.cpp -lengine
g++ -o client src/client_main.cpp src/client_network.cpp src/packet_builder.cpp -lengine
```
启动登录服务器、游戏服务器和客户端,观察整个宝宝召唤卷流程是否顺畅。
```sh
start auth_server.exe
start game_server.exe
start client.exe
```
#### 5. 日志文件检查
##### 查看游戏服务器日志
打开游戏服务器的日志文件(通常位于`log\game_server.log`),查找相关的错误信息。
```plaintext
[2023-10-01 12:34:56] INFO: Game server started on port 2107.
[2023-10-01 12:34:56] INFO: Connected to database succesully.
[2023-10-01 12:34:56] INFO: Loaded 2 babies from database.
[2023-10-01 12:34:56] INFO: Player [1] requested to use item [9999].
[2023-10-01 12:34:56] INFO: Character [1] summoned baby [小虎].
```
根据日志中的信息,确认游戏服务器是否正常运行以及宝宝是否成功召唤。
##### 查看客户端日志
打开客户端的日志文件(通常位于`log\client.log`),查找相关的错误信息。
```plaintext
[2023-10-01 12:34:56] INFO: Connecting to auth server at 127.0.0.1:2106.
[2023-10-01 12:34:56] INFO: Connected to auth server at 127.0.0.1:2106.
[2023-10-01 12:34:56] INFO: Logging in as testuser.
[2023-10-01 12:34:56] INFO: Login succesul, account ID: 1.
[2023-10-01 12:34:56] INFO: Connecting to game server at 127.0.0.1:2107.
[2023-10-01 12:34:56] INFO: Connected to game server at 127.0.0.1:2107.
[2023-10-01 12:34:56] INFO: Requesting to use item [9999].
[2023-10-01 12:34:56] INFO: Summoned baby [小虎].
```
根据日志中的信息,确认客户端是否正确发送了请求以及服务器的响应。
#### 6. 常见问题及解决方案
##### 问题一:无法连接到游戏服务器
- **检查网络设置**:确保客户端和游戏服务器之间的网络连接正常。
- **检查配置文件**:确保`client_config.txt`中的游戏服务器IP和端口配置正确。
- **检查防火墙设置**:确保防火墙没有阻止游戏服务器的端口。
##### 问题二:登录失败
- **检查数据库配置**:确保`auth_config.txt`中的数据库配置正确。
- **检查数据库服务**:确保数据库服务正在运行并且可以访问。
- **检查用户数据**:确保`account_table`中包含正确的用户信息。
##### 问题三:角色加载失败
- **检查角色数据**:确保`char_table`中包含正确的角色信息。
- **检查物品数据**:确保`item_table`中包含正确的物品信息。
- **检查宝宝数据**:确保`baby_table`中包含正确的宝宝信息。
##### 问题四:客户端版本不匹配
- **更新客户端**:确保客户端版本与服务器版本兼容。
- **同步资源文件**:确保客户端和服务器之间的资源文件一致。
##### 问题五:宝宝召唤失败
- **检查宝宝数据表**:确保`baby_table`中包含正确的宝宝数据。
- **检查物品数据表**:确保`item_table`中包含正确的宝宝召唤卷数据。
- **检查日志文件**:查看日志文件以确定是否有宝宝召唤失败的记录。
#### 7. 总结
通过以上步骤,你应该能够在GOM引擎的单机传奇版本中成功添加宝宝召唤卷功能。这不仅增加了游戏的乐趣性和互动性,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!
GOM引擎单机传奇增设“宝宝召唤卷”全攻略:从脚本到数据库的实战指南
来源:
作者:
点击:

