攻城战是传奇类游戏的核心玩法,但不同引擎(GOM/HERO/LEG)的开启逻辑差异显著。本文从基础配置、行会申请到沙巴克规则,详解各引擎的攻沙实现方案,并提供脚本代码与避坑指南。
---
###一、通用前置条件
1.**服务器时间校准**:确保所有引擎服务与北京时间同步。
2.**沙巴克地图配置**:检查盟重省(3)与沙巴克皇宫(015)地图的通行性。
3.**行会系统激活**:至少存在一个已创建的行会(行会战需开启)。
---
###二、GOM引擎开启攻沙教程
####**步骤1:配置沙巴克基础参数**
文件路径:`Mir200\Envir\SabukW.txt`
```ini
[Setup]
CastleName=沙巴克;城池名称
OwnGuild=无;初始占领行会
WarTime=20:00-22:00;攻城时段(每日)
ToKillGuard=YES;允许击杀守卫
IncomeGold=1000000;每日税收(金币)
```
####**步骤2:设置攻城申请NPC**
在盟重安全区添加NPC脚本(如`Market_Def\盟重_145.txt`):
```lua
[@Main]
#if
CheckCastleWarStatus=0;检查是否非攻城期间
#act
OpenApplyWindow;打开申请界面
#say
请支付500万金币申请攻城!
[@ApplySuccess]
#IF
CheckGold5000000
#ACT
TakeGold5000000
SetApplyAttackWar1;设置行会为攻方
SysMsg“攻城申请成功!”
```
####**步骤3:启用机器人定时器**
路径:`Mir200\Envir\Robot.txt`
```ini
#AutoRunNPCSEC10@CheckCastleWar
```
脚本(`Robot_def\AutoRun.txt`):
```lua
[@CheckCastleWar]
#IF
EQUAL<$CASTLEWARSTATUS>0
HOUR19MIN55;提前5分钟准备
#ACT
StartCastleWar;触发攻城战
SysMsg“沙巴克攻城战即将开始!”
```
####**步骤4:奖励发放逻辑**
皇宫内NPC脚本(`Market_Def\沙巴克_3.txt`):
```lua
[@WinGuild]
#IF
IsCastleMaster;检测是否为占领行会
#ACT
AddGuildCredit2000;行会资金
Give<$USERNAME>圣战戒指1;个人奖励
```
---
###三、HERO引擎攻沙配置
####**1.修改沙巴克参数**
文件路径:`Mir200\Envir\SabukW.txt`
```ini
[Settings]
CastleName=沙巴克
WarTime=20:00-22:00
DefenseFee=1000000;守城费用
```
####**2.添加申请NPC脚本**
```lua
[@Main]
#if
CheckAttackWarList<$GUILDNAME>;检查是否已申请
#act
MessageBox已申请攻城!
#elseact
OpenAttackWarWindow
```
####**3.启动攻城事件**
通过GM命令立即测试:
```
@StartCastleWar
```
---
###四、LEG引擎攻沙配置
####**1.配置文件调整**
路径:`Mir200\Envir\MapInfo.txt`
```ini
[015沙巴克皇宫]FIGHT3
```
参数说明:`FIGHT3`表示该地图可被行会占领。
####**2.手动触发攻沙**
GM命令:
```
@ForcedStartCastleWar
```
---
###五、高频问题解决方案
|**问题现象**|**原因**|**解决方案**|
|--------------------------|-------------------------|---------------------------------|
|行会无法申请攻城|申请费用不足或时间段冲突|检查NPC脚本的CheckGold条件与WarTime重叠|
|攻城期间皇宫无法进入|地图参数未开放PK或禁止传送|在MapInfo.txt中移除NoCastleWar标记|
|占领后税收未发放|税收脚本未绑定或路径错误|确认Mir200\Envir\Market_Def\Guild-*脚本存在|
|攻城结束后行会未获得占领状态|数据库未更新CastleOwner字段|手动执行SQL:UPDATECastleSETOwnerGuild='行会名'|
---
###六、进阶玩法:自定义规则
####**1.多重城门攻破机制**
在沙巴克城墙地图(如D716)添加破坏触发器:
```lua
[@DestroyGate]
#IF
CheckMonMapD7160;检测地图无守卫
#ACT
OpenMainGate;开启主城门
```
####**2.动态税收与奖励**
根据占领时长调整税率:
```lua
[@DailyTax]
#IF
CheckCastleHoldDays>7
#ACT
SetIncomeGold1500000;占领超7天税率提升50%
```
####**3.攻城BOSS刷新**
在皇宫内定时刷新BOSS(赤月恶魔):
```lua
[@CastleBoss]
#IF
Random10;10%概率刷新
#ACT
MobGen330330赤月恶魔1
```
---
####结语
通过精准的引擎配置与脚本定制,攻城战可成为玩家活跃的核心驱动力。重点在于测试阶段的全流程验证(申请-战斗-占领-奖励),并建立日志监控体系(如M2Server.log)快速定位异常。对于高并发服,建议采用分线攻沙或动态副本机制以优化体验。
####1.攻城功能概述
#####什么是攻城?
攻城战是一种团队对抗活动,通常发生在两个或多个势力之间,争夺某个特定区域(如城堡、城市)的控制权。攻城战不仅增加了游戏的竞争性和策略性,还能为玩家带来丰厚的奖励。
####2.GOM引擎简介
#####GOM引擎特点
-**高效稳定**:GOM引擎以其高效的处理能力和稳定的运行表现著称。
-**易用性强**:GOM引擎提供了简洁明了的API接口,方便开发者进行二次开发。
-**功能全面**:支持多种游戏元素的添加,包括但不限于技能、怪物、地图等。
#####支持攻城功能
GOM引擎内置了对攻城战的支持,只需要正确配置即可启用该项功能。
####3.开启攻城功能步骤
#####步骤一:准备工作
确保你已经安装了GOM引擎,并且有一个基本的游戏框架搭建完成。此外,还需要准备好所有必要的客户端和服务器端文件。
#####步骤二:配置攻城数据表
在数据库中创建或修改相关的攻城数据表,以存储攻城相关信息。
**创建攻城数据表**
```sql
CREATETABLEcastle(
idINTAUTO_INCREMENTPRIMARYKEY
nameVARCHAR(50)NOTNULL
owner_idINTDEFAULT0--当前占领者ID,默认为0表示无人占领
last_capture_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP
);
CREATETABLEsiege_schedule(
idINTAUTO_INCREMENTPRIMARYKEY
castle_idINTNOTNULL
start_timeDATETIMENOTNULL
end_timeDATETIMENOTNULL
FOREIGNKEY(castle_id)REFERENCEScastle(id)
);
```
#####步骤三:插入示例数据
插入一些示例数据以便进行测试。
**插入城堡数据**
```sql
INSERTINTOcastle(nameowner_id)VALUES('风之城堡'0);
INSERTINTOcastle(nameowner_id)VALUES('火之城堡'0);
```
**插入攻城日程数据**
```sql
INSERTINTOsiege_schedule(castle_idstart_timeend_time)VALUES(1'2023-10-0120:00:00''2023-10-0122:00:00');
INSERTINTOsiege_schedule(castle_idstart_timeend_time)VALUES(2'2023-10-0220:00:00''2023-10-0222:00:00');
```
#####步骤四:修改配置文件
在`game_config.txt`中添加或修改攻城相关配置项。
**game_config.txt**
```ini
[Siege]
EnableSiege=1;启用攻城功能
CastleCount=2;城堡数量
SiegeInterval=86400;攻城间隔时间(秒),默认一天
MinPlayers=5;最少参加攻城人数
MaxPlayers=20;最多参加攻城人数
CaptureReward=100000;占领城堡奖励金币数
DefenseBonus=1.5;防守方伤害加成倍率
AttackBonus=1.2;攻击方伤害加成倍率
```
#####步骤五:修改代码实现
######修改`siege_manager.cpp`
在`src\siege_manager.cpp`文件中实现攻城管理功能。
**siege_manager.cpp**
```cpp
#include"siege_manager.h"
#include"database_manager.h"
#include<map>
#include<vector>
SiegeManager*SiegeManager::GetInstance()
{
staticSiegeManagerinstance;
return&instance;
}
SiegeManager::SiegeManager()
{
LoadCastlesFromDatabase();
LoadSiegeSchedulesFromDatabase();
}
voidSiegeManager::LoadCastlesFromDatabase()
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();
MYSQL_RES*result=dbManager->Query("SELECT*FROMcastle");
if(!result)
{
SystemLog::LogError("Failedtoloadcastlesfromdatabase.");
return;
}
MYSQL_ROWrow;
while((row=mysql_fetch_row(result)))
{
intid=atoi(row[0]);
std::stringname=row[1];
intownerId=atoi(row[2]);
Castlecastle(idnameownerId);
m_castles[id]=castle;
}
mysql_free_result(result);
SystemLog::LogInfo("Loaded%dcastlesfromdatabase."m_castles.size());
}
voidSiegeManager::LoadSiegeSchedulesFromDatabase()
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();
MYSQL_RES*result=dbManager->Query("SELECT*FROMsiege_schedule");
if(!result)
{
SystemLog::LogError("Failedtoloadsiegeschedulesfromdatabase.");
return;
}
MYSQL_ROWrow;
while((row=mysql_fetch_row(result)))
{
intid=atoi(row[0]);
intcastleId=atoi(row[1]);
time_tstartTime=strtotime(row[2]);
time_tendTime=strtotime(row[3]);
SiegeScheduleschedule(idcastleIdstartTimeendTime);
m_siegeSchedules.push_back(schedule);
}
mysql_free_result(result);
SystemLog::LogInfo("Loaded%dsiegeschedulesfromdatabase."m_siegeSchedules.size());
}
voidSiegeManager::StartSiege(intcastleId)
{
autoit=m_castles.find(castleId);
if(it==m_castles.end())
{
SystemLog::LogWarning("Castle[%d]notfound."castleId);
return;
}
Castle&castle=it->second;
castle.SetOccupied(true);
SystemLog::LogInfo("Startedsiegeforcastle[%s]."castle.GetName().c_str());
//Notifyclientsaboutthestartofthesiege
CPacketBuilderpacket(PACKET_TYPE_SIEGE_START_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}
voidSiegeManager::EndSiege(intcastleId)
{
autoit=m_castles.find(castleId);
if(it==m_castles.end())
{
SystemLog::LogWarning("Castle[%d]notfound."castleId);
return;
}
Castle&castle=it->second;
castle.SetOccupied(false);
SystemLog::LogInfo("Endedsiegeforcastle[%s]."castle.GetName().c_str());
//Notifyclientsabouttheendofthesiege
CPacketBuilderpacket(PACKET_TYPE_SIEGE_END_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}
voidSiegeManager::CheckAndProcessSieges()
{
time_tcurrentTime=time(nullptr);
for(constSiegeSchedule&schedule:m_siegeSchedules)
{
if(currentTime>=schedule.GetStartTime()&¤tTime<=schedule.GetEndTime())
{
StartSiege(schedule.GetCastleId());
}
elseif(currentTime>schedule.GetEndTime())
{
EndSiege(schedule.GetCastleId());
}
}
}
```
######修改`character.cpp`
在`src\character.cpp`文件中添加参与攻城的逻辑。
**character.cpp**
```cpp
#include"character.h"
Character::Character(intid)
{
m_id=id;
}
voidCharacter::JoinSiege(intcastleId)
{
if(m_currentSiegeCastle!=-1)
{
SystemLog::LogWarning("Character[%d]isalreadyinasiege."m_id);
return;
}
m_currentSiegeCastle=castleId;
SystemLog::LogInfo("Character[%d]joinedsiegeforcastle[%d]."m_idcastleId);
//Notifyserveraboutjoiningthesiege
CPacketBuilderpacket(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(m_id);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
voidCharacter::LeaveSiege()
{
if(m_currentSiegeCastle==-1)
{
SystemLog::LogWarning("Character[%d]isnotinanysiege."m_id);
return;
}
intcastleId=m_currentSiegeCastle;
m_currentSiegeCastle=-1;
SystemLog::LogInfo("Character[%d]leftsiegeforcastle[%d]."m_idcastleId);
//Notifyserveraboutleavingthesiege
CPacketBuilderpacket(PACKET_TYPE_LEAVE_SIEGE_REQUEST);
packet.WriteInt(m_id);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
```
######修改`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::SendJoinSiegeRequest(intcastleId)
{
CPacketBuilderpacket(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
voidCClientNetwork::SendLeaveSiegeRequest()
{
CPacketBuilderpacket(PACKET_TYPE_LEAVE_SIEGE_REQUEST);
SendPacketToGameServer(packet.Build());
}
voidCClientNetwork::HandleSiegeStartNotify(constPacket&packet)
{
intcastleId=packet.ReadInt();
SystemLog::LogInfo("Siegestartedforcastle[%d]."castleId);
//UpdateUItoshowsiegestatus
}
voidCClientNetwork::HandleSiegeEndNotify(constPacket&packet)
{
intcastleId=packet.ReadInt();
SystemLog::LogInfo("Siegeendedforcastle[%d]."castleId);
//UpdateUItoshowsiegestatus
}
```
#####步骤六:编译并测试
确保所有修改后的代码都能成功编译。
**编译服务器端**
```sh
g++-ogame_serversrc/game_server.cppsrc/database_manager.cppsrc/siege_manager.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:Loaded2castlesfromdatabase.
[2023-10-0112:34:56]INFO:Loaded2siegeschedulesfromdatabase.
[2023-10-0120:00:00]INFO:Startedsiegeforcastle[风之城堡].
[2023-10-0122:00:00]INFO:Endedsiegeforcastle[风之城堡].
```
根据日志中的信息,确认游戏服务器是否正常运行以及攻城事件是否正确触发。
#####查看客户端日志
打开客户端的日志文件(通常位于`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-0120:00:00]INFO:Siegestartedforcastle[风之城堡].
[2023-10-0122:00:00]INFO:Siegeendedforcastle[风之城堡].
```
根据日志中的信息,确认客户端是否正确接收了服务器的攻城通知。
####5.常见问题及解决方案
#####问题一:无法连接到游戏服务器
-**检查网络设置**:确保客户端和游戏服务器之间的网络连接正常。
-**检查配置文件**:确保`client_config.txt`中的游戏服务器IP和端口配置正确。
-**检查防火墙设置**:确保防火墙没有阻止游戏服务器的端口。
#####问题二:登录失败
-**检查数据库配置**:确保`auth_config.txt`中的数据库配置正确。
-**检查数据库服务**:确保数据库服务正在运行并且可以访问。
-**检查用户数据**:确保`account_table`中包含正确的用户信息。
#####问题三:角色加载失败
-**检查角色数据**:确保`char_table`中包含正确的角色信息。
-**检查物品数据**:确保`item_table`中包含正确的物品信息。
-**检查技能数据**:确保`skill_table`中包含正确的技能信息。
#####问题四:客户端版本不匹配
-**更新客户端**:确保客户端版本与服务器版本兼容。
-**同步资源文件**:确保客户端和服务器之间的资源文件一致。
#####问题五:攻城未触发
-**检查日志文件**:查看日志文件以确定是否有攻城触发的相关记录。
-**检查配置文件**:确保`game_config.txt`中的攻城配置正确。
-**检查时间设置**:确保当前时间符合攻城日程的时间设置。
#####问题六:参与攻城失败
-**检查玩家人数**:确保参与攻城的人数达到最低要求。
-**检查角色状态**:确保角色处于可参与攻城的状态(如不在其他活动中)。
-**检查日志文件**:查看日志文件以确定是否有参与攻城失败的记录。
####6.总结
通过以上步骤,你应该能够在GOM传奇引擎中成功开启并配置攻城功能。这不仅增加了游戏的竞争性和策略性,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!
---
###一、通用前置条件
1.**服务器时间校准**:确保所有引擎服务与北京时间同步。
2.**沙巴克地图配置**:检查盟重省(3)与沙巴克皇宫(015)地图的通行性。
3.**行会系统激活**:至少存在一个已创建的行会(行会战需开启)。
---
###二、GOM引擎开启攻沙教程
####**步骤1:配置沙巴克基础参数**
文件路径:`Mir200\Envir\SabukW.txt`
```ini
[Setup]
CastleName=沙巴克;城池名称
OwnGuild=无;初始占领行会
WarTime=20:00-22:00;攻城时段(每日)
ToKillGuard=YES;允许击杀守卫
IncomeGold=1000000;每日税收(金币)
```
####**步骤2:设置攻城申请NPC**
在盟重安全区添加NPC脚本(如`Market_Def\盟重_145.txt`):
```lua
[@Main]
#if
CheckCastleWarStatus=0;检查是否非攻城期间
#act
OpenApplyWindow;打开申请界面
#say
请支付500万金币申请攻城!
[@ApplySuccess]
#IF
CheckGold5000000
#ACT
TakeGold5000000
SetApplyAttackWar1;设置行会为攻方
SysMsg“攻城申请成功!”
```
####**步骤3:启用机器人定时器**
路径:`Mir200\Envir\Robot.txt`
```ini
#AutoRunNPCSEC10@CheckCastleWar
```
脚本(`Robot_def\AutoRun.txt`):
```lua
[@CheckCastleWar]
#IF
EQUAL<$CASTLEWARSTATUS>0
HOUR19MIN55;提前5分钟准备
#ACT
StartCastleWar;触发攻城战
SysMsg“沙巴克攻城战即将开始!”
```
####**步骤4:奖励发放逻辑**
皇宫内NPC脚本(`Market_Def\沙巴克_3.txt`):
```lua
[@WinGuild]
#IF
IsCastleMaster;检测是否为占领行会
#ACT
AddGuildCredit2000;行会资金
Give<$USERNAME>圣战戒指1;个人奖励
```
---
###三、HERO引擎攻沙配置
####**1.修改沙巴克参数**
文件路径:`Mir200\Envir\SabukW.txt`
```ini
[Settings]
CastleName=沙巴克
WarTime=20:00-22:00
DefenseFee=1000000;守城费用
```
####**2.添加申请NPC脚本**
```lua
[@Main]
#if
CheckAttackWarList<$GUILDNAME>;检查是否已申请
#act
MessageBox已申请攻城!
#elseact
OpenAttackWarWindow
```
####**3.启动攻城事件**
通过GM命令立即测试:
```
@StartCastleWar
```
---
###四、LEG引擎攻沙配置
####**1.配置文件调整**
路径:`Mir200\Envir\MapInfo.txt`
```ini
[015沙巴克皇宫]FIGHT3
```
参数说明:`FIGHT3`表示该地图可被行会占领。
####**2.手动触发攻沙**
GM命令:
```
@ForcedStartCastleWar
```
---
###五、高频问题解决方案
|**问题现象**|**原因**|**解决方案**|
|--------------------------|-------------------------|---------------------------------|
|行会无法申请攻城|申请费用不足或时间段冲突|检查NPC脚本的CheckGold条件与WarTime重叠|
|攻城期间皇宫无法进入|地图参数未开放PK或禁止传送|在MapInfo.txt中移除NoCastleWar标记|
|占领后税收未发放|税收脚本未绑定或路径错误|确认Mir200\Envir\Market_Def\Guild-*脚本存在|
|攻城结束后行会未获得占领状态|数据库未更新CastleOwner字段|手动执行SQL:UPDATECastleSETOwnerGuild='行会名'|
---
###六、进阶玩法:自定义规则
####**1.多重城门攻破机制**
在沙巴克城墙地图(如D716)添加破坏触发器:
```lua
[@DestroyGate]
#IF
CheckMonMapD7160;检测地图无守卫
#ACT
OpenMainGate;开启主城门
```
####**2.动态税收与奖励**
根据占领时长调整税率:
```lua
[@DailyTax]
#IF
CheckCastleHoldDays>7
#ACT
SetIncomeGold1500000;占领超7天税率提升50%
```
####**3.攻城BOSS刷新**
在皇宫内定时刷新BOSS(赤月恶魔):
```lua
[@CastleBoss]
#IF
Random10;10%概率刷新
#ACT
MobGen330330赤月恶魔1
```
---
####结语
通过精准的引擎配置与脚本定制,攻城战可成为玩家活跃的核心驱动力。重点在于测试阶段的全流程验证(申请-战斗-占领-奖励),并建立日志监控体系(如M2Server.log)快速定位异常。对于高并发服,建议采用分线攻沙或动态副本机制以优化体验。
####1.攻城功能概述
#####什么是攻城?
攻城战是一种团队对抗活动,通常发生在两个或多个势力之间,争夺某个特定区域(如城堡、城市)的控制权。攻城战不仅增加了游戏的竞争性和策略性,还能为玩家带来丰厚的奖励。
####2.GOM引擎简介
#####GOM引擎特点
-**高效稳定**:GOM引擎以其高效的处理能力和稳定的运行表现著称。
-**易用性强**:GOM引擎提供了简洁明了的API接口,方便开发者进行二次开发。
-**功能全面**:支持多种游戏元素的添加,包括但不限于技能、怪物、地图等。
#####支持攻城功能
GOM引擎内置了对攻城战的支持,只需要正确配置即可启用该项功能。
####3.开启攻城功能步骤
#####步骤一:准备工作
确保你已经安装了GOM引擎,并且有一个基本的游戏框架搭建完成。此外,还需要准备好所有必要的客户端和服务器端文件。
#####步骤二:配置攻城数据表
在数据库中创建或修改相关的攻城数据表,以存储攻城相关信息。
**创建攻城数据表**
```sql
CREATETABLEcastle(
idINTAUTO_INCREMENTPRIMARYKEY
nameVARCHAR(50)NOTNULL
owner_idINTDEFAULT0--当前占领者ID,默认为0表示无人占领
last_capture_timeTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP
);
CREATETABLEsiege_schedule(
idINTAUTO_INCREMENTPRIMARYKEY
castle_idINTNOTNULL
start_timeDATETIMENOTNULL
end_timeDATETIMENOTNULL
FOREIGNKEY(castle_id)REFERENCEScastle(id)
);
```
#####步骤三:插入示例数据
插入一些示例数据以便进行测试。
**插入城堡数据**
```sql
INSERTINTOcastle(nameowner_id)VALUES('风之城堡'0);
INSERTINTOcastle(nameowner_id)VALUES('火之城堡'0);
```
**插入攻城日程数据**
```sql
INSERTINTOsiege_schedule(castle_idstart_timeend_time)VALUES(1'2023-10-0120:00:00''2023-10-0122:00:00');
INSERTINTOsiege_schedule(castle_idstart_timeend_time)VALUES(2'2023-10-0220:00:00''2023-10-0222:00:00');
```
#####步骤四:修改配置文件
在`game_config.txt`中添加或修改攻城相关配置项。
**game_config.txt**
```ini
[Siege]
EnableSiege=1;启用攻城功能
CastleCount=2;城堡数量
SiegeInterval=86400;攻城间隔时间(秒),默认一天
MinPlayers=5;最少参加攻城人数
MaxPlayers=20;最多参加攻城人数
CaptureReward=100000;占领城堡奖励金币数
DefenseBonus=1.5;防守方伤害加成倍率
AttackBonus=1.2;攻击方伤害加成倍率
```
#####步骤五:修改代码实现
######修改`siege_manager.cpp`
在`src\siege_manager.cpp`文件中实现攻城管理功能。
**siege_manager.cpp**
```cpp
#include"siege_manager.h"
#include"database_manager.h"
#include<map>
#include<vector>
SiegeManager*SiegeManager::GetInstance()
{
staticSiegeManagerinstance;
return&instance;
}
SiegeManager::SiegeManager()
{
LoadCastlesFromDatabase();
LoadSiegeSchedulesFromDatabase();
}
voidSiegeManager::LoadCastlesFromDatabase()
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();
MYSQL_RES*result=dbManager->Query("SELECT*FROMcastle");
if(!result)
{
SystemLog::LogError("Failedtoloadcastlesfromdatabase.");
return;
}
MYSQL_ROWrow;
while((row=mysql_fetch_row(result)))
{
intid=atoi(row[0]);
std::stringname=row[1];
intownerId=atoi(row[2]);
Castlecastle(idnameownerId);
m_castles[id]=castle;
}
mysql_free_result(result);
SystemLog::LogInfo("Loaded%dcastlesfromdatabase."m_castles.size());
}
voidSiegeManager::LoadSiegeSchedulesFromDatabase()
{
DatabaseManager*dbManager=DatabaseManager::GetInstance();
MYSQL_RES*result=dbManager->Query("SELECT*FROMsiege_schedule");
if(!result)
{
SystemLog::LogError("Failedtoloadsiegeschedulesfromdatabase.");
return;
}
MYSQL_ROWrow;
while((row=mysql_fetch_row(result)))
{
intid=atoi(row[0]);
intcastleId=atoi(row[1]);
time_tstartTime=strtotime(row[2]);
time_tendTime=strtotime(row[3]);
SiegeScheduleschedule(idcastleIdstartTimeendTime);
m_siegeSchedules.push_back(schedule);
}
mysql_free_result(result);
SystemLog::LogInfo("Loaded%dsiegeschedulesfromdatabase."m_siegeSchedules.size());
}
voidSiegeManager::StartSiege(intcastleId)
{
autoit=m_castles.find(castleId);
if(it==m_castles.end())
{
SystemLog::LogWarning("Castle[%d]notfound."castleId);
return;
}
Castle&castle=it->second;
castle.SetOccupied(true);
SystemLog::LogInfo("Startedsiegeforcastle[%s]."castle.GetName().c_str());
//Notifyclientsaboutthestartofthesiege
CPacketBuilderpacket(PACKET_TYPE_SIEGE_START_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}
voidSiegeManager::EndSiege(intcastleId)
{
autoit=m_castles.find(castleId);
if(it==m_castles.end())
{
SystemLog::LogWarning("Castle[%d]notfound."castleId);
return;
}
Castle&castle=it->second;
castle.SetOccupied(false);
SystemLog::LogInfo("Endedsiegeforcastle[%s]."castle.GetName().c_str());
//Notifyclientsabouttheendofthesiege
CPacketBuilderpacket(PACKET_TYPE_SIEGE_END_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}
voidSiegeManager::CheckAndProcessSieges()
{
time_tcurrentTime=time(nullptr);
for(constSiegeSchedule&schedule:m_siegeSchedules)
{
if(currentTime>=schedule.GetStartTime()&¤tTime<=schedule.GetEndTime())
{
StartSiege(schedule.GetCastleId());
}
elseif(currentTime>schedule.GetEndTime())
{
EndSiege(schedule.GetCastleId());
}
}
}
```
######修改`character.cpp`
在`src\character.cpp`文件中添加参与攻城的逻辑。
**character.cpp**
```cpp
#include"character.h"
Character::Character(intid)
{
m_id=id;
}
voidCharacter::JoinSiege(intcastleId)
{
if(m_currentSiegeCastle!=-1)
{
SystemLog::LogWarning("Character[%d]isalreadyinasiege."m_id);
return;
}
m_currentSiegeCastle=castleId;
SystemLog::LogInfo("Character[%d]joinedsiegeforcastle[%d]."m_idcastleId);
//Notifyserveraboutjoiningthesiege
CPacketBuilderpacket(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(m_id);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
voidCharacter::LeaveSiege()
{
if(m_currentSiegeCastle==-1)
{
SystemLog::LogWarning("Character[%d]isnotinanysiege."m_id);
return;
}
intcastleId=m_currentSiegeCastle;
m_currentSiegeCastle=-1;
SystemLog::LogInfo("Character[%d]leftsiegeforcastle[%d]."m_idcastleId);
//Notifyserveraboutleavingthesiege
CPacketBuilderpacket(PACKET_TYPE_LEAVE_SIEGE_REQUEST);
packet.WriteInt(m_id);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
```
######修改`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::SendJoinSiegeRequest(intcastleId)
{
CPacketBuilderpacket(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}
voidCClientNetwork::SendLeaveSiegeRequest()
{
CPacketBuilderpacket(PACKET_TYPE_LEAVE_SIEGE_REQUEST);
SendPacketToGameServer(packet.Build());
}
voidCClientNetwork::HandleSiegeStartNotify(constPacket&packet)
{
intcastleId=packet.ReadInt();
SystemLog::LogInfo("Siegestartedforcastle[%d]."castleId);
//UpdateUItoshowsiegestatus
}
voidCClientNetwork::HandleSiegeEndNotify(constPacket&packet)
{
intcastleId=packet.ReadInt();
SystemLog::LogInfo("Siegeendedforcastle[%d]."castleId);
//UpdateUItoshowsiegestatus
}
```
#####步骤六:编译并测试
确保所有修改后的代码都能成功编译。
**编译服务器端**
```sh
g++-ogame_serversrc/game_server.cppsrc/database_manager.cppsrc/siege_manager.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:Loaded2castlesfromdatabase.
[2023-10-0112:34:56]INFO:Loaded2siegeschedulesfromdatabase.
[2023-10-0120:00:00]INFO:Startedsiegeforcastle[风之城堡].
[2023-10-0122:00:00]INFO:Endedsiegeforcastle[风之城堡].
```
根据日志中的信息,确认游戏服务器是否正常运行以及攻城事件是否正确触发。
#####查看客户端日志
打开客户端的日志文件(通常位于`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-0120:00:00]INFO:Siegestartedforcastle[风之城堡].
[2023-10-0122:00:00]INFO:Siegeendedforcastle[风之城堡].
```
根据日志中的信息,确认客户端是否正确接收了服务器的攻城通知。
####5.常见问题及解决方案
#####问题一:无法连接到游戏服务器
-**检查网络设置**:确保客户端和游戏服务器之间的网络连接正常。
-**检查配置文件**:确保`client_config.txt`中的游戏服务器IP和端口配置正确。
-**检查防火墙设置**:确保防火墙没有阻止游戏服务器的端口。
#####问题二:登录失败
-**检查数据库配置**:确保`auth_config.txt`中的数据库配置正确。
-**检查数据库服务**:确保数据库服务正在运行并且可以访问。
-**检查用户数据**:确保`account_table`中包含正确的用户信息。
#####问题三:角色加载失败
-**检查角色数据**:确保`char_table`中包含正确的角色信息。
-**检查物品数据**:确保`item_table`中包含正确的物品信息。
-**检查技能数据**:确保`skill_table`中包含正确的技能信息。
#####问题四:客户端版本不匹配
-**更新客户端**:确保客户端版本与服务器版本兼容。
-**同步资源文件**:确保客户端和服务器之间的资源文件一致。
#####问题五:攻城未触发
-**检查日志文件**:查看日志文件以确定是否有攻城触发的相关记录。
-**检查配置文件**:确保`game_config.txt`中的攻城配置正确。
-**检查时间设置**:确保当前时间符合攻城日程的时间设置。
#####问题六:参与攻城失败
-**检查玩家人数**:确保参与攻城的人数达到最低要求。
-**检查角色状态**:确保角色处于可参与攻城的状态(如不在其他活动中)。
-**检查日志文件**:查看日志文件以确定是否有参与攻城失败的记录。
####6.总结
通过以上步骤,你应该能够在GOM传奇引擎中成功开启并配置攻城功能。这不仅增加了游戏的竞争性和策略性,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!

