alchemistarkの日記

やったことのメモ

Entityについて解説したい 1

詰まるようなところでは無いのかもしれないが、自分は延々と詰まったので解説したい
だがどこからどう解説したものか…

「自分はこういう認識をしている」という体で話します。
実際どうであるかとはかけ離れている可能性は大いにあります。


Entityとは、ゲーム上に存在するほぼ全ての物がEntityです。地面を転がっている酒瓶から、プレイしているプレイヤーキャラクターもEntityです。
ここではそう認識します。ぶっちゃゲーム内で管理されていていて個別の番号を割り振られているオブジェクトの事をEntityだと思ってて特に困らないと思います。
逆に、Entityで無い物もあります。地面や壁等のsolidがそうです。ただし、Entity化されたSolidを除きます。*1
このあたりはマップ制作の話に被ります。Entityを分かるのに避けて通れないと思うので、プラグイン開発者はHammerの最低限の知識が必須です。*2

Valve ハンマーエディタ講座 4.1
必読ですよ というかHammerの資料少なすぎやしませんか…
こちら必要な事は一通り抑えてあり、記述している所を一通りやれば次に必要な事は自分で見つけられると思います。

尚、TF2のHammerEditerはtf2インストール配下のbinフォルダ内にあります。SourceSDKは不要です
大事なことなのでもう一度言います。SourceSDKは不要です

話が少しそれましたが、Entityは大きく分けて2種類あります。
・SolidをEntity化したbrushエンティティ
・マップ上に直接設置、もしくはゲームエンジンが動的に生成するpointエンティティ
ポイントエンティティは実体を持つ(ゲーム上に表示される)物と、実体を持たない(表示されず、機能のみを持つ)ものがあります。

ブラシエンティティは例えばキャプチャーエリアの範囲やリスポンルームの範囲等を作る事ができます。
ポイントエンティティは弾薬やヘルスキット等のアイテムや、ゲームのタイマー、物理演算オブジェクト等多岐に渡ります。

Entityの一覧を貼っておきます。
どのようなEntityがあってどんな機能を持っているのか、見てみても良いかもしれません。

SourceEngine全般
List of base entities - Valve Developer Community

TF2
List of Team Fortress 2 Entities - Valve Developer Community


次があったらInputとOutputの話をします。
とりあえず今回はゲーム上の殆どのものはEntityなんだって思ってくれたらOKです。

*1:ごく例外として、Solidを生成して消えてしまうEntityもあります…prop_staticとかがそうです

*2:Entityのプロパティに何があるか確認するだけでも凄い助かる

KothIntelプラグイン

KothでCPの代わりにインテルを使うプラグイン
インテルを拾っている間タイマーが動作します。落とすと止まります。


リスポン前に要塞作った園児がインテル持って立てこもるんですよね分かってます

ソースは貼りません(面倒になった)
希望者はご連絡ください

kothのタイマーを操作する

クソみたいに遠回りした…
とりあえず順番に語ります。本題だけ見たい人は最後のコードを見てください。


kothのタイマーを操作したい
とりあえず止めたり動かしたり時間を変えたりしたい

ではkothのタイマーはどこにあるのか?
とりあえず思いつくのはtf_logic_koth、tf_gamerules、team_round_timer

tf_logic_kothはとりあえずkothのHUDを表示してる
後タイマーにセットする時間を持っている

tf_gamerulesにはkoth関係と思われるInputはSetRedKothClockActiveとSetBlueKothClockActiveしかない
これはkothのタイマーをスタートさせるだけ 一方が動き出すともう片方は止まる。

team_round_timerは複数のマップをHammerEditerで開いてみたところ、
そもそもkothマップには置いてない事が多いように見える。

つまり、bspファイル内にkothのタイマーを記録しているEntityは存在しない…?

と思っていたらtf_logic_kothのページにこのような記述を発見。

It creates two entities named zz_red_koth_timer and zz_blue_koth_timer which have inputs like pause,resume,etc

これは、zz_red_koth_timerとzz_blue_koth_timerという名前の2つのエンティティを作成します。これらのエンティティには、一時停止、再開などの入力があります。

動的に作ってんのかよ!!!

じゃープラグインからzz_*_koth_timerってEntity探したり作ったりしてみようか
まで考えたけど見つからないし作成できず。

うん、僕バカなのでzz_*_koth_timerってClassnameでEntity探したよね

個別のEntity名ですってば。プロパティ名で言うとtargetname
恐らくEntityClassnameはteam_round_timerだと思われ

HammerEditerでOutput先にzz_~を指定するとOutput先が存在しないって言われるけど動作します。
何かのイベントが起きるとタイマーが止まるkothマップとか作れます。

zz_~で調べてたら、その名前で引っ掛けてEntityを特定してるコードを発見。
けどもう少し調べたらtf_gamerulesのNetPropに普通に格納されてた
んでtf_gamerulesはsdktoolsをincludeしたらGameRules_GetPropEntで読めるから…
あれ、これコード凄い単純じゃね…?

以下コード。

public Action:Cmd_get(client, args){

	//タイマーのEntityを確保する
	new bluetimerEnt = GameRules_GetPropEnt("m_hBlueKothTimer");
	new redtimerEnt = GameRules_GetPropEnt("m_hRedKothTimer");

	new float:fBluTime;
	new float:fRedTime;
	
	//青タイマー確認	//止まっていると1、動いていると0
	if(GetEntProp(bluetimerEnt, Prop_Send, "m_bTimerPaused") == 0){
		//タイマー動作中(タイマー開始時の時刻-現在時刻)
		fBluTime = GetEntPropFloat(bluetimerEnt, Prop_Send, "m_flTimerEndTime") -  GetGameTime();
	}
	else{
		//タイマー停止中
		fBluTime = GetEntPropFloat(bluetimerEnt, Prop_Send, "m_flTimeRemaining");
	}
	
	//赤タイマー確認
	if(GetEntProp(redtimerEnt, Prop_Send, "m_bTimerPaused") == 0){
		fRedTime = GetEntPropFloat(redtimerEnt, Prop_Send, "m_flTimerEndTime") -  GetGameTime();
	}
	else{
		fRedTime = GetEntPropFloat(redtimerEnt, Prop_Send, "m_flTimeRemaining");
	}
	
	//表示する
	PrintToChat(client,"Blue:%f Red:%f",fBluTime,fRedTime);
	PrintToChat(client,"BlueTimer:%d RedTimer:%d",GetEntProp(bluetimerEnt, Prop_Send, "m_bTimerPaused"),GetEntProp(redtimerEnt, Prop_Send, "m_bTimerPaused"));
	
	/*
	SetEntProp(bluetimerEnt, Prop_Send, "m_bTimerPaused",1);
	"m_bTimerPaused"に1を書き込むとタイマーが停止するが、同時に"m_flTimeRemaining"の値でタイマーが上書きされる
	停止させる前に手動で"m_flTimeRemaining"の値を更新すること
	また、CPは取得されたままになるのでCPの所有者を開放する等の処置を取ること
	*/
	
}

多分team_round_timeのInputが使えるので、全部GetEntPropする必要は無いと思う。
コメントにも書いてるけど、タイマーをただ停止させるとタイマーが動く前の時間に戻されるので注意。
先にTimeRemainingの値を書き換えておけばOK

そういえば両方動かしたら両方同時に動き出してちょっと笑える

ていうか最終的にコピペしたのがmorestocksなのちょっと納得行かない
つまりmorestocksをincludeした方が断然早い。

sourcemod-snippets/tf2_morestocks.inc at master · powerlord/sourcemod-snippets · GitHub

SouceModプラグインの開発を始めるにあたって必要なもの

SourcePawnのWiki作ろうって瞬間だけ思って放置してるやつに書いてたやつ。

準備するもの

プラグインのアイディア、プラグインを作成して行いたいこと

これが無ければ始まりません。

MetaMod、SourceModが動作する環境

srcdsのインストールにて解説します。(機会があれば書きます)

SourcePawnのコンパイラ

SourceModがインストールされていれば、
addons/sourcemod/scripting/compile.exe
にあります。

また、ブラウザから利用できるコンパイラとしてSourceModの配布ページの
www.sourcemod.net

こちらか、

Spider - SourcePawn Compiler

こちらが利用できます。

下の方はincファイルのincludeができるのでこちらの方が少々多機能です。

テキストエディタ

文字コード UTF-8(BOMなし)で保存できるエディタ なら何でも良いです。
プラグイン自体は文字コードを問いませんが、KeyValueを使用する時に使うファイルは UTF-8(BOMなし)である必要があります。

お好みのエディタがあるならばそれを、特にこだわらないならNotepad++を推奨します。
「Notepad++」オープンソースで開発されているプログラマー向けのテキストエディタ
forest.watch.impress.co.jp


SourcePawn用のハイライト機能を使うことができます。

こちらで配布されています。
forums.alliedmods.net


プラグインになりますが、NppExecというプラグインを導入することによって保存してそのままコンパイラにファイルを送ることができるので、
こちらも導入を推奨します。

NppExecの使い方を解説しているページ
NppExec (コマンドの実行) | Notepad++のプラグイン解説




設定例

NPP_SAVE 
(インストールフォルダ)\addons\sourcemod\scripting\spcomp.exe "$(FULL_CURRENT_PATH)" 
powershell move $(NAME_PART).smx (インストールフォルダ)\addons\sourcemod\plugins\ -force

別にpowershellで無くても良いですが、サーバーパスがsmbの向こう側だったりすると必要です。

以上が揃えばプラグインを書く準備が整います。

PropSpawnプラグイン

クソプラグイン作った
指定したPropを出現させて位置を設定、設置した位置を保存できる
ラウンド開始時に自動でロードする機能付き
マップをカスタムしたりしてどうぞ

以下ソースコード全文

続きを読む

Propの出現と破壊可能にする方法

ゴリゴリ書いてたのでとりあえず全部貼る。

public Action:Cmd_prop(client, args){

	new ent = CreateEntityByName("prop_dynamic");
	new filter;
	new String:temp[255];
	

	//フィルター関係///////////////////////////
	
	while ((filter = FindEntityByClassname(filter, "filter_activator_tfteam")) != -1){
		GetEntPropString(filter, Prop_Data, "m_iGlobalname", temp,255);
		if(StrEqual(temp,"prop_filter")){
			//既にフィルターが生成されていた場合、削除する
			AcceptEntityInput(filter, "Kill");
		}
	}
	
	filter = CreateEntityByName("filter_activator_tfteam");	//フィルターを作成する
	SetEntProp(filter, Prop_Data, "m_iTeamNum",TFTeam_Blue);	//フィルター対象チームを設定
	
	DispatchSpawn(filter);	//スポーンさせる	
	
	//フィルター名を設定する
	SetEntPropString(filter, Prop_Data, "m_iGlobalname", "prop_filter");

	//Propにフィルターを設定する
	//フィルター名とフィルターEntityが両方必要?
	SetEntPropString(ent, Prop_Data, "m_iszDamageFilterName","prop_filter");
	
	//PropにフィルターEntityを設定
	SetEntPropEnt(ent, Prop_Data, "m_hDamageFilter",filter);
	
	//Prop関係//////////////////////////////////////
	
	//Propのダメージイベントをフックする
	HookSingleEntityOutput(ent,"OnTakeDamage",EntityHook,false);
	
	//Propにモデルを設定する
	SetEntPropString(ent, Prop_Data, "m_ModelName", "models/props_badlands/barrel_flatbed01.mdl");
	//衝突判定を設定
	SetEntProp(ent, Prop_Data, "m_nSolidType",6);
	
	//Propをスポーンさせる
	DispatchSpawn(ent);
	
	//Propのヘルスを設定する
	SetEntProp(ent, Prop_Data, "m_iHealth", 200);
	
	//Propがダメージを受けるようにする
	SetEntProp(ent, Prop_Data, "m_takedamage", 2, 1);	//Mortal
	
	
	//適当な位置に出現させる
	new float:vec[3];
	GetClientAbsOrigin(client, vec);
	TeleportEntity(ent, vec, NULL_VECTOR, NULL_VECTOR);
	
}

public EntityHook(const char[] output, int caller, int activator, float delay){
	PrintToChatAll("%d",GetEntProp(caller, Prop_Data, "m_iHealth"));
}

大体コメントの通り
寝て良いよ

SetEntProp(ent, Prop_Data, "m_nSolidType",6);の値が6なのは何故か分かってない
一応プロパティのCollisionsの値がUse VPhysicsの時6になる。
KeyValueのキー名はsolid。

破壊可能にするだけならヘルスを設定してMortal化するだけでいいはず
ただし破壊された時何も表示せずただ消えるだけなので、破壊時にパーティクルを表示する等の配慮が必要。

フィルターは指定チームのみダメージを与えられるように設定してるので、誰でも壊せるならフィルター自体不要
なるべくフィルターには変な名前付けてマップ既存のフィルターと重複しないように注意した方がいいと思うけど大丈夫じゃないかな

あと言うまでもないけどTeleportEntityの座標がクライアントの位置なのでそのまま呼ぶと自分がPropにハマるから注意。

prop_dynamic - Valve Developer Community
filter_activator_tfteam - Valve Developer Community


追記
prop_physics_overrideを呼び出した場合、何故かHookSingleEntityOutputが上手く動作しないので
HookEntityOutputを使用した方がいいかも。この場合Entityの番号を保存しておくかユニークな名称を付けておく必要があるのでかなりめんどくさい。

prop_physics_override - Valve Developer Community

ていうかね!違いが全然分かってないんだよね!!!
prop_physics→モデル側で対応が必要
prop_physics_multiplayer→わからない モデルが動作中はプレイヤーがすり抜けてしまう?(モデルが静止するとCollisionが復活するのでプレイヤーが埋まる)
prop_physics_override→physics非対応モデルをphysics化?以降Entityのクラス名はprop_physicsになる
prop_physics_respawnable→知らない子です