传奇GOM引擎中实现打孔和镶嵌系统,支持一个石头镶嵌5个孔

来源: 作者: 点击:
在传奇类游戏中,“打孔镶嵌”系统是提升装备属性的核心玩法。针对GOM引擎,如何通过脚本实现**单颗宝石同时镶嵌5个孔位**,并确保属性叠加与客户端显示正常?本文提供一套完整的数据库配置、脚本逻辑与客户端适配方案,附可直接使用的代码示例。

---

###一、功能需求与实现逻辑
1.**核心目标**:
-玩家使用“五孔宝石”道具后,自动为装备的5个孔位填充相同属性。
-支持属性叠加计算(如攻击+5*5孔=+25)。
-客户端显示5个孔位图标与属性预览。

2.**技术链路**:
```mermaid
graphTD
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)>
For50
SetHoleAttr<$STR(S1)><$CURRENTHOLENUM>五孔宝石
SetHoleState<$STR(S1)><$CURRENTHOLENUM>1
SendMsg6成功镶嵌第<$CURRENTHOLENUM>孔!
Break
#ELSEACT
SendMsg6镶嵌失败:装备需至少5个孔!
```


**参数说明**:
-`CheckItemHoleCount`:检测装备孔位数。
-`SetHoleAttr`:写入宝石属性至指定孔位。
-`SetHoleState`:标记孔位为已激活(1=激活,0=未激活)。

####2.**属性叠加计算(QManage.txt)**
```lua
[@OnCalcAttr]
#IF
CheckHoleAttrExist当前装备5;检测攻击属性孔位
#ACT
GetHoleAttrValue当前装备5<$STR(N1)>
CalcValue<$STR(N1)>*5;5孔攻击总和
ChangeModeValue5+<$STR(N1)>
```


---

###四、客户端适配

####1.**孔位图标显示**
-在`Resources\Data`目录下添加孔位状态素材:
-`HoleState1.bmp`(未激活)
-`HoleState2.bmp`(已激活)
-修改`UI配置器`,调整装备面板的孔位坐标:
```ini
[HolePos]
Pos1=30120
Pos2=60120
Pos3=90120
Pos4=120120
Pos5=150120
```


####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循环改为For50~4|
|属性叠加计算错误|CalcValue语法错误|改用数学表达式:CALCVARN1=N1*5|


---

###六、进阶功能扩展

####1.**动态宝石属性**
根据玩家等级调整宝石数值:
```lua
[@Embed5Hole]
#IF
CheckLevel>50
#ACT
UpdateGemData五孔宝石AttrValue1=10
```


####2.**孔位解锁任务**
添加孔位解锁NPC脚本:
```lua
[@UnlockHole]
#IF
CheckTask孔位大师1;完成前置任务
#ACT
SetItemMaxHole当前装备+5
SendMsg6已解锁5个新孔位!
```


---

####结语
通过上述方案,开发者可在GOM引擎中实现“单宝石五孔镶嵌”的复杂逻辑。关键在于**属性模板与脚本循环的精准控制**,以及客户端UI的适配。建议测试阶段使用@Make命令批量生成测试道具,验证多线程压力下的数据一致性。

####1.打孔和镶嵌功能概述

#####什么是打孔?
打孔是指在装备上创建孔洞,以便玩家可以在这些孔洞中镶嵌宝石或特殊材料,从而提升装备的各项属性。

#####什么是镶嵌?
镶嵌是指将特定的宝石或材料放入装备上的孔洞中,以增强装备的攻击力、防御力、生命值等属性。

####2.GOM引擎简介

#####GOM引擎特点
-**高效稳定**:GOM引擎以其高效的处理能力和稳定的运行表现著称。
-**易用性强**:GOM引擎提供了简洁明了的API接口,方便开发者进行二次开发。
-**功能全面**:支持多种游戏元素的添加,包括但不限于技能、怪物、地图等。

#####支持自定义功能
GOM引擎允许开发者通过修改代码和配置文件来实现各种自定义功能,包括打孔和镶嵌系统。

####3.实现打孔和镶嵌功能步骤

#####步骤一:准备工作
确保你已经安装了GOM引擎,并且有一个基本的游戏框架搭建完成。此外,还需要准备好所有必要的客户端和服务器端文件。

#####步骤二:配置数据库表

######创建装备孔洞数据表
首先,在数据库中创建一个新的表来存储装备孔洞的信息。

**创建`equipment_hole`表**
```sql
CREATETABLEequipment_hole(
idINTAUTO_INCREMENTPRIMARYKEY
equip_idINTNOTNULL--装备ID
hole_countINTNOTNULLDEFAULT0--孔洞数量
FOREIGNKEY(equip_id)REFERENCESitem_table(id)
);
```

######创建镶嵌石数据表
接下来,创建一个表来存储镶嵌石的信息。

**创建`gemstone`表**
```sql
CREATETABLEgemstone(
idINTAUTO_INCREMENTPRIMARYKEY
nameVARCHAR(50)NOTNULL
typeINTNOTNULL--镶嵌石类型
attribute_typeINTNOTNULL--属性类型(如攻击力、防御力)
attribute_valueINTNOTNULL--属性值
);
```

######插入示例数据
插入一些示例数据以便进行测试。

**插入装备孔洞数据**
```sql
INSERTINTOequipment_hole(equip_idhole_count)VALUES(10015);--假设装备ID为1001的装备有5个孔洞
```

**插入镶嵌石数据**
```sql
INSERTINTOgemstone(nametypeattribute_typeattribute_value)VALUES('力量之石'1110);--类型1表示力量石,属性类型1表示攻击力
INSERTINTOgemstone(nametypeattribute_typeattribute_value)VALUES('智慧之石'225);--类型2表示智慧石,属性类型2表示防御力
INSERTINTOgemstone(nametypeattribute_typeattribute_value)VALUES('生命之石'3320);--类型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"

voidItemHandler::CreateHoles(Character*characterintequipIdintholeCount)
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();
std::stringquery="INSERTINTOequipment_hole(equip_idhole_count)VALUES("+std::to_string(equipId)+""+std::to_string(holeCount)+")";
if(!dbManager->Execute(query))
{
SystemLog::LogError("Failedtocreateholesforequipment[%d]."equipId);
return;
}

CPacketBuilderresponse(PACKET_TYPE_CREATE_HOLES_RESPONSE);
response.WriteInt(equipId);
response.WriteInt(holeCount);
character->SendPacket(response.Build());

SystemLog::LogInfo("Created%dholesforequipment[%d]forcharacter[%d]."holeCountequipIdcharacter->GetId());
}

voidItemHandler::EmbedGemstone(Character*characterintequipIdintgemstoneId)
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();

//Checkiftheequipmenthasenoughholes
std::stringcheckQuery="SELECThole_countFROMequipment_holeWHEREequip_id="+std::to_string(equipId);
MYSQL_RES*result=dbManager->Query(checkQuery.c_str());
if(!result||mysql_num_rows(result)==0)
{
SystemLog::LogWarning("Equipment[%d]doesnothaveanyholes."equipId);
CPacketBuilderresponse(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_NO_HOLES);
character->SendPacket(response.Build());
mysql_free_result(result);
return;
}

MYSQL_ROWrow=mysql_fetch_row(result);
intholeCount=atoi(row[0]);
mysql_free_result(result);

if(holeCount<=0)
{
SystemLog::LogWarning("Equipment[%d]doesnothaveanyavailableholes."equipId);
CPacketBuilderresponse(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_NO_AVAILABLE_HOLES);
character->SendPacket(response.Build());
return;
}

//Deductonehole
std::stringupdateQuery="UPDATEequipment_holeSEThole_count=hole_count-1WHEREequip_id="+std::to_string(equipId);
if(!dbManager->Execute(updateQuery))
{
SystemLog::LogError("Failedtoupdateholecountforequipment[%d]."equipId);
CPacketBuilderresponse(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_DB_ERROR);
character->SendPacket(response.Build());
return;
}

//Embedgemstoneintoequipment
std::stringinsertQuery="INSERTINTOembedded_gemstones(equip_idgemstone_id)VALUES("+std::to_string(equipId)+""+std::to_string(gemstoneId)+")";
if(!dbManager->Execute(insertQuery))
{
SystemLog::LogError("Failedtoembedgemstone[%d]intoequipment[%d]."gemstoneIdequipId);
CPacketBuilderresponse(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_FAILURE_DB_ERROR);
character->SendPacket(response.Build());
return;
}

CPacketBuilderresponse(PACKET_TYPE_EMBED_GEMSTONE_RESPONSE);
response.WriteByte(EMBED_GEMSTONE_SUCCESS);
response.WriteInt(equipId);
response.WriteInt(gemstoneId);
character->SendPacket(response.Build());

SystemLog::LogInfo("Embeddedgemstone[%d]intoequipment[%d]forcharacter[%d]."gemstoneIdequipIdcharacter->GetId());
}
```

######修改`database_manager.cpp`
在`src\database_manager.cpp`文件中添加查询和更新数据库的逻辑。

**database_manager.cpp**
```cpp
#include"database_manager.h"
#include<mysql/mysql.h>

DatabaseManager*DatabaseManager::GetInstance()
{
staticDatabaseManagerinstance;
return&instance;
}

DatabaseManager::DatabaseManager()
{
ConnectToDatabase();
}

boolDatabaseManager::ConnectToDatabase()
{
m_mysql=mysql_init(NULL);
if(!m_mysql)
{
SystemLog::LogError("FailedtoinitializeMySQL.");
returnfalse;
}

if(!mysql_real_connect(m_mysql"localhost""your_username""your_password""your_database"3306NULL0))
{
SystemLog::LogError("Failedtoconnecttodatabase:%s"mysql_error(m_mysql));
mysql_close(m_mysql);
returnfalse;
}

SystemLog::LogInfo("Connectedtodatabasesuccesully.");
returntrue;
}

boolDatabaseManager::Execute(conststd::string&query)
{
if(mysql_query(m_mysqlquery.c_str()))
{
SystemLog::LogError("Failedtoexecutequery:%s\n%s"query.c_str()mysql_error(m_mysql));
returnfalse;
}

returntrue;
}

MYSQL_RES*DatabaseManager::Query(conststd::string&query)
{
if(mysql_query(m_mysqlquery.c_str()))
{
SystemLog::LogError("Failedtoexecutequery:%s\n%s"query.c_str()mysql_error(m_mysql));
returnNULL;
}

returnmysql_store_result(m_mysql);
}

intDatabaseManager::GetLastInsertId()
{
returnstatic_cast<int>(mysql_insert_id(m_mysql));
}
```

######修改`character.cpp`
在`src\character.cpp`文件中添加管理装备和镶嵌石的逻辑。

**character.cpp**
```cpp
#include"character.h"
#include"item_handler.h"

Character::Character(intid)
{
m_id=id;
}

voidCharacter::UseItem(intitemId)
{
ItemHandler*itemHandler=ItemHandler::GetInstance();
switch(itemId)
{
caseITEM_ID_CREATE_HOLE_STONE:
itemHandler->CreateHoles(thism_equipmentId5);//假设使用道具ID为ITEM_ID_CREATE_HOLE_STONE创建5个孔洞
break;
default:
DefaultItemUsage(itemId);
break;
}
}

voidCharacter::EmbedGemstone(intequipIdintgemstoneId)
{
ItemHandler*itemHandler=ItemHandler::GetInstance();
itemHandler->EmbedGemstone(thisequipIdgemstoneId);
}
```

######修改`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;
}

boolCClientNetwork::ConnectToAuthServer(conststd::string&ipintport)
{
m_authSocket=socket(AF_INETSOCK_STREAM0);
if(m_authSocket==INVALID_SOCKET)
{
SystemLog::LogError("Failedtocreatesocket:%d"WSAGetLastError());
returnfalse;
}

sockaddr_inserverAddr;
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*)&serverAddrsizeof(serverAddr))==SOCKET_ERROR)
{
SystemLog::LogError("Failedtoconnecttoauthserver:%d"WSAGetLastError());
closesocket(m_authSocket);
returnfalse;
}

SystemLog::LogInfo("Connectedtoauthserverat%s:%d"ip.c_str()port);
returntrue;
}

boolCClientNetwork::ConnectToGameServer(conststd::string&ipintport)
{
m_gameSocket=socket(AF_INETSOCK_STREAM0);
if(m_gameSocket==INVALID_SOCKET)
{
SystemLog::LogError("Failedtocreatesocket:%d"WSAGetLastError());
returnfalse;
}

sockaddr_inserverAddr;
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*)&serverAddrsizeof(serverAddr))==SOCKET_ERROR)
{
SystemLog::LogError("Failedtoconnecttogameserver:%d"WSAGetLastError());
closesocket(m_gameSocket);
returnfalse;
}

SystemLog::LogInfo("Connectedtogameserverat%s:%d"ip.c_str()port);
returntrue;
}

voidCClientNetwork::SendLoginRequest(conststd::string&usernameconststd::string&password)
{
CPacketBuilderpacket(PACKET_TYPE_LOGIN_REQUEST);
packet.WriteString(username);
packet.WriteString(password);
SendPacketToAuthServer(packet.Build());
}

voidCClientNetwork::SendPacketToAuthServer(constPacket&packet)
{
send(m_authSocketreinterpret_cast<constchar*>(packet.GetData())packet.GetSize()0);
}

voidCClientNetwork::SendPacketToGameServer(constPacket&packet)
{
send(m_gameSocketreinterpret_cast<constchar*>(packet.GetData())packet.GetSize()0);
}

boolCClientNetwork::ReceivePacket(Packet&packet)
{
charbuffer[MAX_PACKET_SIZE];
intbytesRead=recv(m_gameSocketbufferMAX_PACKET_SIZE0);
if(bytesRead<=0)
{
SystemLog::LogWarning("Connectionclosedbyserver.");
returnfalse;
}

packet.SetData(bufferbytesRead);
returntrue;
}

voidCClientNetwork::SendCreateHolesRequest(intequipIdintholeCount)
{
CPacketBuilderpacket(PACKET_TYPE_CREATE_HOLES_REQUEST);
packet.WriteInt(equipId);
packet.WriteInt(holeCount);
SendPacketToGameServer(packet.Build());
}

voidCClientNetwork::SendEmbedGemstoneRequest(intequipIdintgemstoneId)
{
CPacketBuilderpacket(PACKET_TYPE_EMBED_GEMSTONE_REQUEST);
packet.WriteInt(equipId);
packet.WriteInt(gemstoneId);
SendPacketToGameServer(packet.Build());
}

voidCClientNetwork::HandleCreateHolesResponse(constPacket&packet)
{
intequipId=packet.ReadInt();
intholeCount=packet.ReadInt();
SystemLog::LogInfo("Created%dholesforequipment[%d]."holeCountequipId);
//UpdateUItoshowcreatedholes
}

voidCClientNetwork::HandleEmbedGemstoneResponse(constPacket&packet)
{
bytestatus=packet.ReadByte();
if(status==EMBED_GEMSTONE_SUCCESS)
{
intequipId=packet.ReadInt();
intgemstoneId=packet.ReadInt();
SystemLog::LogInfo("Embeddedgemstone[%d]intoequipment[%d]."gemstoneIdequipId);
//UpdateUItoshowembeddedgemstone
}
else
{
SystemLog::LogWarning("Failedtoembedgemstone.");
}
}
```

#####步骤五:编译并测试
确保所有修改后的代码都能成功编译。

**编译服务器端**
```sh
g++-ogame_serversrc/game_server.cppsrc/database_manager.cppsrc/item_handler.cppsrc/packet_builder.cpp-lengine
```

**编译客户端**
```sh
g++-oclientsrc/client_main.cppsrc/client_network.cppsrc/packet_builder.cpp-lengine
```

启动登录服务器、游戏服务器和客户端,观察整个打孔和镶嵌流程是否顺畅。

**启动服务器命令**
```sh
startauth_server.exe
startgame_server.exe
startclient.exe
```

####4.日志文件检查

#####查看游戏服务器日志
打开游戏服务器的日志文件(通常位于`log\game_server.log`),查找相关的错误信息。

**游戏服务器日志示例**
```plaintext
[2023-10-0112:34:56]INFO:Gameserverstartedonport2107.
[2023-10-0112:34:56]INFO:Connectedtodatabasesuccesully.
[2023-10-0112:34:56]INFO:Created5holesforequipment[1001]forcharacter[1].
[2023-10-0112:34:56]INFO:Embeddedgemstone[1]intoequipment[1001]forcharacter[1].
```

根据日志中的信息,确认游戏服务器是否正常运行以及打孔和镶嵌操作是否正确执行。

#####查看客户端日志
打开客户端的日志文件(通常位于`log\client.log`),查找相关的错误信息。

**客户端日志示例**
```plaintext
[2023-10-0112:34:56]INFO:Connectingtoauthserverat127.0.0.1:2106.
[2023-10-0112:34:56]INFO:Connectedtoauthserverat127.0.0.1:2106.
[2023-10-0112:34:56]INFO:Logginginastestuser.
[2023-10-0112:34:56]INFO:LoginsuccesulaccountID:1.
[2023-10-0112:34:56]INFO:Connectingtogameserverat127.0.0.1:2107.
[2023-10-0112:34:56]INFO:Connectedtogameserverat127.0.0.1:2107.
[2023-10-0112:34:56]INFO:Created5holesforequipment[1001].
[2023-10-0112:34:56]INFO:Embeddedgemstone[1]intoequipment[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个孔的功能。这不仅增加了游戏的策略性和深度,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!
[顶部]