WaitingDeathMatch プラグイン
— ArcMage (@Alchemist_Ark) 2018年4月6日
ここホメて欲しいんだけど、最終CPを基準に周囲にランダムスポーンする仕様なんだけど、溶岩とか即死床の上にはスポーンしないんですよ 他のマップでも大丈夫 pic.twitter.com/N1Dn2hBC3s
— ArcMage (@Alchemist_Ark) 2018年4月6日
というわけで主に鯖温め中とかにMGEよろしくバトルする人向けプラグインです 400行程度 koth、PL、5CP、A/Dで動作可能です。 ただしマップによってはエリア外にスポーンすること多々。こればかりはどうしようもない。
— ArcMage (@Alchemist_Ark) 2018年4月6日
#include <sourcemod> #include <TF2> #include <tf2_stocks> #include <sdktools> /* サーバー温め中に全員でDMするプラグイン ・5CP、KOTH CP(5CPの場合センターCP)周辺で即スポーンする ・他マップ PL、A/Dも動作可能 CTF、PLRは無効 他、マップ判定できないものは無効 ・動作 対象CPから一定距離離れかつ一定距離以内でスポーンする とは言ってもセンターCPのpropから少し離れるぐらいでいいと思う スポーンしたらCPの方を向く angleは水平で良い オバヒは与える ・未実装 DMに参加したくない人は無敵になれる(ただし与ダメも無くなる) 無敵状態はエフェクトで確認可能に */ #define SPAWN_RANGE 200 new Handle:v_enable = INVALID_HANDLE; new Handle:v_range_max = INVALID_HANDLE; new Handle:v_range_min = INVALID_HANDLE; new g_ent = -1; new bool:g_wait = true; public Plugin:myinfo = { name = "[TF2]Waiting DeathMatch", author = "不利ジョイナーAMG", description = "簡易デスマッチ プレイヤー待ち向け", version = "", url = "" } //プラグイン起動時 public OnPluginStart(){ //cmd RegAdminCmd("sm_reload", Cmd_reload, 0, "デバッグコマンド - プラグインをリロードする"); RegAdminCmd("sm_r", Cmd_respawn, 0, "リスポーンする"); //cvar v_enable = CreateConVar("sm_wdm_enable", "0", "WDMモードのOnOffを切り替える"); v_range_max = CreateConVar("sm_wdm_rangemax", "700", "スポーン範囲(最大値)"); v_range_min = CreateConVar("sm_wdm_rangemin", "200", "スポーン範囲(最低値)"); //hook HookEvent("player_death", OnPlayerDeath); HookEvent("player_spawn", OnPlayerSpawned, EventHookMode_Post); HookEvent("teamplay_round_active", OnRoundStart); HookConVarChange(v_enable, OnConVarChanged_enable); HookConVarChange(v_range_max, OnConVarChanged_range); HookConVarChange(v_range_min, OnConVarChanged_range); } //待機時間終了時に呼び出される(tf2_stocks.inc) public TF2_OnWaitingForPlayersEnd(){ } //cmd デバッグ用 プラグインのリロード public Action:Cmd_reload(client, args){ new String:strName[255]; GetPluginFilename(INVALID_HANDLE, strName, sizeof(strName)); ServerCommand("sm plugins reload %s", strName); GetPluginInfo(INVALID_HANDLE, PlInfo_Name, strName, sizeof(strName)); ReplyToCommand(client,"reload plugin '%s'", strName); } //cmd エリア外スポーンとかした時用 public Action:Cmd_respawn(client, args){ if(GetConVarInt(v_enable) == 0 || g_wait == true){ return; } TF2_RespawnPlayer(client); } //関数 EntityにInputを送信 public Entity_Input(String:classname[], String:Input[]){ new ent = -1; while((ent = FindEntityByClassname(ent, classname)) != -1){ new ref = EntIndexToEntRef(ent); AcceptEntityInput(ref, Input); } } //マップ終了時にプラグインも終了する public OnMapStart(){ SetConVarInt(v_enable,0,false,false); } public OnMapEnd(){ SetConVarInt(v_enable,0,false,false); } public OnPluginEnd(){ SetConVarInt(v_enable,0,false,false); } //ConVar変更時(プラグイン有効無効切り替え時) public OnConVarChanged_enable(ConVar convar, const char[] oldValue, const char[] newValue){ new i; //プラグインが有効化された if(StringToInt(newValue) == 1){ //初期化処理 Func_Start(); } //プラグインが無効化された else if(StringToInt(newValue) == 0){ //Entityを動作させる Entity_Input("team_control_point_master","Enable"); Entity_Input("team_control_point","Enable"); Entity_Input("trigger_capture_area","Enable"); Entity_Input("func_capturezone","Enable"); new ent = -1; if(FindEntityByClassname(ent,"tf_logic_koth") == -1){ //kothの場合タイマーは止めたままにしておく Entity_Input("team_round_timer","Resume"); } //変数のリセット g_ent = -1; //スポーン処理 for(i = 1;i<MaxClients;i++){ if(IsClientConnected(i) == true && IsClientInGame(i) == true && GetClientTeam(i) >= 2){ //全員リスポーンさせる TF2_RespawnPlayer(i); } } } //不正な値の場合元に戻す else{ PrintToServer("[WDM]ERROR:値が不正です。"); SetConVarInt(convar,StringToInt(oldValue)); } return true; } //ConVar変更時(スポーン範囲変更時) public OnConVarChanged_range(ConVar convar, const char[] oldValue, const char[] newValue){ new String:strName[20]; new iMax; new iMin; //数値以外が入力された or 負の値が入力された if((StringToInt(newValue) == 0 && StrEqual(newValue, "0") == false) || StringToInt(newValue) < 0){ PrintToServer("[WDM]ERROR:値が不正です。"); SetConVarInt(convar,StringToInt(oldValue)); return false; } //どちらのConvarか確認用 GetConVarName(convar, strName, sizeof(strName)); if(StrEqual(strName, "sm_wdm_rangemax")){ iMax = StringToInt(newValue); iMin = GetConVarInt(v_range_min); } else{ //else if(StrEqual(strName, "sm_wdm_rangemin"){ iMax = GetConVarInt(v_range_max); iMin = StringToInt(newValue); } //差分を確認 if((iMax - iMin) < SPAWN_RANGE){ PrintToServer("[WDM]スポーン範囲は最低%d確保するようにしてください。",SPAWN_RANGE); SetConVarInt(convar, StringToInt(oldValue)); return false; } return true; } //関数 マップ確認、基準CPの確保 public Func_CPCheck(){ //マップを確認する エラーは-1 new ent = -1; new eTemp = -1; new eCP = -1; new iOwner[4]; new iTemp; new String:strFinalCP[255]; new String:strProp[255]; //new i; //item_teamflagが存在したならばCTFである while((ent = FindEntityByClassname(ent,"item_teamflag")) != -1) { PrintToServer("[WDM]ERROR:対応していないマップです。(マップがCTFです。)"); return -1; } //tf_logic_multiple_escortが存在したならばPLRである while((ent = FindEntityByClassname(ent,"tf_logic_multiple_escort")) != -1) { PrintToServer("[WDM]ERROR:対応していないマップです。(マップがPLRです。)"); return -1; } //マップがPL、A/Dの場合 while((ent = FindEntityByClassname(ent,"team_control_point_round")) != -1){ //各ラウンドのCPの所有者を確認 GetEntPropString(ent, Prop_Data, "m_iszCPNames", strProp, sizeof(strProp)); new i; //現在のマップの最終CP名を確保 while((i = BreakString(strProp[i], strFinalCP, sizeof(strFinalCP))) != -1){ Format(strProp, sizeof(strProp), "%s", strProp[i]); } //名前から最終CPのEntを確保 while((eTemp = FindEntityByClassname(eTemp,"team_control_point")) != -1){ GetEntPropString(eTemp, Prop_Data, "m_iName", strProp, sizeof(strProp)); if(StrEqual(strProp, strFinalCP)){ //キャプチャーされてないCPにおいて if(GetEntProp(eTemp, Prop_Data, "m_iDefaultOwner") == GetEntProp(eTemp, Prop_Send, "m_iTeamNum")){ //プライオリティのより高いCPのent番号を確保 if(iTemp <= (iTemp = GetEntProp(ent, Prop_Data, "m_nPriority"))){ eCP = eTemp; } } } } } //PL,A/Dの対象CPがある場合、終了 if(eCP != -1){ return eCP; } //それ以外のマップ while((ent = FindEntityByClassname(ent,"team_control_point")) != -1) { //各チームの所有CP数をカウント iTemp = GetEntProp(ent, Prop_Data, "m_iDefaultOwner"); iOwner[iTemp]++; //所有者無しCPは対象CPの可能性が高いので確保(kothもしくは普通の5cp系マップ) if(iTemp == 0){ eCP = ent; } //一番Index値の高いCPは最終CPの可能性が高い if(IsValidEntity(eTemp)){ if(GetEntProp(ent, Prop_Data, "m_iPointIndex") > GetEntProp(eTemp, Prop_Data, "m_iPointIndex")){ eTemp = ent; } } else{ eTemp = ent; } } //所有者無しCPが1つの場合、それで確定 if(iOwner[TFTeam_Unassigned] == 1){ return eCP; } else if(iOwner[TFTeam_Unassigned] > 1){ //所有者無しCPが複数あるマップは対象外とする オレンジとか return -1; } //CPが存在しない if(iOwner[TFTeam_Red] == 0 && iOwner[TFTeam_Blue] == 0){ return -1; } //全てのCPを片方のチームが所有している場合A/Dと判断し、Indexの一番大きいCPを対象とする if(iOwner[TFTeam_Red] == 0 || iOwner[TFTeam_Blue] == 0){ return eTemp; } //マップ不明 return -1; } //関数 初期化処理 public Func_Start(){ //待機中なら有効化させない new ent = -1; new String:strTimer[30]; while((ent = FindEntityByClassname(ent, "team_round_timer")) != -1){ GetEntPropString(ent, Prop_Data, "m_iName", strTimer, sizeof(strTimer)); if(StrEqual(strTimer, "zz_teamplay_waiting_timer") == true){ break; } } //マップとCPを確認 g_ent = Func_CPCheck(); if(g_ent == -1){ PrintToChatAll("[WDM]ERROR:プラグインを有効化できませんでした。"); SetConVarInt(v_enable,0,false,false); return; } if(ent == -1){ g_wait = false; } else{ g_wait = true; } //スポーン処理 new i; for(i = 1;i<MaxClients;i++){ if(IsClientConnected(i) == true && IsClientInGame(i) == true && GetClientTeam(i) >= 2){ //全員リスポーンさせる TF2_RespawnPlayer(i); } } if(g_wait == false){ //Entityを停止させる Entity_Input("team_control_point_master","Disable"); Entity_Input("team_control_point","Disable"); Entity_Input("trigger_capture_area","Disable"); Entity_Input("func_capturezone","Disable"); Entity_Input("team_round_timer","Pause"); } } //HookEvent ラウンド開始時 public OnRoundStart(Handle:event, const String:name[], bool:dontBroadcast){ if(GetConVarInt(v_enable) == 0){ return; } //初期化処理 Func_Start(); } //HookEvent スポーン時 public OnPlayerSpawned(Handle:event, const String:name[], bool:dontBroadcast){ if(GetConVarInt(v_enable) == 0 || g_wait == true){ return; } new client = GetClientOfUserId(GetEventInt(event, "userid")); if(IsClientInGame(client) == false){ return; } while(!Func_Spawn(client)){ //PrintToServer("[WDM]ERROR:トレースに失敗しました。再試行します。"); } //オバヒさせる SetEntityHealth(client, RoundToNearest(GetEntProp(client, Prop_Data, "m_iMaxHealth") * 1.5)); ClientCommand(client, "playgamesound Item.Materialize"); return; } //スポーンさせる public Func_Spawn(int client){ //トレースが一番外のボックスの外側から開始→TR_DidHitがfalse //トレースがマップの外側で一番外のボックスの内側から開始→スポーン地点が不正でTeleportEntityがエラー new Float:vecCP[3]; new Float:angTR[3] = {90.0,0.0,0.0}; new Float:posTR[3]; new Float:flSizeMin[3] = {-24.0,-24.0,-40.0}; new Float:flSizeMax[3] = {24.0,24.0,40.0}; new Float:flRangeMax = GetConVarFloat(v_range_max); new Float:flRangeMin = GetConVarFloat(v_range_min); new Float:flRand; new Handle:trTrace = INVALID_HANDLE; new String:strEntName[255]; //基準CPの座標を確認 if(IsValidEntity(g_ent) == false){ return false; } GetEntPropVector(g_ent, Prop_Data, "m_vecOrigin", vecCP); //基準CPを元に指定範囲内でランダムにスポーン地点を決定 new i; new iFlag; do{ for(;i<3;i++){ flRand = GetURandomFloat() * (flRangeMax*2); //範囲内のランダム値を取得(前後に最大値確保の為倍がけ) flRand = flRand - flRangeMax; //+-範囲内に直す為最大値の値を引く posTR[i] = vecCP[i] + flRand; //CPを基準にランダム値分座標をずらす } if(FloatAbs(flRand) < flRangeMin){ //CPから最低距離以上離れているか? 小さければやり直し iFlag++; } } while(iFlag == 3); //トレース開始 trTrace = TR_TraceRayFilterEx(posTR, angTR, MASK_PLAYERSOLID, RayType_Infinite,TraceEntityFilterPlayer); //トレース結果を取得 if(TR_DidHit(trTrace) == true){ TR_GetEndPosition(posTR, trTrace); } else{ //トレースに失敗 CloseHandle(trTrace); return false; } //トレースで地下世界まで行った(トレースにより範囲外に出た) if((vecCP[2] - flRangeMax) > posTR[2]){ CloseHandle(trTrace); return false; } //トレース終了 CloseHandle(trTrace); //トレースハル開始 trTrace = TR_TraceHullEx(posTR, posTR, flSizeMin, flSizeMax, MASK_SOLID); //着地点が死亡床もしくはスポーンルームだったらやり直し if(TR_GetEntityIndex(trTrace) != -1){ GetEntityClassname(TR_GetEntityIndex(trTrace), strEntName, sizeof(strEntName)); if(StrEqual(strEntName, "trigger_hurt") == true || StrEqual(strEntName, "func_respawnroom") == true) { //スポーン不許可 CloseHandle(trTrace); return false; } } //トレースハル終了 CloseHandle(trTrace); //出現位置確定、トレースハルの為ボックスの中央位置をズラす //出現位置補正+20 ボックス位置補正+40 ボックスサイズは48.48.80 posTR[2] += 60.0; //トレースハル開始 trTrace = TR_TraceHullEx(posTR, posTR, flSizeMin, flSizeMax, MASK_PLAYERSOLID); if(TR_DidHit(trTrace) == true){ //スポーン不可 スタックする CloseHandle(trTrace); return false; } //トレースハル終了 CloseHandle(trTrace); //ボックス位置補正-40 posTR[2] += -40.0; //2点間の角度を取得 angTRを再利用する angTR[0] = angTR[2] = 0.0; //ピッチ(上下角)とロール(回転角)は変更しない angTR[1] = RadToDeg(ArcTangent2(vecCP[1] - posTR[1], vecCP[0] - posTR[0])); //スポーン位置にワープ TeleportEntity(client, posTR, angTR, NULL_VECTOR); return true; } //トレース用フィルター //true でhit falseで通過? public bool:TraceEntityFilterPlayer(entity, contentsMask){ //entityがMaxClientsより大きい(プレイヤー予約枠ではない)か、0である場合trueを返す //この場合、自分を含めプレイヤーはトレース対象にならない //他プレイヤーをトレースで探す場合、自分のみ通過させる return entity > MaxClients || !entity; } //HookEvent 死亡時 public OnPlayerDeath(Handle:event, const String:name[], bool:dontBroadcast){ //プラグイン有効時のみ if(GetConVarInt(v_enable) == 0 || g_wait == true){ return; } //デッドリンガーは除外する if(GetEventInt(event,"death_flags") & TF_DEATHFLAG_DEADRINGER == TF_DEATHFLAG_DEADRINGER){ return; } //強制スポーン CreateTimer(0.2, Cmd_Respawn, GetClientOfUserId(GetEventInt(event,"userid"))); } //スポーンタイマー public Action:Cmd_Respawn(Handle:timer, any:client){ //時間経過後、スポーンする if(IsClientInGame(client) == true){ TF2_RespawnPlayer(client); } }