在传奇类游戏中,“打孔镶嵌”系统是提升装备属性的核心玩法。针对GOM引擎,如何通过脚本实现**单颗宝石同时镶嵌5个孔位**,并确保属性叠加与客户端显示正常?本文提供一套完整的数据库配置、脚本逻辑与客户端适配方案,附可直接使用的代码示例。
---
### 一、功能需求与实现逻辑
1. **核心目标**:
- 玩家使用“五孔宝石”道具后,自动为装备的5个孔位填充相同属性。
- 支持属性叠加计算(如攻击+5 *5孔=+25)。
- 客户端显示5个孔位图标与属性预览。
2. **技术链路**:
```mermaid
graph TD
A[宝石使用] --> B{装备是否有5孔}
B -->|是| C[遍历5个孔位]
C --> D[写入属性至Envir\GemData]
D --> E[刷新客户端属性]
B -->|否| F[提示打孔不足]
```
---
### 二、数据库配置
#### 1. **打孔道具(DBC/Items.DB)**
| 字段名 | 值 | 说明 |
|------------|----------------|---------------------|
| Idx | 1001 | 唯一ID |
| Name | 五孔宝石 | 道具名称 |
| StdMode | 2 | 类型:消耗品 |
| Source | @Embed5Hole | 使用触发脚本标签 |
#### 2. **宝石属性模板(Envir\GemData.txt)**
```ini
[五孔宝石]
AttrType1=5 ; 攻击
AttrValue1=5
AttrType2=6 ; 魔法
AttrValue2=5
MaxHole=5 ; 最大生效孔位
```
---
### 三、脚本实现
#### 1. **打孔检测与镶嵌逻辑(QFunction-0.txt)**
```lua
[@Embed5Hole]
#IF
CheckItemW 当前装备 1 ; 检查是否穿戴装备
CheckItemHoleCount 当前装备 >=5 ; 检查孔位是否≥5
#ACT
GetItemFieldValue 当前装备 Name <$STR(S1)>
For 5 0
SetHoleAttr <$STR(S1)> <$CURRENTHOLENUM> 五孔宝石
SetHoleState <$STR(S1)> <$CURRENTHOLENUM> 1
SendMsg 6 成功镶嵌第<$CURRENTHOLENUM>孔!
Break
#ELSEACT
SendMsg 6 镶嵌失败:装备需至少5个孔!
```
**参数说明**:
- `CheckItemHoleCount`:检测装备孔位数。
- `SetHoleAttr`:写入宝石属性至指定孔位。
- `SetHoleState`:标记孔位为已激活(1=激活,0=未激活)。
#### 2. **属性叠加计算(QManage.txt)**
```lua
[@OnCalcAttr]
#IF
CheckHoleAttrExist 当前装备 5 ; 检测攻击属性孔位
#ACT
GetHoleAttrValue 当前装备 5 <$STR(N1)>
CalcValue <$STR(N1)> *5 ; 5孔攻击总和
ChangeModeValue 5 + <$STR(N1)>
```
---
### 四、客户端适配
#### 1. **孔位图标显示**
- 在`Resources\Data`目录下添加孔位状态素材:
- `HoleState1.bmp`(未激活)
- `HoleState2.bmp`(已激活)
- 修改`UI配置器`,调整装备面板的孔位坐标:
```ini
[HolePos]
Pos1=30,120
Pos2=60,120
Pos3=90,120
Pos4=120,120
Pos5=150,120
```
#### 2. **属性预览浮窗**
在`Tips脚本`中添加多孔属性合并显示:
```lua
[@GetItemTips]
#IF
CheckHoleAttrExist <$CURRITEM> 5
#ACT
GetHoleTotalValue <$CURRITEM> 5 <$STR(N1)>
AddTextLine 攻击+<$STR(N1)>(五孔叠加)
```
---
### 五、高频问题解决方案
| **问题现象** | **原因** | **解决方案** |
|--------------------------|-------------------------|---------------------------------|
| 镶嵌后属性未生效 | Envir\GemData.txt未加载 | 重启M2Server或执行@ReloadGemData |
| 客户端孔位图标错位 | UI坐标配置错误 | 使用WIL编辑器调整HoleState素材位置 |
| 第五孔无法激活 | 脚本循环上限未覆盖 | 将For循环改为For 5 0 ~ 4 |
| 属性叠加计算错误 | CalcValue语法错误 | 改用数学表达式:CALCVAR N1 = N1 *5 |
---
### 六、进阶功能扩展
#### 1. **动态宝石属性**
根据玩家等级调整宝石数值:
```lua
[@Embed5Hole]
#IF
CheckLevel > 50
#ACT
UpdateGemData 五孔宝石 AttrValue1=10
```
#### 2. **孔位解锁任务**
添加孔位解锁NPC脚本:
```lua
[@UnlockHole]
#IF
CheckTask 孔位大师 1 ; 完成前置任务
#ACT
SetItemMaxHole 当前装备 +5
SendMsg 6 已解锁5个新孔位!
```
---
#### 结语
通过上述方案,开发者可在GOM引擎中实现“单宝石五孔镶嵌”的复杂逻辑。关键在于**属性模板与脚本循环的精准控制**,以及客户端UI的适配。建议测试阶段使用@Make命令批量生成测试道具,验证多线程压力下的数据一致性。
#### 1. 打孔和镶嵌功能概述
##### 什么是打孔?
打孔是指在装备上创建孔洞,以便玩家可以在这些孔洞中镶嵌宝石或特殊材料,从而提升装备的各项属性。
##### 什么是镶嵌?
镶嵌是指将特定的宝石或材料放入装备上的孔洞中,以增强装备的攻击力、防御力、生命值等属性。
#### 2. GOM引擎简介
##### GOM引擎特点
- **高效稳定**:GOM引擎以其高效的处理能力和稳定的运行表现著称。
- **易用性强**:GOM引擎提供了简洁明了的API接口,方便开发者进行二次开发。
- **功能全面**:支持多种游戏元素的添加,包括但不限于技能、怪物、地图等。
##### 支持自定义功能
GOM引擎允许开发者通过修改代码和配置文件来实现各种自定义功能,包括打孔和镶嵌系统。
#### 3. 实现打孔和镶嵌功能步骤
##### 步骤一:准备工作
确保你已经安装了GOM引擎,并且有一个基本的游戏框架搭建完成。此外,还需要准备好所有必要的客户端和服务器端文件。
##### 步骤二:配置数据库表
###### 创建装备孔洞数据表
首先,在数据库中创建一个新的表来存储装备孔洞的信息。
**创建`equipment_hole`表**
```sql
CREATE TABLE equipment_hole (
id INT AUTO_INCREMENT PRIMARY KEY,
equip_id INT NOT NULL, -- 装备ID
hole_count INT NOT NULL DEFAULT 0, -- 孔洞数量
FOREIGN KEY (equip_id) REFERENCES item_table(id)
);
```
###### 创建镶嵌石数据表
接下来,创建一个表来存储镶嵌石的信息。
**创建`gemstone`表**
```sql
CREATE TABLE gemstone (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
type INT NOT NULL, -- 镶嵌石类型
attribute_type INT NOT NULL, -- 属性类型(如攻击力、防御力)
attribute_value INT NOT NULL -- 属性值
);
```
###### 插入示例数据
插入一些示例数据以便进行测试。
**插入装备孔洞数据**
```sql
INSERT INTO equipment_hole (equip_id, hole_count) VALUES (1001, 5); -- 假设装备ID为1001的装备有5个孔洞
```
**插入镶嵌石数据**
```sql
INSERT INTO gemstone (name, type, attribute_type, attribute_value) VALUES ('力量之石', 1, 1, 10); -- 类型1表示力量石,属性类型1表示攻击力
INSERT INTO gemstone (name, type, attribute_type, attribute_value) VALUES ('智慧之石', 2, 2, 5); -- 类型2表示智慧石,属性类型2表示防御力
INSERT INTO gemstone (name, type, attribute_type, attribute_value) VALUES ('生命之石', 3, 3, 20); -- 类型3表示生命之石,属性类型3表示生命值
```
##### 步骤三:修改配置文件
###### 修改`item_config.txt`
在`config\item_config.txt`中添加镶嵌石的相关配置。
**item_config.txt**
```ini
[Gemstones]
GemstoneCount=3
Gemstone1=1|力量之石|1|1|10
Gemstone2=2|智慧之石|2|2|5
Gemstone3=3|生命之石|3|3|20
```
##### 步骤四:修改代码实现
###### 修改`item_handler.cpp`
在`src\item_handler.cpp`文件中添加处理打孔和镶嵌的逻辑。
**item_handler.cpp**
```cpp
#include "item_handler.h"
#include "character.h"
#include "database_manager.h"
#include "packet_builder.h"
void ItemHandler::CreateHoles(Character* character, int equipId, int holeCount)
{
DatabaseManager* dbManager = DatabaseManager::GetInstance();
std::string query = "INSERT INTO equipment_hole (equip_id, hole_count) VALUES (" + std::to_string(equipId) + ", " + std::to_string(holeCount) + ")";
if (!dbManager->Execute(query))
{
SystemLog::LogError("Failed to create holes for equipment [%d].", equipId);
return;
}
CPacketBuilder response(PACKET_TYPE_CREATE_HOLES_RESPONSE);
response.WriteInt(equipId);
response.WriteInt(holeCount);
character->SendPacket(response.Build());
SystemLog::LogInfo("Created %d holes for equipment [%d] for character [%d].", holeCount, equipId, character->GetId());
}
void ItemHandler::EmbedGemstone(Character* character, int equipId, int gemstoneId)
{
DatabaseManager* dbManager = DatabaseManager::GetInstance();
// Check if the equipment has enough holes
std::string checkQuery = "SELECT hole_count FROM equipment_hole WHERE equip_id = " + std::to_string(equipId);
MYSQL_RES* result = dbManager->Query(checkQuery.c_str());
if (!result || mysql_num_rows(result) == 0)
{
SystemLog::LogWarning("Equipment [%d] does not have any holes.", equipId);
CPacketBuilder response(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_NO_HOLES);
character->SendPacket(response.Build());
mysql_free_result(result);
return;
}
MYSQL_ROW row = mysql_fetch_row(result);
int holeCount = atoi(row[0]);
mysql_free_result(result);
if (holeCount <= 0)
{
SystemLog::LogWarning("Equipment [%d] does not have any available holes.", equipId);
CPacketBuilder response(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_NO_AVAILABLE_HOLES);
character->SendPacket(response.Build());
return;
}
// Deduct one hole
std::string updateQuery = "UPDATE equipment_hole SET hole_count = hole_count - 1 WHERE equip_id = " + std::to_string(equipId);
if (!dbManager->Execute(updateQuery))
{
SystemLog::LogError("Failed to update hole count for equipment [%d].", equipId);
CPacketBuilder response(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_DB_ERROR);
character->SendPacket(response.Build());
return;
}
// Embed gemstone into equipment
std::string insertQuery = "INSERT INTO embedded_gemstones (equip_id, gemstone_id) VALUES (" + std::to_string(equipId) + ", " + std::to_string(gemstoneId) + ")";
if (!dbManager->Execute(insertQuery))
{
SystemLog::LogError("Failed to embed gemstone [%d] into equipment [%d].", gemstoneId, equipId);
CPacketBuilder response(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_DB_ERROR);
character->SendPacket(response.Build());
return;
}
CPacketBuilder response(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_SUCCESS);
response.WriteInt(equipId);
response.WriteInt(gemstoneId);
character->SendPacket(response.Build());
SystemLog::LogInfo("Embedded gemstone [%d] into equipment [%d] for character [%d].", gemstoneId, equipId, character->GetId());
}
```
###### 修改`database_manager.cpp`
在`src\database_manager.cpp`文件中添加查询和更新数据库的逻辑。
**database_manager.cpp**
```cpp
#include "database_manager.h"
#include <mysql/mysql.h>
DatabaseManager* DatabaseManager::GetInstance()
{
static DatabaseManager instance;
return &instance;
}
DatabaseManager::DatabaseManager()
{
ConnectToDatabase();
}
bool DatabaseManager::ConnectToDatabase()
{
m_mysql = mysql_init(NULL);
if (!m_mysql)
{
SystemLog::LogError("Failed to initialize MySQL.");
return false;
}
if (!mysql_real_connect(m_mysql, "localhost", "your_username", "your_password", "your_database", 3306, NULL, 0))
{
SystemLog::LogError("Failed to connect to database: %s", mysql_error(m_mysql));
mysql_close(m_mysql);
return false;
}
SystemLog::LogInfo("Connected to database succesully.");
return true;
}
bool DatabaseManager::Execute(const std::string& query)
{
if (mysql_query(m_mysql, query.c_str()))
{
SystemLog::LogError("Failed to execute query: %s\n%s", query.c_str(), mysql_error(m_mysql));
return false;
}
return true;
}
MYSQL_RES* DatabaseManager::Query(const std::string& query)
{
if (mysql_query(m_mysql, query.c_str()))
{
SystemLog::LogError("Failed to execute query: %s\n%s", query.c_str(), mysql_error(m_mysql));
return NULL;
}
return mysql_store_result(m_mysql);
}
int DatabaseManager::GetLastInsertId()
{
return static_cast<int>(mysql_insert_id(m_mysql));
}
```
###### 修改`character.cpp`
在`src\character.cpp`文件中添加管理装备和镶嵌石的逻辑。
**character.cpp**
```cpp
#include "character.h"
#include "item_handler.h"
Character::Character(int id)
{
m_id = id;
}
void Character::UseItem(int itemId)
{
ItemHandler* itemHandler = ItemHandler::GetInstance();
switch (itemId)
{
case ITEM_ID_CREATE_HOLE_STONE:
itemHandler->CreateHoles(this, m_equipmentId, 5); // 假设使用道具ID为ITEM_ID_CREATE_HOLE_STONE创建5个孔洞
break;
default:
DefaultItemUsage(itemId);
break;
}
}
void Character::EmbedGemstone(int equipId, int gemstoneId)
{
ItemHandler* itemHandler = ItemHandler::GetInstance();
itemHandler->EmbedGemstone(this, equipId, gemstoneId);
}
```
###### 修改`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::SendCreateHolesRequest(int equipId, int holeCount)
{
CPacketBuilder packet(PACKET_TYPE_CREATE_HOLES_REQUEST);
packet.WriteInt(equipId);
packet.WriteInt(holeCount);
SendPacketToGameServer(packet.Build());
}
void CClientNetwork::SendEmbedGemstoneRequest(int equipId, int gemstoneId)
{
CPacketBuilder packet(PACKET_TYPE_EMBED_GEMSTONE_REQUEST);
packet.WriteInt(equipId);
packet.WriteInt(gemstoneId);
SendPacketToGameServer(packet.Build());
}
void CClientNetwork::HandleCreateHolesResponse(const Packet& packet)
{
int equipId = packet.ReadInt();
int holeCount = packet.ReadInt();
SystemLog::LogInfo("Created %d holes for equipment [%d].", holeCount, equipId);
// Update UI to show created holes
}
void CClientNetwork::HandleEmbedGemstoneResponse(const Packet& packet)
{
byte status = packet.ReadByte();
if (status == EMBED_GEMSTONE_SUCCESS)
{
int equipId = packet.ReadInt();
int gemstoneId = packet.ReadInt();
SystemLog::LogInfo("Embedded gemstone [%d] into equipment [%d].", gemstoneId, equipId);
// Update UI to show embedded gemstone
}
else
{
SystemLog::LogWarning("Failed to embed gemstone.");
}
}
```
##### 步骤五:编译并测试
确保所有修改后的代码都能成功编译。
**编译服务器端**
```sh
g++ -o game_server src/game_server.cpp src/database_manager.cpp src/item_handler.cpp src/packet_builder.cpp -lengine
```
**编译客户端**
```sh
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
```
#### 4. 日志文件检查
##### 查看游戏服务器日志
打开游戏服务器的日志文件(通常位于`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: Created 5 holes for equipment [1001] for character [1].
[2023-10-01 12:34:56] INFO: Embedded gemstone [1] into equipment [1001] for character [1].
```
根据日志中的信息,确认游戏服务器是否正常运行以及打孔和镶嵌操作是否正确执行。
##### 查看客户端日志
打开客户端的日志文件(通常位于`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: Created 5 holes for equipment [1001].
[2023-10-01 12:34:56] INFO: Embedded gemstone [1] into equipment [1001].
```
根据日志中的信息,确认客户端是否正确接收了服务器的响应并且显示了相应的结果。
#### 5. 常见问题及解决方案
##### 问题一:无法连接到游戏服务器
- **检查网络设置**:确保客户端和游戏服务器之间的网络连接正常。
- **检查配置文件**:确保`client_config.txt`中的游戏服务器IP和端口配置正确。
- **检查防火墙设置**:确保防火墙没有阻止游戏服务器的端口。
##### 问题二:登录失败
- **检查数据库配置**:确保`auth_config.txt`中的数据库配置正确。
- **检查数据库服务**:确保数据库服务正在运行并且可以访问。
- **检查用户数据**:确保`account_table`中包含正确的用户信息。
##### 问题三:角色加载失败
- **检查角色数据**:确保`char_table`中包含正确的角色信息。
- **检查物品数据**:确保`item_table`中包含正确的物品信息。
- **检查技能数据**:确保`skill_table`中包含正确的技能信息。
##### 问题四:客户端版本不匹配
- **更新客户端**:确保客户端版本与服务器版本兼容。
- **同步资源文件**:确保客户端和服务器之间的资源文件一致。
##### 问题五:打孔失败
- **检查装备ID**:确保提供的装备ID存在于数据库中。
- **检查权限**:确保角色具有足够的权限进行打孔操作。
- **检查日志文件**:查看日志文件以确定是否有打孔失败的记录。
##### 问题六:镶嵌失败
- **检查装备孔洞**:确保装备上有可用的孔洞。
- **检查镶嵌石**:确保提供的镶嵌石ID存在于数据库中。
- **检查日志文件**:查看日志文件以确定是否有镶嵌失败的记录。
##### 问题七:数据库连接失败
- **检查数据库配置**:确保`game_config.txt`中的数据库配置正确。
- **检查数据库服务**:确保数据库服务正在运行并且可以访问。
- **检查网络设置**:确保服务器能够访问数据库所在的主机。
#### 6. 总结
通过以上步骤,你应该能够在GOM传奇引擎中成功实现一个石头镶嵌5个孔的功能。这不仅增加了游戏的策略性和深度,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!
传奇GOM引擎中实现打孔和镶嵌系统,支持一个石头镶嵌5个孔
来源:
作者:
点击:

