Commit 85d98d58 authored by Nemo Ma's avatar Nemo Ma

BUGFIX: assorted fixes

parent 8dfad3ce
BGM播放卡顿问题修复记录
时间:2025-06-14
问题:播放新歌单时出现AbortError错误
== 问题分析 ==
1. Console错误信息:
- "The play() request was interrupted by a new load request"
- "The play() request was interrupted by a call to pause()"
- 错误发生在game20130526.js:583和game.php:1
2. 根本原因:
- changeBGM()函数中,load()和play()方法调用过于紧密
- 没有处理异步播放的Promise返回值
- 缺少对播放中断的错误处理
- 自动播放可能与用户交互冲突
== 解决方案 ==
1. 修改changeBGM()函数 (include/game20130526.js:566-618):
- 在加载新音频前先暂停当前播放
- 使用Promise处理异步播放请求
- 添加错误捕获和重试机制
- 添加控制台日志用于调试
2. 修改BGM初始化 (include/game.func.php:211-246):
- 移除autoplay属性,改为手动控制
- 添加DOMContentLoaded事件处理自动播放
- 添加beforeunload事件防止页面卸载时的播放错误
- 使用Promise处理自动播放的错误
3. 改进音量控制 (include/game20130526.js:553-576):
- 添加音频元素存在性检查
- 使用try-catch包装音量设置
- 同步更新nowbgmvolume元素
== 技术细节 ==
- 使用audioElement.pause()和currentTime=0确保完全停止
- Promise.then()和.catch()处理异步播放状态
- 100ms延迟重试机制避免快速连续操作冲突
- 控制台日志帮助调试播放状态
== 预期效果 ==
- 消除AbortError错误信息
- 改善歌单切换的流畅性
- 提供更好的错误恢复机制
- 保持音频播放的稳定性
== 第二次修复 (问题仍存在) ==
发现问题根源更深层:
1. 多个地方同时触发音频播放请求
2. DOMContentLoaded自动播放与changeBGM冲突
3. 浏览器的自动播放策略限制
4. 并发的load()和play()调用
== 彻底解决方案 ==
1. 实现全局BGM状态管理 (bgmPlayState):
- 防止并发changeBGM调用
- 跟踪当前播放Promise
- 处理待处理的切换请求
2. 移除自动播放,改为用户交互触发:
- 移除DOMContentLoaded中的自动播放
- 添加首次点击事件监听器
- 使用preload="metadata"预加载
3. 改进changeBGM函数:
- 添加并发保护机制
- 使用canplaythrough事件确保加载完成
- 实现请求队列处理
- 添加适当的延迟避免竞争条件
4. 优化音频元素初始化:
- 使用IIFE避免全局变量污染
- 添加初始化状态检查
- 改进事件监听器管理
== 技术改进 ==
- 状态机模式管理播放状态
- Promise链式处理避免中断
- 事件驱动的播放控制
- 用户交互检测机制
== 第三次修复 (全面重构) ==
实现了完整的BGM管理系统:
1. 创建全局BGM管理器 (window.BGMManager):
- 集中管理所有BGM播放状态
- 防止重复初始化和并发调用
- 实现请求队列和状态跟踪
- 提供统一的API接口
2. 重构changeBGM函数:
- 原函数改为调用BGMManager.changeBGM()
- 保持向后兼容性
- 简化调用逻辑
3. 改进BGM初始化:
- 添加唯一时间戳防止重复初始化
- 清理旧的事件监听器
- 优先使用BGMManager处理事件
4. 增强播放控制:
- 使用readyState检查音频加载状态
- 实现智能重试机制
- 添加详细的控制台日志
== 核心改进 ==
- 单例模式的BGM管理器
- 状态机驱动的播放控制
- 防重复初始化机制
- 智能的音频加载检测
- 完善的错误处理和恢复
== 测试建议 ==
1. 快速连续切换BGM测试
2. 页面刷新时的音频状态测试
3. 音量调节时的播放稳定性测试
4. 不同浏览器的兼容性测试
5. 用户首次交互后的自动播放测试
6. 并发播放请求处理测试
7. 长时间运行稳定性测试
# 枫火歌者种火数据结构修复
时间:2025-06-14
## 问题描述
根据测试玩家反馈和个人推定,枫火歌者的设计与实现存在以下逻辑问题:
1. **数据冗余问题**:被收纳的种火的icon、hp、mhp、sp、msp、att、def、pls以及身上的装备等本已包含在players表其自己的数据目前存在了clbpara中,这导致侧边栏显示的对应数据和实际这些NPC的数据有时会不同步,clbpara中的信息应是对应这些NPC数据的指针,从而减少clbpara本身的长度,并保证玩家看见的数据一直和players表中的实际数据一致。
2. **死亡状态检查缺失**:玩家可以对被杀死(hp = 0)或者被尸体销毁(hp = 0且pls=254)的种火进行操作,且它们会正常运用跟随和索敌逻辑,这是不符合逻辑的——玩家不能对死亡或者被尸体销毁的种火进行操作,它们也不应该参与跟随,索敌以及提升玩家数值的逻辑。
## 修复方案
### 1. 修改种火数据存储结构
**修改文件**: `include/game/club22.func.php`
- 在 `FireseedRecruit` 函数中,将种火数据存储从完整复制改为指针式存储
- 只在 `clbpara['fireseed']` 中保留管理信息:
- `level`: 强化等级
- `mode`: 部署模式
- `horizon`: 视界状态
- `items`: 探物收集的物品
- `recruited_time`: 收纳时间
**修改前**:
```php
$clbpara['fireseed'][$fireseed_id] = array(
'name' => $npc['name'],
'icon' => $npc['icon'],
'level' => 1,
'mode' => 0,
'pls' => $pls,
'horizon' => 0,
'hp' => $npc['mhp'],
'mhp' => $npc['mhp'],
// ... 更多冗余数据
);
```
**修改后**:
```php
$clbpara['fireseed'][$fireseed_id] = array(
'level' => 1,
'mode' => 0,
'horizon' => 0,
'items' => array(),
'recruited_time' => $now
);
```
### 2. 添加辅助函数
新增两个辅助函数来处理种火数据:
```php
function getFireseedRealTimeData($fireseed_id) {
// 从players表获取种火实时数据
// 检查种火是否死亡或被销毁
// 返回完整的种火数据或null
}
function isFireseedAlive($fireseed_id) {
// 检查种火是否存活且可用
// 返回布尔值
}
```
### 3. 修改种火行为函数
在所有种火行为函数中添加死亡状态检查:
- `FireseedSearch`: 探物逻辑
- `FireseedDrainNPC`: 索敌逻辑
- `FireseedFollow`: 跟随逻辑
- `FireseedBuffBonus`: 属性加成逻辑
- `FireseedDeploy`: 部署逻辑
- `FireseedEnhance`: 强化逻辑
每个函数在处理种火前都会调用 `isFireseedAlive()` 检查种火状态。
### 4. 创建实时数据API
**新增文件**: `fireseed_data.php`
提供种火实时数据的API接口,返回JSON格式的数据:
- 合并管理数据(来自clbpara)和实时数据(来自players表)
- 标记死亡种火的状态
- 供前端JavaScript调用
### 5. 修改前端显示逻辑
**修改文件**: `templates/default/slidingpanel.htm`
- 修改 `updateFireseedStatusTable()` 函数
- 使用新的API获取实时数据
- 添加 `fetchFireseedRealTimeData()` 函数
- 添加备用显示逻辑 `updateFireseedStatusTableFallback()`
## 修复效果
1. **数据一致性**: 侧边栏显示的种火数据现在直接来自players表,确保与实际状态一致
2. **死亡状态处理**: 死亡或被销毁的种火不再参与任何游戏逻辑
3. **性能优化**: 减少了clbpara中的数据冗余,降低了存储开销
4. **代码维护性**: 统一了数据获取方式,便于后续维护
## 测试建议
1. 测试种火收纳功能
2. 测试种火部署到不同模式
3. 测试种火死亡后的状态显示
4. 测试种火强化功能
5. 测试侧边栏数据更新
6. 测试种火跟随、探物、索敌逻辑
## 问题修复记录
### 问题:HTTP 500错误 - fetch_playerdata_by_name函数未定义
**错误信息**:
```
PHP Fatal error: Uncaught Error: Call to undefined function fetch_playerdata_by_name() in fireseed_data.php:17
```
**原因分析**:
`fireseed_data.php` 文件中调用了 `fetch_playerdata_by_name()` 函数,但没有包含定义该函数的文件 `include/game.func.php`。
**修复方案**:
在 `fireseed_data.php` 中添加必要的包含文件:
```php
include_once './include/common.inc.php';
include_once GAME_ROOT.'./include/game.func.php'; // 新增
include_once GAME_ROOT.'./include/game/club22.func.php';
```
**修复时间**:2025-06-14
### 问题:种火重复显示
**问题描述**:
侧边栏中每个种火被显示了两次,相同ID和内容的种火出现重复行。
**原因分析**:
1. `updateFireseedStatusTable()` 函数中表格清空逻辑不完善
2. 备用函数 `updateFireseedStatusTableFallback()` 中存在语法错误
3. 前端验证逻辑仍在检查已不存在的 `name` 字段
**修复方案**:
1. 在获取实时数据后再次确保表格清空
2. 修复备用函数中缺少 `insertRow()` 的错误
3. 更新前端验证逻辑,移除对 `name` 字段的检查
**修复时间**:2025-06-14
### 问题:种火强化HTTP 500错误和探物功能失效
**问题描述**:
1. 种火强化时返回HTTP 500错误:`Call to a member function query() on null`
2. 种火探物功能失去原有功能
**原因分析**:
1. **数据库错误**:`FireseedEnhance` 函数中缺少 `$db` 和 `$tablepre` 全局变量声明
2. **探物逻辑问题**:种火行为检查逻辑不够健壮,没有正确处理新旧数据格式的兼容性
**修复方案**:
1. **修复数据库错误**:
```php
function FireseedEnhance($fireseed_id, $item_index) {
global $log, $fireseed_enhance_multipliers, $db, $tablepre; // 添加缺失的全局变量
```
2. **改进种火行为检查逻辑**:
- 优先检查 `pose` 字段(新数据格式)
- 如果没有 `pose` 字段,则使用 `mode` 字段(旧数据兼容)
- 统一所有种火行为函数的检查逻辑
**修复时间**:2025-06-14
## 注意事项
1. 现有的种火数据需要进行迁移,旧的冗余数据会被忽略
2. 前端需要处理API调用失败的情况
3. 死亡种火的物品仍然保留在clbpara中,可以被获取
4. `fireseed_data.php` 需要正确包含所有依赖的函数文件
<?php
if(!defined('IN_GAME')) {
define('IN_GAME', true);
}
include_once './include/common.inc.php';
include_once GAME_ROOT.'./include/game.func.php';
include_once GAME_ROOT.'./include/game/club22.func.php';
// 检查用户是否登录
if(empty($cuser)) {
echo json_encode(array('error' => 'Not logged in'));
exit;
}
// 获取玩家数据
$pdata = fetch_playerdata_by_name($cuser);
if(!$pdata) {
echo json_encode(array('error' => 'Player data not found'));
exit;
}
// clbpara 已经在 fetch_playerdata_by_name 中通过 check_player_misc_states 处理过了
// 检查是否为枫火歌者
if($pdata['club'] != 22) {
echo json_encode(array('error' => 'Not a Fireseed Singer'));
exit;
}
// 获取种火实时数据
$fireseed_realtime_data = array();
if(!empty($pdata['clbpara']['fireseed'])) {
foreach($pdata['clbpara']['fireseed'] as $fs_id => $fs_data) {
// 获取种火实时数据
$realtime_data = getFireseedRealTimeData($fs_id);
if($realtime_data) {
// 合并管理数据和实时数据
$fireseed_realtime_data[$fs_id] = array(
// 管理数据(来自 clbpara)
'level' => $fs_data['level'],
'mode' => $fs_data['mode'],
'horizon' => isset($fs_data['horizon']) ? $fs_data['horizon'] : 0,
'items' => isset($fs_data['items']) ? $fs_data['items'] : array(),
'recruited_time' => isset($fs_data['recruited_time']) ? $fs_data['recruited_time'] : 0,
'pose' => isset($fs_data['pose']) ? $fs_data['pose'] : null,
// 实时数据(来自 players 表)
'name' => $realtime_data['name'],
'icon' => $realtime_data['icon'],
'hp' => $realtime_data['hp'],
'mhp' => $realtime_data['mhp'],
'sp' => $realtime_data['sp'],
'msp' => $realtime_data['msp'],
'att' => $realtime_data['att'],
'def' => $realtime_data['def'],
'pls' => $realtime_data['pls'],
'wep' => $realtime_data['wep'],
'wepk' => $realtime_data['wepk'],
'wepe' => $realtime_data['wepe'],
'weps' => $realtime_data['weps'],
'wepsk' => $realtime_data['wepsk'],
'arb' => $realtime_data['arb'],
'arbk' => $realtime_data['arbk'],
'arbe' => $realtime_data['arbe'],
'arbs' => $realtime_data['arbs'],
'arbsk' => $realtime_data['arbsk'],
'skills' => isset($realtime_data['clbpara']['skill']) && is_array($realtime_data['clbpara']['skill']) ? $realtime_data['clbpara']['skill'] : array(),
'alive' => true
);
} else {
// 种火已死亡或被销毁,但保留基本信息用于显示
$fireseed_realtime_data[$fs_id] = array(
'level' => $fs_data['level'],
'mode' => $fs_data['mode'],
'horizon' => isset($fs_data['horizon']) ? $fs_data['horizon'] : 0,
'items' => isset($fs_data['items']) ? $fs_data['items'] : array(),
'recruited_time' => isset($fs_data['recruited_time']) ? $fs_data['recruited_time'] : 0,
'pose' => isset($fs_data['pose']) ? $fs_data['pose'] : null,
'name' => '已死亡的种火',
'icon' => '',
'hp' => 0,
'mhp' => 0,
'sp' => 0,
'msp' => 0,
'att' => 0,
'def' => 0,
'pls' => 254,
'alive' => false
);
}
}
}
// 返回 JSON 数据
header('Content-Type: application/json; charset=utf-8');
echo json_encode($fireseed_realtime_data, JSON_UNESCAPED_UNICODE);
?>
......@@ -175,9 +175,17 @@ function init_bgm($force_update=0)
}
}
# 防止重复初始化 - 只在真正需要时重载播放器
static $bgm_initialized = false;
if($bgm_initialized && !$force_update && $command != 'enter')
{
return '';
}
# 刷新页面或输入强制重载参数时,重载播放器
if($command == 'enter' || $force_update)
if($command == 'enter' || $force_update || !$bgm_initialized)
{
$bgm_initialized = true;
$booklist = $bgmarr = Array();
# 为播放列表中的曲集关联对应BGM名、链接与种类
foreach($clbpara['bgmbook'] as $book)
......@@ -208,17 +216,69 @@ function init_bgm($force_update=0)
# 生成播放器与播放队列 太野蛮了……嘻嘻……
if(!empty($bgmlink) && !empty($bgmtype))
{
$bgm_timestamp = time() . rand(1000, 9999); // 唯一标识符
$bgmplayer = <<<EOT
<audio id="gamebgm" autoplay controls=1" onplay="$('gamebgm').volume=$('nowbgmvolume').innerHTML;">
<audio id="gamebgm" controls="1" preload="metadata" data-bgm-init="$bgm_timestamp">
<source id="gbgm" src="$bgmlink" type="$bgmtype">
</audio>
<div id="bgmlist">$json_bgmarr</div>
<div id="nowbgm">0</div>
<div id="nowbgmvolume">$volume_r</div>
<script>
gamebgm.addEventListener('ended', function () {
// 防止重复初始化BGM播放器
(function() {
var currentTimestamp = '$bgm_timestamp';
// 检查是否已经初始化过相同的实例
if (window.bgmInitTimestamp === currentTimestamp) {
console.log('BGM播放器已存在,跳过重复初始化');
return;
}
window.bgmInitTimestamp = currentTimestamp;
var audioElement = document.getElementById('gamebgm');
if (!audioElement) {
console.log('BGM音频元素不存在');
return;
}
// 清理之前的事件监听器
if (window.bgmEndedHandler) {
audioElement.removeEventListener('ended', window.bgmEndedHandler);
}
function initBGM() {
audioElement.volume = $volume_r;
// 创建新的事件处理器
window.bgmEndedHandler = function() {
if (window.BGMManager && typeof BGMManager.changeBGM === 'function') {
BGMManager.changeBGM();
} else if (typeof changeBGM === 'function') {
changeBGM();
}, false);
}
};
// 监听音频结束事件
audioElement.addEventListener('ended', window.bgmEndedHandler, false);
console.log('BGM播放器已初始化 (ID: ' + currentTimestamp + ')');
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBGM);
} else {
initBGM();
}
// 防止页面卸载时的播放中断错误
window.addEventListener('beforeunload', function() {
if (audioElement) {
audioElement.pause();
}
});
})();
</script>
EOT;
}
......
......@@ -4,6 +4,45 @@ if(!defined('IN_GAME')) exit('Access Denied');
include_once GAME_ROOT.'./gamedata/cache/club22cfg.php';
/**
* 获取种火的实时数据
*
* @param int $fireseed_id 种火ID
* @return array|null 种火数据,如果种火不存在或已死亡则返回null
*/
function getFireseedRealTimeData($fireseed_id) {
global $db, $tablepre;
$result = $db->query("SELECT * FROM {$tablepre}players WHERE pid='$fireseed_id' AND type=92");
if($db->num_rows($result) == 0) {
return null; // 种火不存在
}
$fireseed_data = $db->fetch_array($result);
// 检查种火是否死亡或被销毁
if($fireseed_data['hp'] <= 0 || $fireseed_data['pls'] == 254) {
return null; // 种火已死亡或被销毁
}
// 确保 clbpara 是数组格式
if(!is_array($fireseed_data['clbpara'])) {
$fireseed_data['clbpara'] = get_clbpara($fireseed_data['clbpara']);
}
return $fireseed_data;
}
/**
* 检查种火是否存活且可用
*
* @param int $fireseed_id 种火ID
* @return bool 种火是否存活且可用
*/
function isFireseedAlive($fireseed_id) {
return getFireseedRealTimeData($fireseed_id) !== null;
}
/**
* 将目标种火复活,并进行收纳种火
*
......@@ -51,32 +90,12 @@ function FireseedRecruit($npc) {
// 生成种火ID
$fireseed_id = $npc['pid'];
// 记录种火数据
// 记录种火数据 - 使用指针式存储,减少数据冗余
$clbpara['fireseed'][$fireseed_id] = array(
'name' => $npc['name'],
'icon' => $npc['icon'],
'level' => 1, // 初始等级
'mode' => 0, // 初始模式:跟随
'pls' => $pls, // 初始位置
'horizon' => 0, // 初始位于通常视界而非灵子视界
'hp' => $npc['mhp'],
'mhp' => $npc['mhp'],
'sp' => $npc['msp'],
'msp' => $npc['msp'],
'att' => $npc['att'],
'def' => $npc['def'],
'skills' => isset($npc['clbpara']['skill']) && is_array($npc['clbpara']['skill']) ? $npc['clbpara']['skill'] : array(),
'items' => array(), // 探物模式下收集的物品
'wep' => $npc['wep'],
'wepk' => $npc['wepk'],
'wepe' => $npc['wepe'],
'weps' => $npc['weps'],
'wepsk' => $npc['wepsk'],
'arb' => $npc['arb'],
'arbk' => $npc['arbk'],
'arbe' => $npc['arbe'],
'arbs' => $npc['arbs'],
'arbsk' => $npc['arbsk'],
'recruited_time' => $now
);
......@@ -122,6 +141,12 @@ function FireseedDeploy($fireseed_id, $mode, $deploypls = 0) {
return false;
}
// 检查种火是否存活
if(!isFireseedAlive($fireseed_id)) {
$log .= "<span class='red'>指定的种火已死亡或被销毁,无法部署!</span><br>";
return false;
}
// 检查模式是否有效
if(!isset($fireseed_deploy_modes[$mode])) {
$log .= "<span class='red'>无效的部署模式!</span><br>";
......@@ -173,7 +198,11 @@ function FireseedDeploy($fireseed_id, $mode, $deploypls = 0) {
$location = $plsinfo[$fspls];
$pose_name = $poseinfo[$pose];
$log .= "<span class='lime'>你将种火「{$clbpara['fireseed'][$fireseed_id]['name']}」的状态设置为「{$mode_name}」({$pose_name})";
// 获取种火名称
$fireseed_data = getFireseedRealTimeData($fireseed_id);
$fireseed_name = $fireseed_data ? $fireseed_data['name'] : '未知种火';
$log .= "<span class='lime'>你将种火「{$fireseed_name}」的状态设置为「{$mode_name}」({$pose_name})";
$log .= ",并部署在了「{$location}」";
$log .= "。</span><br>";
......@@ -200,15 +229,33 @@ function FireseedSearch($pls) {
return;
}
// 查找所有处于探物模式的种火,不限制地图位置
// 查找所有处于探物模式且存活的种火,不限制地图位置
$search_fireseeds = array();
foreach($clbpara['fireseed'] as $fs_id => $fs_data) {
// 使用 pose 值 3 表示探物姿态
if(isset($fs_data['pose']) && $fs_data['pose'] == 3) {
$search_fireseeds[$fs_id] = $fs_data;
} else if($fs_data['mode'] == 1) {
// 兼容旧数据,如果没有 pose 字段,则使用 mode
$search_fireseeds[$fs_id] = $fs_data;
// 检查种火是否存活
if(!isFireseedAlive($fs_id)) {
continue; // 跳过死亡或被销毁的种火
}
// 获取种火实时数据
$realtime_data = getFireseedRealTimeData($fs_id);
if(!$realtime_data) {
continue;
}
// 检查是否为探物模式
$is_search_mode = false;
// 优先检查 pose 字段(新数据)
if(isset($fs_data['pose'])) {
$is_search_mode = ($fs_data['pose'] == 3); // 探物姿态
} else {
// 兼容旧数据,使用 mode 字段
$is_search_mode = ($fs_data['mode'] == 1); // 探物模式
}
if($is_search_mode) {
$search_fireseeds[$fs_id] = array_merge($fs_data, array('pls' => $realtime_data['pls']));
}
}
......@@ -267,7 +314,11 @@ function FireseedSearch($pls) {
// 从地图上移除物品
$db->query("DELETE FROM {$tablepre}mapitem WHERE iid='{$item_data['iid']}'");
$log .= "<span class='lime'>你的种火「{$clbpara['fireseed'][$finder_id]['name']}」在「{$plsinfo[$location]}」发现了物品「{$item_data['itm']}」!</span><br>";
// 获取种火名称
$finder_data = getFireseedRealTimeData($finder_id);
$finder_name = $finder_data ? $finder_data['name'] : '未知种火';
$log .= "<span class='lime'>你的种火「{$finder_name}」在「{$plsinfo[$location]}」发现了物品「{$item_data['itm']}」!</span><br>";
}
// 如果有任何种火成功探物,更新玩家的 clbpara
......@@ -298,15 +349,36 @@ function FireseedDrainNPC($pls) {
return;
}
// 查找所有处于索敌模式的种火,不限制地图位置
// 查找所有处于索敌模式且存活的种火,不限制地图位置
$drain_fireseeds = array();
foreach($clbpara['fireseed'] as $fs_id => $fs_data) {
// 使用 pose 值 2 表示强袭姿态(索敌)
if(isset($fs_data['pose']) && $fs_data['pose'] == 2) {
$drain_fireseeds[$fs_id] = $fs_data;
} else if($fs_data['mode'] == 2) {
// 兼容旧数据,如果没有 pose 字段,则使用 mode
$drain_fireseeds[$fs_id] = $fs_data;
// 检查种火是否存活
if(!isFireseedAlive($fs_id)) {
continue; // 跳过死亡或被销毁的种火
}
// 获取种火实时数据
$realtime_data = getFireseedRealTimeData($fs_id);
if(!$realtime_data) {
continue;
}
// 检查是否为索敌模式
$is_drain_mode = false;
// 优先检查 pose 字段(新数据)
if(isset($fs_data['pose'])) {
$is_drain_mode = ($fs_data['pose'] == 2); // 强袭姿态
} else {
// 兼容旧数据,使用 mode 字段
$is_drain_mode = ($fs_data['mode'] == 2); // 索敌模式
}
if($is_drain_mode) {
$drain_fireseeds[$fs_id] = array_merge($fs_data, array(
'pls' => $realtime_data['pls'],
'att' => $realtime_data['att']
));
}
}
......@@ -378,7 +450,11 @@ function FireseedDrainNPC($pls) {
// 更新NPC生命值
$db->query("UPDATE {$tablepre}players SET hp='$new_hp' WHERE pid='{$npc['pid']}'");
$log .= "<span class='lime'>你的种火「{$clbpara['fireseed'][$drainer_id]['name']}」在「{$plsinfo[$location]}」削弱了「{$npc['name']}」,造成了{$drain_amount}点伤害!</span><br>";
// 获取种火名称
$drainer_data = getFireseedRealTimeData($drainer_id);
$drainer_name = $drainer_data ? $drainer_data['name'] : '未知种火';
$log .= "<span class='lime'>你的种火「{$drainer_name}」在「{$plsinfo[$location]}」削弱了「{$npc['name']}」,造成了{$drain_amount}点伤害!</span><br>";
}
}
}
......@@ -391,7 +467,7 @@ function FireseedDrainNPC($pls) {
* @return bool 是否成功强化
*/
function FireseedEnhance($fireseed_id, $item_index) {
global $log, $fireseed_enhance_multipliers;
global $log, $fireseed_enhance_multipliers, $db, $tablepre;
if(!isset($data)) {
global $pdata;
......@@ -405,6 +481,12 @@ function FireseedEnhance($fireseed_id, $item_index) {
return false;
}
// 检查种火是否存活
if(!isFireseedAlive($fireseed_id)) {
$log .= "<span class='red'>指定的种火已死亡或被销毁,无法强化!</span><br>";
return false;
}
// 检查物品是否存在
$item_var = 'itm' . $item_index;
$itemk_var = 'itmk' . $item_index;
......@@ -427,23 +509,32 @@ function FireseedEnhance($fireseed_id, $item_index) {
// 获取强化倍率
$multiplier = $fireseed_enhance_multipliers[$item_name];
// 获取种火当前数据
$fireseed_data = getFireseedRealTimeData($fireseed_id);
if(!$fireseed_data) {
$log .= "<span class='red'>无法获取种火数据!</span><br>";
return false;
}
// 更新种火属性
$old_level = $clbpara['fireseed'][$fireseed_id]['level'];
$clbpara['fireseed'][$fireseed_id]['level'] = $multiplier;
$clbpara['fireseed'][$fireseed_id]['hp'] *= $multiplier / $old_level;
$clbpara['fireseed'][$fireseed_id]['mhp'] *= $multiplier / $old_level;
$clbpara['fireseed'][$fireseed_id]['sp'] *= $multiplier / $old_level;
$clbpara['fireseed'][$fireseed_id]['msp'] *= $multiplier / $old_level;
$clbpara['fireseed'][$fireseed_id]['att'] *= $multiplier / $old_level;
$clbpara['fireseed'][$fireseed_id]['def'] *= $multiplier / $old_level;
// 更新武器和防具效果
if(!empty($clbpara['fireseed'][$fireseed_id]['wepe'])) {
$clbpara['fireseed'][$fireseed_id]['wepe'] *= $multiplier / $old_level;
}
if(!empty($clbpara['fireseed'][$fireseed_id]['arbe'])) {
$clbpara['fireseed'][$fireseed_id]['arbe'] *= $multiplier / $old_level;
}
// 计算新的属性值
$new_hp = ceil($fireseed_data['hp'] * $multiplier / $old_level);
$new_mhp = ceil($fireseed_data['mhp'] * $multiplier / $old_level);
$new_sp = ceil($fireseed_data['sp'] * $multiplier / $old_level);
$new_msp = ceil($fireseed_data['msp'] * $multiplier / $old_level);
$new_att = ceil($fireseed_data['att'] * $multiplier / $old_level);
$new_def = ceil($fireseed_data['def'] * $multiplier / $old_level);
$new_wepe = !empty($fireseed_data['wepe']) ? ceil($fireseed_data['wepe'] * $multiplier / $old_level) : $fireseed_data['wepe'];
$new_arbe = !empty($fireseed_data['arbe']) ? ceil($fireseed_data['arbe'] * $multiplier / $old_level) : $fireseed_data['arbe'];
// 更新数据库中的种火属性
$db->query("UPDATE {$tablepre}players SET
hp='$new_hp', mhp='$new_mhp', sp='$new_sp', msp='$new_msp',
att='$new_att', def='$new_def', wepe='$new_wepe', arbe='$new_arbe'
WHERE pid='$fireseed_id'");
// 消耗物品
$$items_var--;
......@@ -452,9 +543,13 @@ function FireseedEnhance($fireseed_id, $item_index) {
$$iteme_var = $$items_var = 0;
}
$log .= "<span class='lime'>你使用「{$item_name}」强化了种火「{$clbpara['fireseed'][$fireseed_id]['name']}」!</span><br>";
$log .= "<span class='lime'>你使用「{$item_name}」强化了种火「{$fireseed_data['name']}」!</span><br>";
$log .= "<span class='yellow'>种火的强化倍率提升到了{$multiplier}倍!</span><br>";
// 保存玩家的clbpara数据到数据库
$encoded_clbpara = json_encode($clbpara, JSON_UNESCAPED_UNICODE);
$db->query("UPDATE {$tablepre}players SET clbpara='$encoded_clbpara' WHERE pid='$pid'");
return true;
}
......@@ -478,13 +573,33 @@ function FireseedFollow($target_pls) {
return;
}
// 查找所有处于跟随模式的种火
// 查找所有处于跟随模式且存活的种火
$follow_fireseeds = array();
foreach($clbpara['fireseed'] as $fs_id => $fs_data) {
// 使用 pose 值 1 表示作战姿态(跟随)
if((isset($fs_data['pose']) && $fs_data['pose'] == 1) ||
($fs_data['mode'] == 0)) {
$follow_fireseeds[$fs_id] = $fs_data;
// 检查种火是否存活
if(!isFireseedAlive($fs_id)) {
continue; // 跳过死亡或被销毁的种火
}
// 获取种火实时数据
$realtime_data = getFireseedRealTimeData($fs_id);
if(!$realtime_data) {
continue;
}
// 检查是否为跟随模式
$is_follow_mode = false;
// 优先检查 pose 字段(新数据)
if(isset($fs_data['pose'])) {
$is_follow_mode = ($fs_data['pose'] == 1); // 作战姿态
} else {
// 兼容旧数据,使用 mode 字段
$is_follow_mode = ($fs_data['mode'] == 0); // 跟随模式
}
if($is_follow_mode) {
$follow_fireseeds[$fs_id] = array_merge($fs_data, array('pls' => $realtime_data['pls']));
}
}
......@@ -568,13 +683,33 @@ function FireseedBuffBonus($base_att = null, $base_def = null) {
*/
foreach($clbpara['fireseed'] as $fs_id => $fs_data) {
// 检查种火是否存活
if(!isFireseedAlive($fs_id)) {
continue; // 跳过死亡或被销毁的种火
}
// 获取种火实时数据
$realtime_data = getFireseedRealTimeData($fs_id);
if(!$realtime_data) {
continue;
}
/*
$debug_info .= "【种火调试】种火ID: {$fs_id}, 位置: {$fs_data['pls']}, 模式: " . (isset($fs_data['pose']) ? $fs_data['pose'] : $fs_data['mode']) . ", 等级: {$fs_data['level']}<br>";
$debug_info .= "【种火调试】种火ID: {$fs_id}, 位置: {$realtime_data['pls']}, 模式: " . (isset($fs_data['pose']) ? $fs_data['pose'] : $fs_data['mode']) . ", 等级: {$fs_data['level']}<br>";
*/
// 使用 pose 值 1 表示作战姿态(跟随)
if((isset($fs_data['pose']) && $fs_data['pose'] == 1 && $fs_data['pls'] == $pls) ||
($fs_data['mode'] == 0 && $fs_data['pls'] == $pls)) {
// 检查是否为跟随模式且在同一位置
$is_follow_mode = false;
// 优先检查 pose 字段(新数据)
if(isset($fs_data['pose'])) {
$is_follow_mode = ($fs_data['pose'] == 1 && $realtime_data['pls'] == $pls); // 作战姿态且同位置
} else {
// 兼容旧数据,使用 mode 字段
$is_follow_mode = ($fs_data['mode'] == 0 && $realtime_data['pls'] == $pls); // 跟随模式且同位置
}
if($is_follow_mode) {
// 加成 = 数量(1) × 强化层数 × 1%
$bonus_percent = 1 * $fs_data['level'] * $fireseed_follow_bonus_rate;
$att_bonus += ceil($base_att * $bonus_percent / 100);
......
......@@ -551,7 +551,10 @@ function AddMixElements(emix_arr) {
}
function changeVolume(cv){
var v = $('gamebgm').volume;
var audioElement = $('gamebgm');
if (!audioElement) return;
var v = audioElement.volume;
v = v+cv;
v = Math.min(1,v); v = Math.max(0,v); v = v.toFixed(2);
Cookie.setCookie("volume",v, {
......@@ -559,11 +562,79 @@ function changeVolume(cv){
path: "/",
});
s = Math.round(v*100);
$('gamebgm').volume = v;
// 安全地设置音量,避免在播放状态变化时出错
try {
audioElement.volume = v;
$('nowbgmvolume').innerHTML = v;
if ($('volume_num')) {
$('volume_num').innerHTML = s+'%';
}
} catch (error) {
console.log('音量设置失败: ' + error.message);
}
}
function changeBGM(mode=1){
// 全局BGM管理器
window.BGMManager = {
isChanging: false,
currentPromise: null,
pendingChange: null,
isPlaying: false,
userInteracted: false,
initialized: false,
// 初始化BGM管理器
init: function() {
if (this.initialized) return;
this.initialized = true;
// 在任何用户交互时尝试启动BGM
var self = this;
document.addEventListener('click', function() {
if (!self.userInteracted) {
self.startBGM();
}
}, { once: true });
console.log('BGM管理器已初始化');
},
// 启动BGM播放(需要用户交互)
startBGM: function() {
if (this.userInteracted) return;
this.userInteracted = true;
var audioElement = $('gamebgm');
if (audioElement && audioElement.paused) {
var self = this;
var playPromise = audioElement.play();
if (playPromise !== undefined) {
playPromise.then(function() {
self.isPlaying = true;
console.log('BGM开始播放');
}).catch(function(error) {
console.log('BGM启动失败: ' + error.message);
});
}
}
},
// 安全地切换BGM
changeBGM: function(mode) {
mode = mode || 1;
// 防止并发调用
if (this.isChanging) {
this.pendingChange = mode;
console.log('BGM正在切换中,将在完成后执行新的切换请求');
return;
}
this.isChanging = true;
var self = this;
try {
var bgmlist = JSON.parse($('bgmlist').innerHTML);
var nowbgm = Math.round($('nowbgm').innerHTML);
nowbgm = nowbgm + mode;
......@@ -572,17 +643,108 @@ function changeBGM(mode=1){
}else{
nowbgm = nowbgm % bgmlist.length;
}
$('gbgm').src = bgmlist[nowbgm].url;
$('gbgm').type = bgmlist[nowbgm].type;
var audioElement = $('gamebgm');
var sourceElement = $('gbgm');
if (!audioElement || !sourceElement) {
this.isChanging = false;
return;
}
// 取消当前的播放Promise
if (this.currentPromise) {
audioElement.pause();
this.currentPromise = null;
}
// 暂停当前播放并重置
audioElement.pause();
audioElement.currentTime = 0;
// 更新音频源
sourceElement.src = bgmlist[nowbgm].url;
sourceElement.type = bgmlist[nowbgm].type;
$('nowbgm').innerHTML = nowbgm;
Cookie.setCookie("nowbgmid",bgmlist[nowbgm].id, {
path: "/",
});
var v = $('gamebgm').volume;
var v = audioElement.volume;
if ($('bgmname')) {
$('bgmname').innerHTML = bgmlist[nowbgm].name;
$('gamebgm').load();
$('gamebgm').volume = v;
$('gamebgm').play();
}
// 等待一小段时间确保pause()完成
setTimeout(function() {
// 加载新音频
audioElement.load();
audioElement.volume = v;
// 等待load完成后再播放
function tryPlay() {
if (audioElement.readyState >= 3) { // HAVE_FUTURE_DATA
// 使用Promise处理异步播放
self.currentPromise = audioElement.play();
if (self.currentPromise !== undefined) {
self.currentPromise.then(function() {
console.log('BGM播放成功: ' + bgmlist[nowbgm].name);
self.currentPromise = null;
self.isChanging = false;
self.isPlaying = true;
// 处理待处理的切换请求
if (self.pendingChange !== null) {
var pendingMode = self.pendingChange;
self.pendingChange = null;
setTimeout(function() {
self.changeBGM(pendingMode);
}, 100);
}
}).catch(function(error) {
console.log('BGM播放失败: ' + error.message);
self.currentPromise = null;
self.isChanging = false;
// 处理待处理的切换请求
if (self.pendingChange !== null) {
var pendingMode = self.pendingChange;
self.pendingChange = null;
setTimeout(function() {
self.changeBGM(pendingMode);
}, 100);
}
});
} else {
self.isChanging = false;
}
} else {
// 等待音频加载完成
setTimeout(tryPlay, 50);
}
}
tryPlay();
}, 100);
} catch (error) {
console.log('changeBGM错误: ' + error.message);
this.isChanging = false;
this.currentPromise = null;
}
}
};
// 初始化BGM管理器
BGMManager.init();
// 兼容性函数 - 调用新的BGM管理器
function changeBGM(mode) {
if (window.BGMManager) {
BGMManager.changeBGM(mode);
} else {
console.log('BGM管理器未初始化');
}
}
function changePages(mode,cPages)
......
......@@ -1507,23 +1507,30 @@
return;
}
// 获取种火实时数据
fetchFireseedRealTimeData().then(function(realtimeData) {
// 再次清除现有行,确保表格是空的
while (statusTable.rows.length > 1) {
statusTable.deleteRow(1);
}
// 添加种火数据行
for (const fsId in clbpara.fireseed) {
// 跳过非对象类型的条目或无效数据
if (!clbpara.fireseed[fsId] || typeof clbpara.fireseed[fsId] !== 'object' ||
!clbpara.fireseed[fsId].name || !clbpara.fireseed[fsId].level) {
!clbpara.fireseed[fsId].level) {
console.log('跳过无效种火数据:', fsId, clbpara.fireseed[fsId]);
continue;
}
const fsData = clbpara.fireseed[fsId];
const fsData = realtimeData[fsId] || clbpara.fireseed[fsId];
const row = statusTable.insertRow();
// 名称
const nameCell = row.insertCell();
const nameSpan = document.createElement('span');
nameSpan.className = 'yellow';
nameSpan.textContent = fsData.name;
nameSpan.className = fsData.alive === false ? 'red' : 'yellow';
nameSpan.textContent = fsData.name || '未知种火';
nameCell.appendChild(nameSpan);
// 等级
......@@ -1537,7 +1544,6 @@
// 位置
const plsCell = row.insertCell();
// 获取位置名称
const plsName = getPlsName(fsData.pls);
plsCell.textContent = plsName;
......@@ -1557,6 +1563,104 @@
const defCell = row.insertCell();
defCell.textContent = fsData.def;
// ID
const idCell = row.insertCell();
idCell.textContent = fsId;
}
}).catch(function(error) {
console.error('获取种火实时数据失败:', error);
// 如果获取实时数据失败,使用原有逻辑
updateFireseedStatusTableFallback();
});
}
// 获取种火实时数据
function fetchFireseedRealTimeData() {
return new Promise(function(resolve, reject) {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'fireseed_data.php', true);
xhr.onload = function() {
if (xhr.status === 200) {
try {
const data = JSON.parse(xhr.responseText);
if (data.error) {
reject(new Error(data.error));
} else {
resolve(data);
}
} catch (e) {
reject(new Error('解析响应数据失败'));
}
} else {
reject(new Error('请求失败: ' + xhr.statusText));
}
};
xhr.onerror = function() {
reject(new Error('网络错误'));
};
xhr.send();
});
}
// 备用的种火状态表格更新函数(使用原有逻辑)
function updateFireseedStatusTableFallback() {
const statusTable = document.querySelector('#fireseed-tab .fireseed-table');
if (!statusTable) return;
// 确保表格是空的
while (statusTable.rows.length > 1) {
statusTable.deleteRow(1);
}
// 添加种火数据行
for (const fsId in clbpara.fireseed) {
// 跳过非对象类型的条目或无效数据
if (!clbpara.fireseed[fsId] || typeof clbpara.fireseed[fsId] !== 'object' ||
!clbpara.fireseed[fsId].level) {
console.log('跳过无效种火数据:', fsId, clbpara.fireseed[fsId]);
continue;
}
const fsData = clbpara.fireseed[fsId];
const row = statusTable.insertRow(); // 修复:先创建行
// 名称
const nameCell = row.insertCell();
const nameSpan = document.createElement('span');
nameSpan.className = 'yellow';
nameSpan.textContent = fsData.name || '未知种火';
nameCell.appendChild(nameSpan);
// 等级
const levelCell = row.insertCell();
levelCell.textContent = fsData.level;
// 状态
const modeCell = row.insertCell();
const modes = ['跟随', '探物', '索敌', '隐藏'];
modeCell.textContent = modes[fsData.mode] || '未知';
// 位置
const plsCell = row.insertCell();
const plsName = getPlsName(fsData.pls);
plsCell.textContent = plsName;
// 生命
const hpCell = row.insertCell();
hpCell.textContent = (fsData.hp || 0) + '/' + (fsData.mhp || 0);
// 体力
const spCell = row.insertCell();
spCell.textContent = (fsData.sp || 0) + '/' + (fsData.msp || 0);
// 攻击
const attCell = row.insertCell();
attCell.textContent = fsData.att || 0;
// 防御
const defCell = row.insertCell();
defCell.textContent = fsData.def || 0;
// ID
const idCell = row.insertCell();
const idSpan = document.createElement('span');
......@@ -1619,22 +1723,17 @@
continue;
}
// 检查必要字段,但更宽松一些
if (!fsData.name && !fsData.level) {
console.log('跳过无效种火数据(缺少name和level):', fsId, fsData);
// 检查必要字段 - 现在只检查level,因为name不再存储在clbpara中
if (!fsData.level) {
console.log('跳过无效种火数据(缺少level):', fsId, fsData);
continue;
}
const option = document.createElement('option');
option.value = fsId;
// 构建显示文本,处理可能缺失的字段
let displayText = '';
if (fsData.name) {
displayText += fsData.name;
} else {
displayText += '种火' + fsId;
}
// 构建显示文本 - 使用种火ID作为标识
let displayText = '种火' + fsId;
if (fsData.level) {
displayText += ' (等级: ' + fsData.level;
......@@ -1666,9 +1765,9 @@
// 为每个种火创建物品列表
for (const fsId in clbpara.fireseed) {
// 跳过非对象类型的条目或无效数据
// 跳过非对象类型的条目或无效数据 - 现在只检查level
if (!clbpara.fireseed[fsId] || typeof clbpara.fireseed[fsId] !== 'object' ||
!clbpara.fireseed[fsId].name || !clbpara.fireseed[fsId].level) {
!clbpara.fireseed[fsId].level) {
console.log('跳过无效种火数据(物品列表):', fsId, clbpara.fireseed[fsId]);
continue;
}
......@@ -1779,7 +1878,7 @@
let validFireseeds = [];
for (const fsId in clbpara.fireseed) {
if (clbpara.fireseed[fsId] && typeof clbpara.fireseed[fsId] === 'object' &&
clbpara.fireseed[fsId].name && clbpara.fireseed[fsId].level) {
clbpara.fireseed[fsId].level) {
validFireseeds.push(fsId);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment