传奇引擎开启攻城全攻略:GOM/HERO/LEG三大引擎配置详解

来源: 作者: 点击:
攻城战是传奇类游戏的核心玩法,但不同引擎(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
CheckGold 5000000
#ACT
TakeGold 5000000
SetApplyAttackWar 1 ; 设置行会为攻方
SysMsg “攻城申请成功!”
```


#### **步骤3:启用机器人定时器**
路径:`Mir200\Envir\Robot.txt`
```ini
#AutoRun NPC SEC 10 @CheckCastleWar
```

脚本(`Robot_def\AutoRun.txt`):
```lua
[@CheckCastleWar]
#IF
EQUAL <$CASTLEWARSTATUS> 0
HOUR 19 MIN 55 ; 提前5分钟准备
#ACT
StartCastleWar ; 触发攻城战
SysMsg “沙巴克攻城战即将开始!”
```


#### **步骤4:奖励发放逻辑**
皇宫内NPC脚本(`Market_Def\沙巴克_3.txt`):
```lua
[@WinGuild]
#IF
IsCastleMaster ; 检测是否为占领行会
#ACT
AddGuildCredit 2000 ; 行会资金
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:UPDATE Castle SET OwnerGuild='行会名' |


---

### 六、进阶玩法:自定义规则

#### **1. 多重城门攻破机制**
在沙巴克城墙地图(如D716)添加破坏触发器:
```lua
[@DestroyGate]
#IF
CheckMonMap D716 0 ; 检测地图无守卫
#ACT
OpenMainGate ; 开启主城门
```


#### **2. 动态税收与奖励**
根据占领时长调整税率:
```lua
[@DailyTax]
#IF
CheckCastleHoldDays > 7
#ACT
SetIncomeGold 1500000 ; 占领超7天税率提升50%
```


#### **3. 攻城BOSS刷新**
在皇宫内定时刷新BOSS(赤月恶魔):
```lua
[@CastleBoss]
#IF
Random 10 ; 10%概率刷新
#ACT
MobGen 330 330 赤月恶魔 1
```


---

#### 结语
通过精准的引擎配置与脚本定制,攻城战可成为玩家活跃的核心驱动力。重点在于测试阶段的全流程验证(申请-战斗-占领-奖励),并建立日志监控体系(如M2Server.log)快速定位异常。对于高并发服,建议采用分线攻沙或动态副本机制以优化体验。

#### 1. 攻城功能概述

##### 什么是攻城?
攻城战是一种团队对抗活动,通常发生在两个或多个势力之间,争夺某个特定区域(如城堡、城市)的控制权。攻城战不仅增加了游戏的竞争性和策略性,还能为玩家带来丰厚的奖励。

#### 2. GOM引擎简介

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

##### 支持攻城功能
GOM引擎内置了对攻城战的支持,只需要正确配置即可启用该项功能。

#### 3. 开启攻城功能步骤

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

##### 步骤二:配置攻城数据表
在数据库中创建或修改相关的攻城数据表,以存储攻城相关信息。

**创建攻城数据表**
```sql
CREATE TABLE castle (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
owner_id INT DEFAULT 0, -- 当前占领者ID,默认为0表示无人占领
last_capture_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

CREATE TABLE siege_schedule (
id INT AUTO_INCREMENT PRIMARY KEY,
castle_id INT NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
FOREIGN KEY (castle_id) REFERENCES castle(id)
);
```

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

**插入城堡数据**
```sql
INSERT INTO castle (name, owner_id) VALUES ('风之城堡', 0);
INSERT INTO castle (name, owner_id) VALUES ('火之城堡', 0);
```

**插入攻城日程数据**
```sql
INSERT INTO siege_schedule (castle_id, start_time, end_time) VALUES (1, '2023-10-01 20:00:00', '2023-10-01 22:00:00');
INSERT INTO siege_schedule (castle_id, start_time, end_time) VALUES (2, '2023-10-02 20:00:00', '2023-10-02 22: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()
{
static SiegeManager instance;
return &instance;
}

SiegeManager::SiegeManager()
{
LoadCastlesFromDatabase();
LoadSiegeSchedulesFromDatabase();
}

void SiegeManager::LoadCastlesFromDatabase()
{
DatabaseManager* dbManager = DatabaseManager::GetInstance();
MYSQL_RES* result = dbManager->Query("SELECT * FROM castle");
if (!result)
{
SystemLog::LogError("Failed to load castles from database.");
return;
}

MYSQL_ROW row;
while ((row = mysql_fetch_row(result)))
{
int id = atoi(row[0]);
std::string name = row[1];
int ownerId = atoi(row[2]);

Castle castle(id, name, ownerId);
m_castles[id] = castle;
}

mysql_free_result(result);
SystemLog::LogInfo("Loaded %d castles from database.", m_castles.size());
}

void SiegeManager::LoadSiegeSchedulesFromDatabase()
{
DatabaseManager* dbManager = DatabaseManager::GetInstance();
MYSQL_RES* result = dbManager->Query("SELECT * FROM siege_schedule");
if (!result)
{
SystemLog::LogError("Failed to load siege schedules from database.");
return;
}

MYSQL_ROW row;
while ((row = mysql_fetch_row(result)))
{
int id = atoi(row[0]);
int castleId = atoi(row[1]);
time_t startTime = strtotime(row[2]);
time_t endTime = strtotime(row[3]);

SiegeSchedule schedule(id, castleId, startTime, endTime);
m_siegeSchedules.push_back(schedule);
}

mysql_free_result(result);
SystemLog::LogInfo("Loaded %d siege schedules from database.", m_siegeSchedules.size());
}

void SiegeManager::StartSiege(int castleId)
{
auto it = m_castles.find(castleId);
if (it == m_castles.end())
{
SystemLog::LogWarning("Castle [%d] not found.", castleId);
return;
}

Castle& castle = it->second;
castle.SetOccupied(true);
SystemLog::LogInfo("Started siege for castle [%s].", castle.GetName().c_str());

// Notify clients about the start of the siege
CPacketBuilder packet(PACKET_TYPE_SIEGE_START_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}

void SiegeManager::EndSiege(int castleId)
{
auto it = m_castles.find(castleId);
if (it == m_castles.end())
{
SystemLog::LogWarning("Castle [%d] not found.", castleId);
return;
}

Castle& castle = it->second;
castle.SetOccupied(false);
SystemLog::LogInfo("Ended siege for castle [%s].", castle.GetName().c_str());

// Notify clients about the end of the siege
CPacketBuilder packet(PACKET_TYPE_SIEGE_END_NOTIFY);
packet.WriteInt(castle.GetId());
SendPacketToAllClients(packet.Build());
}

void SiegeManager::CheckAndProcessSieges()
{
time_t currentTime = time(nullptr);
for (const SiegeSchedule& schedule : m_siegeSchedules)
{
if (currentTime >= schedule.GetStartTime() && currentTime <= schedule.GetEndTime())
{
StartSiege(schedule.GetCastleId());
}
else if (currentTime > schedule.GetEndTime())
{
EndSiege(schedule.GetCastleId());
}
}
}
```

###### 修改`character.cpp`
在`src\character.cpp`文件中添加参与攻城的逻辑。

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

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

void Character::JoinSiege(int castleId)
{
if (m_currentSiegeCastle != -1)
{
SystemLog::LogWarning("Character [%d] is already in a siege.", m_id);
return;
}

m_currentSiegeCastle = castleId;
SystemLog::LogInfo("Character [%d] joined siege for castle [%d].", m_id, castleId);

// Notify server about joining the siege
CPacketBuilder packet(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(m_id);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}

void Character::LeaveSiege()
{
if (m_currentSiegeCastle == -1)
{
SystemLog::LogWarning("Character [%d] is not in any siege.", m_id);
return;
}

int castleId = m_currentSiegeCastle;
m_currentSiegeCastle = -1;
SystemLog::LogInfo("Character [%d] left siege for castle [%d].", m_id, castleId);

// Notify server about leaving the siege
CPacketBuilder packet(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;
}

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::SendJoinSiegeRequest(int castleId)
{
CPacketBuilder packet(PACKET_TYPE_JOIN_SIEGE_REQUEST);
packet.WriteInt(castleId);
SendPacketToGameServer(packet.Build());
}

void CClientNetwork::SendLeaveSiegeRequest()
{
CPacketBuilder packet(PACKET_TYPE_LEAVE_SIEGE_REQUEST);
SendPacketToGameServer(packet.Build());
}

void CClientNetwork::HandleSiegeStartNotify(const Packet& packet)
{
int castleId = packet.ReadInt();
SystemLog::LogInfo("Siege started for castle [%d].", castleId);
// Update UI to show siege status
}

void CClientNetwork::HandleSiegeEndNotify(const Packet& packet)
{
int castleId = packet.ReadInt();
SystemLog::LogInfo("Siege ended for castle [%d].", castleId);
// Update UI to show siege status
}
```

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

**编译服务器端**
```sh
g++ -o game_server src/game_server.cpp src/database_manager.cpp src/siege_manager.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: Loaded 2 castles from database.
[2023-10-01 12:34:56] INFO: Loaded 2 siege schedules from database.
[2023-10-01 20:00:00] INFO: Started siege for castle [风之城堡].
[2023-10-01 22:00:00] INFO: Ended siege for castle [风之城堡].
```

根据日志中的信息,确认游戏服务器是否正常运行以及攻城事件是否正确触发。

##### 查看客户端日志
打开客户端的日志文件(通常位于`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 20:00:00] INFO: Siege started for castle [风之城堡].
[2023-10-01 22:00:00] INFO: Siege ended for castle [风之城堡].
```

根据日志中的信息,确认客户端是否正确接收了服务器的攻城通知。

#### 5. 常见问题及解决方案

##### 问题一:无法连接到游戏服务器
- **检查网络设置**:确保客户端和游戏服务器之间的网络连接正常。
- **检查配置文件**:确保`client_config.txt`中的游戏服务器IP和端口配置正确。
- **检查防火墙设置**:确保防火墙没有阻止游戏服务器的端口。

##### 问题二:登录失败
- **检查数据库配置**:确保`auth_config.txt`中的数据库配置正确。
- **检查数据库服务**:确保数据库服务正在运行并且可以访问。
- **检查用户数据**:确保`account_table`中包含正确的用户信息。

##### 问题三:角色加载失败
- **检查角色数据**:确保`char_table`中包含正确的角色信息。
- **检查物品数据**:确保`item_table`中包含正确的物品信息。
- **检查技能数据**:确保`skill_table`中包含正确的技能信息。

##### 问题四:客户端版本不匹配
- **更新客户端**:确保客户端版本与服务器版本兼容。
- **同步资源文件**:确保客户端和服务器之间的资源文件一致。

##### 问题五:攻城未触发
- **检查日志文件**:查看日志文件以确定是否有攻城触发的相关记录。
- **检查配置文件**:确保`game_config.txt`中的攻城配置正确。
- **检查时间设置**:确保当前时间符合攻城日程的时间设置。

##### 问题六:参与攻城失败
- **检查玩家人数**:确保参与攻城的人数达到最低要求。
- **检查角色状态**:确保角色处于可参与攻城的状态(如不在其他活动中)。
- **检查日志文件**:查看日志文件以确定是否有参与攻城失败的记录。

#### 6. 总结
通过以上步骤,你应该能够在GOM传奇引擎中成功开启并配置攻城功能。这不仅增加了游戏的竞争性和策略性,还提升了玩家的游戏体验。希望这篇教程对你有所帮助!