本文大部分使用ai生成,但是每个坑我都踩过
📋 准备工作
- 服务器环境:Docker (Ubuntu/Debian 容器) 或 Linux VPS。
- 数据库:MySQL 5.7
本地电脑:用于修改源码和编译插件 (Windows)。无需
插件下载链接在下方
第一步:数据库准备 (MySQL)
这是最基础的一步。为了避免 Unknown column 报错,我们直接建立一个“全功能”的数据库。
创建数据库与用户
在宝塔面板或命令行中:- 数据库名:
l4d2 - 用户名:
l4d2 - 密码:(你自己设定的)
- 权限:必须设置为
所有人(即%),否则 Docker 连不上。
- 数据库名:
修改 MySQL 配置 (解决连接超时)
修改 MySQL 配置文件 (my.cnf/my.ini):- 在
[mysqld]下添加skip-name-resolve - 重启 MySQL 服务。
- 在
导入“完美版”表结构
打开 phpMyAdmin -> l4d2 -> SQL,执行以下语句(这包含了所有必需字段和触发器):SET NAMES utf8mb4; -- 1. 创建核心玩家表 (包含所有 Anne 版特有字段) CREATE TABLE IF NOT EXISTS `players` ( `steamid` varchar(64) NOT NULL, `name` varchar(128) NOT NULL DEFAULT '', `ip` varchar(32) NOT NULL DEFAULT '', `lastontime` int(11) NOT NULL DEFAULT '0', `playtime` int(11) NOT NULL DEFAULT '0', `points` int(11) NOT NULL DEFAULT '0', `kills` int(11) NOT NULL DEFAULT '0', `headshots` int(11) NOT NULL DEFAULT '0', `playtime_versus` int(11) NOT NULL DEFAULT '0', `playtime_realism` int(11) NOT NULL DEFAULT '0', `playtime_survival` int(11) NOT NULL DEFAULT '0', `playtime_scavenge` int(11) NOT NULL DEFAULT '0', `playtime_realismversus` int(11) NOT NULL DEFAULT '0', `playtime_mutations` int(11) NOT NULL DEFAULT '0', `points_survivors` int(11) NOT NULL DEFAULT '0', `points_infected` int(11) NOT NULL DEFAULT '0', `points_realism` int(11) NOT NULL DEFAULT '0', `points_survival` int(11) NOT NULL DEFAULT '0', `points_scavenge_survivors` int(11) NOT NULL DEFAULT '0', `points_scavenge_infected` int(11) NOT NULL DEFAULT '0', `points_realism_survivors` int(11) NOT NULL DEFAULT '0', `points_realism_infected` int(11) NOT NULL DEFAULT '0', `points_mutations` int(11) NOT NULL DEFAULT '0', `versus_kills_survivors` int(11) NOT NULL DEFAULT '0', `scavenge_kills_survivors` int(11) NOT NULL DEFAULT '0', `realism_kills_survivors` int(11) NOT NULL DEFAULT '0', `mutations_kills_survivors` int(11) NOT NULL DEFAULT '0', `kill_infected` int(11) NOT NULL DEFAULT '0', `kill_hunter` int(11) NOT NULL DEFAULT '0', `kill_smoker` int(11) NOT NULL DEFAULT '0', `kill_boomer` int(11) NOT NULL DEFAULT '0', `kill_spitter` int(11) NOT NULL DEFAULT '0', `kill_jockey` int(11) NOT NULL DEFAULT '0', `kill_charger` int(11) NOT NULL DEFAULT '0', `melee_kills` int(11) NOT NULL DEFAULT '0', `infected_spawn_1` int(11) NOT NULL DEFAULT '0', `infected_spawn_2` int(11) NOT NULL DEFAULT '0', `infected_spawn_3` int(11) NOT NULL DEFAULT '0', `infected_spawn_4` int(11) NOT NULL DEFAULT '0', `infected_spawn_5` int(11) NOT NULL DEFAULT '0', `infected_spawn_6` int(11) NOT NULL DEFAULT '0', `infected_spawn_7` int(11) NOT NULL DEFAULT '0', `infected_spawn_8` int(11) NOT NULL DEFAULT '0', `infected_boomer_blinded` int(11) NOT NULL DEFAULT '0', `infected_boomer_vomits` int(11) NOT NULL DEFAULT '0', `infected_smoker_damage` int(11) NOT NULL DEFAULT '0', `infected_jockey_damage` int(11) NOT NULL DEFAULT '0', `infected_jockey_ridetime` float NOT NULL DEFAULT '0', `infected_charger_damage` int(11) NOT NULL DEFAULT '0', `infected_tank_damage` int(11) NOT NULL DEFAULT '0', `infected_hunter_pounce_counter` int(11) NOT NULL DEFAULT '0', `infected_hunter_pounce_dmg` int(11) NOT NULL DEFAULT '0', `infected_tanksniper` int(11) NOT NULL DEFAULT '0', `charger_impacts` int(11) NOT NULL DEFAULT '0', `award_pills` int(11) NOT NULL DEFAULT '0', `award_medkit` int(11) NOT NULL DEFAULT '0', `award_defib` int(11) NOT NULL DEFAULT '0', `award_revive` int(11) NOT NULL DEFAULT '0', `award_rescue` int(11) NOT NULL DEFAULT '0', `award_tankkill` int(11) NOT NULL DEFAULT '0', `award_tankkillnodeaths` int(11) NOT NULL DEFAULT '0', `award_witchcrowned` int(11) NOT NULL DEFAULT '0', `award_witchdisturb` int(11) NOT NULL DEFAULT '0', `award_friendlyfire` int(11) NOT NULL DEFAULT '0', `award_fincap` int(11) NOT NULL DEFAULT '0', `award_teamkill` int(11) NOT NULL DEFAULT '0', `award_left4dead` int(11) NOT NULL DEFAULT '0', `award_letinsafehouse` int(11) NOT NULL DEFAULT '0', `award_allinsafehouse` int(11) NOT NULL DEFAULT '0', `award_survivor_down` int(11) NOT NULL DEFAULT '0', `award_infected_win` int(11) NOT NULL DEFAULT '0', `award_scavenge_infected_win` int(11) NOT NULL DEFAULT '0', `award_jockey` int(11) NOT NULL DEFAULT '0', `award_charger` int(11) NOT NULL DEFAULT '0', `award_gascans_poured` int(11) NOT NULL DEFAULT '0', `award_upgrades_added` int(11) NOT NULL DEFAULT '0', `award_matador` int(11) NOT NULL DEFAULT '0', `award_ledgegrab` int(11) NOT NULL DEFAULT '0', `award_scatteringram` int(11) NOT NULL DEFAULT '0', `award_bulldozer` int(11) NOT NULL DEFAULT '0', `award_perfect_blindness` int(11) NOT NULL DEFAULT '0', `award_pounce_nice` int(11) NOT NULL DEFAULT '0', `award_pounce_perfect` int(11) NOT NULL DEFAULT '0', `award_campaigns` int(11) NOT NULL DEFAULT '0', `award_protect` int(11) NOT NULL DEFAULT '0', `award_smoker` int(11) NOT NULL DEFAULT '0', `award_hunter` int(11) NOT NULL DEFAULT '0', `award_adrenaline` int(11) NOT NULL DEFAULT '0', `lastannemode` int(11) NOT NULL DEFAULT '0', `lastgamemode` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`steamid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 创建辅助表 CREATE TABLE IF NOT EXISTS `settings` ( `steamid` varchar(64) NOT NULL, `mute` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`steamid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `server_settings` ( `sname` varchar(64) NOT NULL, `svalue` varchar(1024) NOT NULL DEFAULT '', PRIMARY KEY (`sname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `maps` ( `name` varchar(64) NOT NULL, `gamemode` int(11) NOT NULL DEFAULT '0', `custom` int(11) NOT NULL DEFAULT '0', `mutation` varchar(64) NOT NULL DEFAULT '', `playtime_nor` int(11) NOT NULL DEFAULT '0', `playtime_adv` int(11) NOT NULL DEFAULT '0', `playtime_exp` int(11) NOT NULL DEFAULT '0', `kills_nor` int(11) NOT NULL DEFAULT '0', `kills_adv` int(11) NOT NULL DEFAULT '0', `kills_exp` int(11) NOT NULL DEFAULT '0', `points_nor` int(11) NOT NULL DEFAULT '0', `points_adv` int(11) NOT NULL DEFAULT '0', `points_exp` int(11) NOT NULL DEFAULT '0', `restarts_nor` int(11) NOT NULL DEFAULT '0', `restarts_adv` int(11) NOT NULL DEFAULT '0', `restarts_exp` int(11) NOT NULL DEFAULT '0', `caralarm_nor` int(11) NOT NULL DEFAULT '0', `caralarm_adv` int(11) NOT NULL DEFAULT '0', `caralarm_exp` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`name`,`gamemode`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `timedmaps` ( `id` int(11) NOT NULL AUTO_INCREMENT, `map` varchar(64) NOT NULL, `gamemode` int(11) NOT NULL, `difficulty` int(11) NOT NULL, `mutation` varchar(64) NOT NULL, `steamid` varchar(64) NOT NULL, `time` float NOT NULL, `players` int(11) NOT NULL, `mode` int(11) NOT NULL DEFAULT '0', `plays` int(11) NOT NULL DEFAULT '1', `modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `sinum` int(11) NOT NULL DEFAULT '0', `sitime` int(11) NOT NULL DEFAULT '0', `anneversion` varchar(32) NOT NULL DEFAULT '', `usebuy` int(11) NOT NULL DEFAULT '0', `auto` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 3. 创建防火墙触发器 (拦截幽灵账号) DELIMITER // CREATE TRIGGER `block_bad_id_insert` BEFORE INSERT ON `l4dstats_players` FOR EACH ROW BEGIN IF NEW.steamid LIKE '%STOP_IGNORING%' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Blocked invalid SteamID'; END IF; END; // DELIMITER ;
第二步:服务器环境准备 (Docker/Linux)
安装 32位 运行库
- 如果不装这个,SourceMod 加载 MySQL 驱动会失败。
命令:
sudo dpkg --add-architecture i386 sudo apt-get update sudo apt-get install lib32z1- Docker用户:如果重启后失效,参考之前的教程把宿主机的
/usr/lib32/libz.so.1复制 (docker cp) 到容器的/游戏目录/bin/下。
升级 SourceMod
- 务必确保服务器使用的是 SourceMod 1.12。旧版 (1.11) 无法运行此插件。
第三步:修改与编译插件 (Windows)
这是解决“不记录时间”、“人数不足不记录”的关键。
准备文件
- 将
l4d_stats.sp放入scripting/。 - 下载
left4dhooks.inc和colors.inc放入scripting/include/。
- 将
修改源码 (
l4d_stats.sp)- 解除无法统计时间限制:
搜索stock int IsFunGame(),把return 3;改为return 0;。 - 解除人数限制 (可选,方便单人测试):
搜索CheckSurvivorsHumans,把判断条件里的human < 3改为human < 1。
- 解除无法统计时间限制:
编译
- 把修改后的
.sp文件拖到spcomp.exe上。 - 得到
l4d_stats.smx。
- 把修改后的
无需编译,我已经编译过了
https://wwbou.lanzouq.com/i0zxZ3d30m8f
密码:fosv
第四步:安装与配置
上传插件
- 把编译好的
l4d_stats.smx放入addons/sourcemod/plugins/。 - 把
Left4DHooks.smx(依赖插件) 也放入同目录。 - 把
Left4DHooks.ext.2.l4d2.so(扩展文件) 放入addons/sourcemod/extensions/。
- 把编译好的
配置
databases.cfg
编辑addons/sourcemod/configs/databases.cfg,加入:"l4dstats" // 名字必须是这个 { "driver" "default" "host" "172.17.0.1" //你自己的数据库地址 "database" "l4d2" //你的数据库名称 "user" "l4d2" //你的数据库用户名 "pass" "你的密码" "port" "3306" "charset" "utf8mb4" }
第五步:启动与验证
- 重启服务器。
- 检查插件状态:
控制台输入sm plugins list,确保L4D2 Player Stats和Left4DHooks都是运行状态。 - 检查报错:
查看addons/sourcemod/logs/errors_xxxx.log。如果按照上述步骤操作,应该只有Blocked invalid SteamID这种正常的拦截日志,不应再有Unknown column或Table doesn't exist。 进服测试:
- 输入
!rank。 - 在服务器里挂机 2分钟。
- 去 phpMyAdmin 刷新
players表,你会发现playtime变成了 2,points也有了数据。
- 输入
至此,部署完成!