




已阅读5页,还剩38页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Changing player models and lowering svc_bad简介本教程面向需要在Counter-Strike中改变用户模型的中级AMXX编程人员,它将探讨几种应对广为人知的SVC_BAD错误的不同方法及其影响。SVC_BAD错误对那些没有留意它的人,这个错误通常是由于异常的引擎消息引起的。当异常消息产生时,最常见的后果是你从正在游戏的服务器里被踢出,并且在你的控制台会出现以下几种错误之一: Host_Error: Illegible Server Message: SVC_BAD Host_Error: UserMsg: Not Present on Client # Host_Error: CL_ParseServerMessage: Bad server message 诸如此类. 这些错误的主要原因可能是: 数据在网络传送过程中出错 MODs或插件发送了一个不存在/无效的消息 MODs或插件发送了参数数量不正确的消息然而在我们的情况里,它是因为在同一时刻改变大量玩家的模型而产生的,并导致几乎每个玩家无明显原因的发生错误。也许是因为Valve没有修正某个小BUG而导致这种情况吧。那么这种情况下我们应该怎么办呢?请继续读下去CStrike 模块里的native函数也许你改变用户模型的首选方法是这个。是的,这种方法具有简单易行的特点并且你只需要学习三个小小的native函数。但是我们将看到为什么不能在你的插件里用它们改变大量的模型(例如:在丧尸服务器里)。通过阅读源代码我们知道它在玩家的InfoBuffer里设置模型名称并持续监控它,到目前为止似乎一切正常Code:static cell AMX_NATIVE_CALL cs_set_user_model(AMX *amx, cell *params) / cs_set_user_model(index, const model); = 2 params/ .g_playersparams1.SetModel(model);g_playersparams1.SetModelled(true);SETCLIENTKEYVALUE(params1, GETINFOKEYBUFFER(pPlayer), model, char*)g_playersparams1.GetModel();/ .问题来了: 为了防止CS重置我们自已设置的模型,它在玩家的每一个PostThink事件中检查模型是否发生了变化,如果变化了,就立即将模型改回我们设置的模型:Code:void PlayerPostThink(edict_t* pPlayer)int entityIndex = ENTINDEX(pPlayer);if(g_playersentityIndex.GetModelled()if (g_playersentityIndex.GetInspectModel() & strcmp(g_playersentityIndex.GetModel(), GETCLIENTKEYVALUE(GETINFOKEYBUFFER(pPlayer), model) != 0)/LOG_CONSOLE(PLID, %s should have model %s and currently has %s, STRING(pPname), (char*)g_playersentityIndex.GetModel(), GETCLIENTKEYVALUE(GETINFOKEYBUFFER(pPlayer), model);SETCLIENTKEYVALUE(entityIndex, GETINFOKEYBUFFER(pPlayer), model, (char*)g_playersentityIndex.GetModel();g_playersentityIndex.SetInspectModel(false);RETURN_META(MRES_IGNORED);但在一些事件比如round start事件发生时, CS会将每个玩家的模型设置回默认模型(gign, sas, leet, 等等). 所以如果一个叫Player的玩家有自定义模型时事情将会变成这样: Player 被设置成自定义模型zombie 服务器发送一个消息给所有玩家以通知这个事情。 新的一局 许多消息被发送: 更新成绩、删除实体、玩家重生、等等。 CS 将Player的模型重设为 leet 另一个消息被发送。 Player的模型马上在PostThink事件中被设回zombie 再次发送消息。如你所见,在新的一局开始时产生了大量的网络流量。你甚至可以使用Damaged Soul写的Message Logging插件亲自探究一番。现在,如果你服务器里的32个玩家都有自定义的模型,那么除了开局时大量的消息外还得加上大量的模型更新消息。这时就发生了我们所说的错误: 先是某个玩家因为Reliable channel overflow错误关闭, 接着 (几乎) 所有玩家因为SVC_BAD错误被踢出.或者至少象我所看到的: 如果你足够幸运没被踢出, 你也会发现游戏被冻住几秒并且其它玩家被踢出。顺便说一句,使用CStrike模块改变模型还会导致另一个问题,只要你对玩家使用cs_set_user_team() , 无论你是否传送了CS_DONTCHANGE 做为第三个参数, 他的模型将会被重设。我们可以再次看看源代码找到原因:Code:static cell AMX_NATIVE_CALL cs_set_user_team(AMX *amx, cell *params) / cs_set_user_team(index, team, model = 0); = 3 params/ .int model = params3;*(int *)pPlayer-pvPrivateData + OFFSET_TEAM) = params2;if (model != 0)*(int *)pPlayer-pvPrivateData + OFFSET_INTERNALMODEL) = model;/ This makes the model get updated right away.MDLL_ClientUserInfoChanged(pPlayer, GETINFOKEYBUFFER(pPlayer); / If this causes any problems for WON, do this line only in STEAM builds./ .你可以看到在设置team和internal model之后, 它调用了 DLL_ClientUserInfoChanged, 导致 CS 重设玩家的模型通常如果是由CS调用该函数这种情况会被避免:Code:void ClientUserInfoChanged(edict_t *pEntity, char *infobuffer)int index = ENTINDEX(pEntity);if(g_playersindex.GetModelled() & pEntity-v.deadflag = DEAD_NO)RETURN_META(MRES_SUPERCEDE);elseRETURN_META(MRES_IGNORED);但是显然如果我们在自己的代码中调用这个函数那么这个hook没有效果,玩家模型将被重设。工作区#1 使用Fakemeta所以基本上我们需要找到一个方法使上述的native函数恢复功能以便我们完全控制整个过程。 Fakemeta 可以满足我们的需求。我们要完成5件事: 设置玩家的模型 获取玩家的模型 重设玩家的(默认)模型 防止CS改变我们的模型 防止玩家改变我们的模型一开始, 我们要定义两个全局数组: 第一个bool数组标示玩家是否使用了自定义模型, 第二个数组保存使用的自定义模型的名字(字符串变量)。Code:new g_has_custom_model33new g_player_model33321. 设置一个自定义模型时只要简单的告诉引擎设置客户端的KeyValue值:Code:stock fm_set_user_model(player, const modelname) / 设置新模型 engfunc(EngFunc_SetClientKeyValue, player, engfunc(EngFunc_GetInfoKeyBuffer, player), model, modelname) / 记住这个用户使用了自定义模型 g_has_custom_modelplayer = true2. 获取用户的模型: 我们可以使用cs_get_user_model()函数, 不过如果我们不打算使用cstrike模块l, 可以使用这个方法代替: (edit: model is now passed byref- thanks XxAvalanchexX)Code:stock fm_get_user_model(player, model, len) / 获取当前的模型 engfunc(EngFunc_InfoKeyValue, engfunc(EngFunc_GetInfoKeyBuffer, player), model, model, len)3. 恢复默认的模型:正如我们前面所看到的,只要告诉引擎客户端的UserInfo已经改变了就足够了, CS将会为我们完成剩余的工作:Code:stock fm_reset_user_model(player) / 玩家不再使用自定义模型 g_has_custom_modelplayer = false dllfunc(DLLFunc_ClientUserInfoChanged, player, engfunc(EngFunc_GetInfoKeyBuffer, player)4. 为了防止CS改变我们的模型, 我们使用forward函数FM_SetClientKeyValue:Code:public plugin_init() / . register_forward(FM_SetClientKeyValue,fw_SetClientKeyValue)public fw_SetClientKeyValue(id, const infobuffer, const key) / 阻止CS改变模型 if (g_has_custom_modelid & equal(key, model) return FMRES_SUPERCEDE; return FMRES_IGNORED;5. 因为我们阻止了SetClientKeyValue, CS将不能防止玩家输入model modelname 改变模型,所以我们还得小心处理这种情况:Code:public plugin_init() / . register_forward(FM_ClientUserInfoChanged,fw_ClientUserInfoChanged)public fw_ClientUserInfoChanged(id) / 玩家没有使用自定义模型 if (!g_has_custom_modelid) return FMRES_IGNORED; / 获取当前模型 static currentmodel32 fm_get_user_model(id, currentmodel, sizeof currentmodel - 1) / 看它是否和自定义模型一致如果不是, 重新设回自定义的模型 if (!equal(currentmodel, g_player_modelid) fm_set_user_model(id, g_player_modelid) return FMRES_IGNORED;6. 注意: 你也可以使用set_user_info(player, model, modelname) 和 get_user_info(player, model, model, len) 去设置或获取玩家模型。看起来它们工作正常并且更容易使用,但因为我们假定我们使用FakeMeta所以.最后,为了保证在每次模型改变之间有一个延时,你可以使用以下的代码:#define MODELCHANGE_DELAY 0.5 / delay between model changesnew Float:g_models_targettime / target time for the last model changepublic fm_user_model_update(id)static Float:current_timecurrent_time = get_gametime()/ Delay needed?if (current_time - g_models_targettime = MODELCHANGE_DELAY) fm_set_user_model(id) g_models_targettime = current_timeelse set_task(g_models_targettime + MODELCHANGE_DELAY) - current_time), fm_set_user_model, id) g_models_targettime = g_models_targettime + MODELCHANGE_DELAY 好了,现在我们可以使用tasks一个接一个的改变玩家的模型,而不是在开局时同时改变32个玩家的模型,下面是一个完整的例子:Code:#include #include #include #include #define ZOMBIE_MODEL zombie / 丧尸使用的模型#define TASK_DELAY 0.5 / 改变玩家模型的task之间的延迟(你可以试着减少它)#define MODELSET_TASK 100 / an offset for our models tasknew g_has_custom_model33 / 玩家是否使用自定义模型new g_player_model3332 / 玩家自定义模型的名字 (string)new g_zombie33 / 玩家是否是丧尸new Float:g_models_counter / a counter used to set task durations/*=Plugin Start=*/public plugin_precache() new modelpath100 formatex(modelpath, sizeof modelpath - 1, models/player/%s/%s.mdl, ZOMBIE_MODEL, ZOMBIE_MODEL) engfunc(EngFunc_PrecacheModel, modelpath)public plugin_init() register_plugin(Player Model Changer Example, 0.2, MeRcyLeZZ) RegisterHam(Ham_Spawn, player, fw_PlayerSpawn, 1) register_logevent(event_round_end, 2, 1=Round_End) register_forward(FM_SetClientKeyValue, fw_SetClientKeyValue) register_forward(FM_ClientUserInfoChanged, fw_ClientUserInfoChanged)/*=Player Spawn Event=*/public fw_PlayerSpawn(id) / 死人 if (!is_user_alive(id) return; / 设置为zombie 如果是恐怖分子 g_zombieid = cs_get_user_team(id) = CS_TEAM_T ? true : false; / 删除先前的task (如果有) remove_task(id+MODELSET_TASK) / 检查玩家是否是丧尸 if (g_zombieid) / 把模型名字保存在g_player_modelid中 copy(g_player_modelid, sizeof g_player_model - 1, ZOMBIE_MODEL) / 取得当前模型 new currentmodel32 fm_get_user_model(id, currentmodel, sizeof currentmodel - 1) / 看它是否符合当前自定义模型 if (!equal(currentmodel, g_player_modelid) / 如果不符合,改变它 set_task(1.0 + g_models_counter, task_set_model, id+MODELSET_TASK) / 为每次模型的改变加一个延迟 g_models_counter += TASK_DELAY / 不是丧尸但还是使用自定义模型 else if (g_has_custom_modelid) / 把模型设为默认模型 fm_reset_user_model(id) /*=Round End Event=*/public event_round_end() / Reset the models task counter g_models_counter = 0.0/*=Forwards=*/public fw_SetClientKeyValue(id, const infobuffer, const key) / 防止CS改变模型 if (g_has_custom_modelid & equal(key, model) return FMRES_SUPERCEDE; return FMRES_IGNORED;public fw_ClientUserInfoChanged(id) / 玩家没有使用自定义模型 if (!g_has_custom_modelid) return FMRES_IGNORED; / 取得当前模型 static currentmodel32 fm_get_user_model(id, currentmodel, sizeof currentmodel - 1) / 看它是否和自定义模型一致 如果不是, 再次重设 if (!equal(currentmodel, g_player_modelid) fm_set_user_model(id, g_player_modelid) return FMRES_IGNORED;/*=Tasks=*/public task_set_model(id) / 取得玩家 id id -= MODELSET_TASK / 设置玩家模型 fm_set_user_model(id, g_player_modelid)/*=Stocks=*/stock fm_set_user_model(player, const modelname) / 设置新模型 engfunc(EngFunc_SetClientKeyValue, player, engfunc(EngFunc_GetInfoKeyBuffer, player), model, modelname) / 记住玩家使用了自定义模型 g_has_custom_modelplayer = truestock fm_get_user_model(player, model, len) / 取得当前模型 engfunc(EngFunc_InfoKeyValue, engfunc(EngFunc_GetInfoKeyBuffer, player), model, model, len)stock fm_reset_user_model(player) / 玩家不再使用自定义模型 g_has_custom_modelplayer = false dllfunc(DLLFunc_ClientUserInfoChanged, player, engfunc(EngFunc_GetInfoKeyBuffer, player)工作区#2 使用Separate entities你已经试着使用上面的方法并且为每个Task设置了尽可能长的延时但还是不时得到SVC_BAD 错误。 还能怎么办呢? 好,简单,不要删掉你的模型! 嗨,我可是认真的。新的方法是让真实的玩家不可见但有分离的实体(having individual entities) (拥有你的自定义模型)跟随你并复制你的动作。所以即使看起来象是更换了模型,但实际上你根本没有改变模型,并且也不会产生SVC_BAD 错误。 (灵感来自 ChickenMod )坏消息是, 实体可能创建失败或者使服务器不稳定(更多的实体意味着更高的CPU使用率), 所以这种方法带有一定的实验性质。不管怎样, 让我们继续吧。 注意你要为每个玩家准备两个实体,因为使玩家不可见也会使他的武器不可见。 首先, 使用3个全局数组。一个保存模型名字。 其余的将用来保存跟随玩家的实体的索引。 还有两个它们classname的define。Code:new g_player_model3332new g_ent_playermodel33new g_ent_weaponmodel33#define PLAYERMODEL_CLASSNAME ent_playermodel#define WEAPONMODEL_CLASSNAME ent_weaponmodel 接着, 当你要设置自定义模型时你需要调用这个函数:Code:stock fm_set_playermodel_ent(id, const modelname) / 使用原有的玩家实体不可见 set_pev(id, pev_rendermode, kRenderTransTexture) / 这里不能为0因为那将使一些武器开火时的效果不可见 set_pev(id, pev_renderamt, 1.0) / 生成模型的全路径名称 static modelpath100 formatex(modelpath, sizeof modelpath - 1, models/player/%s/%s.mdl, modelname, modelname) / 检查代替玩家的实体是否已经存在 if (!pev_valid(g_ent_playermodelid) / 不存在的话就生成一个新的实体 g_ent_playermodelid = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, info_target) / 如果生成失败,这能防止出现Invalid entity错误. if (!pev_valid(g_ent_playermodelid) return; / 设置实体的classname set_pev(g_ent_playermodelid, pev_classname, PLAYERMODEL_CLASSNAME) / 让它跟随玩家的动作 set_pev(g_ent_playermodelid, pev_movetype, MOVETYPE_FOLLOW) set_pev(g_ent_playermodelid, pev_aiment, id) set_pev(g_ent_playermodelid, pev_owner, id) / 实体已经准备好了,现在设置它的模型 engfunc(EngFunc_SetModel, g_ent_playermodelid, modelpath)这个stock函数将返回一个玩家是否使用了自定义模型:Code:stock fm_has_custom_model(id) return pev_valid(g_ent_playermodelid) ? true : false;这个函数将在更换武器时调用以便更新武器模型实体:Code:stock fm_set_weaponmodel_ent(id) / 取得玩家的武器模型 static model100 pev(id, pev_weaponmodel2, model, sizeof model - 1) / 检查代替武器的实体是否已经存在 if (!pev_valid(g_ent_weaponmodelid) / 如果不存在就生成一个新的 g_ent_weaponmodelid = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, info_target) / 如果生成失败,这能防止出现Invalid entity 错误. if (!pev_valid(g_ent_weaponmodelid) return; / 设置实体的classname set_pev(g_ent_weaponmodelid, pev_classname, WEAPONMODEL_CLASSNAME) / 使实体跟随玩家的动作 set_pev(g_ent_weaponmodelid, pev_movetype, MOVETYPE_FOLLOW) set_pev(g_ent_weaponmodelid, pev_aiment, id) set_pev(g_ent_weaponmodelid, pev_owner, id) / 实体准备好后,设置它的模型 engfunc(EngFunc_SetModel, g_ent_weaponmodelid, model)最后, 这个函数将重设你的自定义玩家模型: (以及实体)Code:stock fm_remove_model_ents(id) / 使用玩家重新可见 set_pev(id, pev_rendermode, kRenderNormal) / 删除 playermodel 实体 if (pev_valid(g_ent_playermodelid) engfunc(EngFunc_RemoveEntity, g_ent_playermodelid) g_ent_playermodelid = 0 / 删除 weaponmodel 实体 if (pev_valid(g_ent_weaponmodelid) engfunc(EngFunc_RemoveEntity, g_ent_weaponmodelid) g_ent_weaponmodelid = 0 并不是那么难吧? 但是还是有一些问题要小心对付.1. 当一个玩家死后, 地上的尸体将不会是你自定义的模型. 为了修正这个问题,我们要Hook尸体消息:Code:public plugin_init() register_message(get_user_msgid(ClCorpse), message_clcorpse)public message_clcorpse() / 取得玩家id static id id = get_msg_arg_int(12) / 检查玩家是否使用自定义模型 if (fm_has_custom_model(id) / 为玩家的尸体设置正确的模型 set_msg_arg_string(1, g_player_modelid) 2. 如果我们想给玩家一种不同的渲染方式 (例如发光) 我们需要把它们设置在实体上:Code:/ 为playermodel 实体设置发红光属性set_pev(g_ent_playermodelid, pev_renderfx, kRenderFxGlowShell)set_pev(g_ent_playermodelid, pev_color, Float:200.0, 0.0, 0.0)set_pev(g_ent_playermodelid, pev_renderamt, 50.0)/ 或者我们使用fakemeta_util的 stock函数代替:fm_set_rendering(g_ent_playermodelid, kRenderFxGlowShell, 200, 0, 0, kRenderNormal, 50)3. 如果模型足够大,它的拥有者将可以看到它的一部分(虽然他本不应该看到)修订:你可能实际上并不需要这一部分,我做了一些测试,模型都自动隐藏了,在这种情况下不需这个函数,因为它的代价太大了(非常占资源).Code:public plugin_init() register_forward(FM_AddToFullPack, fw_AddToFullPack)public fw_AddToFullPack(es, e, ent, host, hostflags, player) / 减少一些匹配范围 if (player | !ent) return FMRES_IGNORED; / 检查是否是我们的自定义实体模型被发送到它的所有者(玩家) static owner for (owner = 1; owner = 32; owner+) / 过滤掉玩家实体和武器实体. if (ent = g_ent_playermodelowner | ent = g_ent_weaponmodelowner) & host = owner) return FMRES_SUPERCEDE; return FMRES_IGNORED;好,我们现在把它们整合在一起以便更清楚的了解它:Code:#include #include #include #include #define ZOMBIE_MODEL zombie / The model were gonna use for zombies#define PLAYERMODEL_CLASSNAME ent_playermodel#define WEAPONMODEL_CLASSNAME ent_weaponmodelnew g_player_model3332 / 玩家的模型名称 (string)new g_ent_playermodel33 / 代替玩家的实体new g_ent_weaponmodel33 / 代替武器的实体new g_zombie33 / 玩家是否是丧尸new g_glow33 / 玩家是否发光/*=Plugin Start=*/public plugin_precache() new modelpath100 formatex(modelpath, sizeof modelpath - 1, models/player/%s/%s.mdl, ZOMBIE_MODEL, ZOMBIE_MODEL) engfunc(EngFunc_PrecacheModel, modelpath)public plugin_init() register_plugin(Player Model Changer Example, 0.3, MeRcyLeZZ) RegisterHam(Ham_Spawn, player, fw_PlayerSpawn, 1) register_forward(FM_AddToFullPack, fw_AddToFullPack) register_event(CurWeapon, event_curweapon, be, 1=1) register_message(get_user_msgid(ClCorpse), message_clcorpse) register_clcmd
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年2月山东领取济宁市份普通话水平测试等级证书考前自测高频考点模拟试题附答案详解(黄金题型)
- 2025儿童医院急诊超声技能考核
- 秦皇岛市中医院特殊单元质量管理考核
- 2025年上海市宝山区罗店中心校实习生招募模拟试卷附答案详解(考试直接用)
- 2025春季福建华南女子职业学院人才招聘20人模拟试卷及完整答案详解一套
- 大学课件醛酮
- 2025第二人民医院肿瘤内科医师规范化培训结业考核
- 保定市人民医院透析患者液体管理专项考核
- 北京市中医院重症患者放射检查考核
- 2025金华市金投集团有限公司招聘7人考前自测高频考点模拟试题含答案详解
- 高中英语新课标3000词汇表(新高考)
- 【MOOC】《中国马克思主义与当代》(北京科技大学)中国大学MOOC慕课答案
- 大厦火灾自动报警系统更换方案
- 基于PLC控制的自动配料系统设计
- 《通信原理》第六版课件(全)
- (完整版)黄帝内经繁体版
- 儿科学-见习课液体疗法
- 高考语文 最是风流袁隆平 课件(59张PPT)
- 河道告示牌设计样图、点、线、面编码及属性统计表、界桩(牌)身份证表、移位桩点之记表样式、数据库结构表
- 房建工程施工工艺标准化手册(图文并茂)
- DB4101-T 25.2-2021物业服务规范 第2部分:住宅-(高清现行)
评论
0/150
提交评论