Описание работы плагинов

AlmazON

Не путать с самим yand3xmail
Сообщения
5,099
Реакции
2,755
Описание работы плагинов

Тема призвана помочь новичкам, начинающим и, возможно даже, любителям освоить язык программирования SourcePawn после изучения всех основ и материалов, вроде "Введение в SourcePawn программирование". Т.е. детально показывает, как можно применять их на практике для написания своих настоящих и будущих плагинов.
Если у кого-либо есть желание научить "строить код", то эта тема также для вас.
Желательно оформить свой пост по такой схеме:
C-подобный:
1. Краткое описание назначения и функций плагина. По возможности, прилагать с ссылкой на его оригинальную версию и полное описание.
2. Ваш действующий код с пояснениями под [I]спойлером[/I] с тегом [B]PHP[/B] кода.
PHP:
Пример
C-подобный:
3. Прикреплённый скрипт-файл [I].sp[/I] с комментариями работы (файл-повтор) и нормальной табуляцией.
 

AlmazON

Не путать с самим yand3xmail
Сообщения
5,099
Реакции
2,755
Resetscore+

Расшифровка кода для плагина обнуления смертей и/или фрагов - Resetscore+.

PHP:
// Включение списков файлов всех структур, функций, вызовов и тегов, которые имеются в наличии.
#include <sourcemod>

// Объявляем новый дескриптор h_mode и переменную с целым значением rsm для квара "sm_resetscore_mode".
new Handle:h_mode, rsm,
// Объявляем новый дескриптор h_annonce и переменную с целым значением ann для квара "sm_resetscore_annonce".
	Handle:h_annonce, ann,
// Объявляем новую строку, хранящую 8 значений (команд) в массиве с "пустым" общим количеством занимаемого места (символов).
	String:ds[8][] = {"!rd", "1rd", "!resetdeath", "!rs", "1rs", "!кы", "1кы", "!resetscore"};

// Общественная информация о плагине.
public Plugin:myinfo = 
{
	name = "Resetscore+",									// Название плагина.
	author = "AlmazON",										// Автор плагина.
	description = "Сбрасывает ваши смерти или счёт",		// Краткое описание плагина.
	version = "1.0.4",										// Версия данного плагина.
	url = "http://www.hlmod.ru"								// Сайт поддержки плагина (автора).
}

// Встроенное глобальное событие, функция которого - единождый вызов при полной инициализации плагина.
public OnPluginStart()
{
// Создаём квар "sm_resetscore_mode" и приравниваем его значение к ранее созданному дескриптору h_mode. Устанавливаем ему минимум 0, максимум 3.
	h_mode = CreateConVar("sm_resetscore_mode",			"3",	"0 - off, 1 - resetdeath, 2 - resetscore, 3 - all.", 0, true, 0.0, true, 3.0);
// Создаём квар "sm_resetscore_annonce и приравниваем его значение к ранее созданному дескриптору h_annonce. Устанавливаем ему минимум 0, максимум 3.
	h_annonce = CreateConVar("sm_resetscore_annonce",	"3",	"0 - disable, 1 - chat, 2 - annonce, 3 - all.", 0, true, 0.0, true, 3.0);

// Возращаем целое значение дескриптора h_mode и приравниваем его к ранее созданной переменной rsm.
	rsm = GetConVarInt(h_mode);
// Возращаем целое значение дескриптора h_annonce и приравниваем его к ранее созданной переменной ann.
	ann = GetConVarInt(h_annonce);
// Отлавливаем изменение значения дескриптора h_mode и вызываем CVarChange.
	HookConVarChange(h_mode, CVarChange);
// Отлавливаем изменение значения дескриптора h_annonce и вызываем CVarChange.
	HookConVarChange(h_annonce, CVarChange);

// Отлавливаем встроенное глобальное событие игры "round_start" с методом: после его начала и без переменных, и вызываем RoundStart.
	HookEvent("round_start", RoundStart, EventHookMode_PostNoCopy);
// Прямая внутренняя функция, добавляющая обратный вызов Say при отправке команды "say" (общий чат).
	AddCommandListener(Say, "say");
// Прямая внутренняя функция, добавляющая обратный вызов Say при отправке команды "say_team" (коммандный чат).
	AddCommandListener(Say, "say_team");
}

// Функция обратного вызова CVarChange.
public CVarChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
// Условие: если уникальный идентификатор convar этой функции равен h_mode, то выполнить операцию, приведённую ниже.
	if (convar == h_mode)
// Преобразуем строку в целое число с новым значением для переменной rsm.
		rsm = StringToInt(newValue);
// Условие: если следующий уникальный идентификатор convar этой функции равен h_annonce, то выполнить операцию, приведённую ниже.
	else if (convar == h_annonce)
// Преобразуем строку в целое число с новым значением для переменной rsm.
		ann = StringToInt(newValue);
}

// Функция обратного вызова RoundStart.
public Action:RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
// Условие: для выполнения операций ниже, переменная rsm должна быть истиной (true), а переменная ann больше 1.
	if (rsm && ann > 1)
	{
// Условие: для выполнения операции ниже, переменная rsm не должна быть равной 2.
		if (rsm != 2)
// Отправка всем сообщения в чат.
			PrintToChatAll("\x01\x04[Resetdeath]\x01 Сбросить \x05смерти\x01: \x03%s\x01, \x03%s\x01 или \x03%s\x01.", ds[0], ds[1], ds[2]);
// Условие: для выполнения операции ниже, переменная rsm не должна быть равной 1.
		if (rsm != 1)
// Отправка всем сообщения в чат.
			PrintToChatAll("\x01\x04[Resetscore]\x01 Сбросить \x05счёт\x01: \x03%s\x01, \x03%s\x01, \x03%s\x01 или \x03%s\x01.", ds[3], ds[5], ds[6], ds[7]);
	}
}

// Функция обратного вызова Say.
public Action:Say(client, const String:command[], args)
{
// Условие: для выполнения всех операций ниже, переменная rsm должна быть истиной.
	if (rsm)
	{
// Готовим строку text, которая пока что содержит хлам, к заполнению символами, ограничив её ёмкость 200 символами кодировки UTF8.
		decl String:text[200];
// Получаем всю строку текущих команд, направляемых на сервер.
		GetCmdArgString(text, sizeof(text));
// Создаём новые переменные для количества убийств GetClientDeaths и фрагов GetClientFrags для игрока с индексом из client.
		new deaths = GetClientDeaths(client), frags = GetClientFrags(client),
// Создаём новую переменную обнаружения нужных нам строк команд обнуления смертей rd, отсутствие которых в сумме равно 0.
			rd = StrContains(text, ds[0], false) * StrContains(text, ds[1], false) + StrContains(text, ds[2], false),
// Создаём новую переменную обнаружения нужных нам строк команд обнуления фрагов rs, отсутствие которых в сумме равно 0.
			rs = (StrContains(text, ds[3], false) + StrContains(text, ds[4], false)) * StrContains(text, ds[5], false) + StrContains(text, ds[6], false) +
				StrContains(text, ds[7], false);
// Условие: для выполнения операции ниже, переменная rsm не должна быть равной 1, переменные смертей или фрагов - обязательно истинны, как и переменная rs.
		if (rsm != 1 && (deaths || frags) && rs)
		{
// Устанавливаем нулевое количество смертей для для игрока с индексом из client.
			SetEntProp(client, Prop_Data, "m_iDeaths", 0);
// Устанавливаем нулевое количество фрагов для для игрока с индексом из client.
			SetEntProp(client, Prop_Data, "m_iFrags", 0);
// Условие: для выполнения операции ниже, переменная ann должна быть истинной и не равна 2.
			if (ann && ann != 2)
// Отправляем сообщение клиенту, успешно обнулившего свои смерти и фраги.
				PrintToChat(client, "\x01\x04[Resetscore]\x01 Ваш \x05общий счёт \x03сброшен\x01!");
		}
// Условие: для выполнения операции ниже, переменная rsm не должна быть равна 2, а переменные смертей deaths и обнаружения команд их сброса rd - истинны.
		else if (rsm != 2 && deaths && rd)
		{
// Устанавливаем нулевое количество смертей для для игрока с индексом из client.
			SetEntProp(client, Prop_Data, "m_iDeaths", 0);
// Условие: для выполнения операции ниже, переменная ann должна быть истинной и не равной 2.
			if (ann && ann != 2)
// Отправляем сообщение клиенту, успешно обнулившего свои смерти.
				PrintToChat(client, "\x01\x04[Resetdeath]\x01 Ваш \x05счёт смертей \x03сброшен\x01!");
		}
// Условие: для выполнения всех операций ниже, переменная ann должна быть истинной и не равной 2, а переменная количества смертей deaths - ложной.
		else if (ann && ann != 2 && !deaths)
		{
// Условие: для выполнения операции ниже, переменная rsm не должна быть равна 1, переменная количества фрагов frags должна быть 0, а rs - истинной.
			if (rsm != 1 && !frags && rs)
// Отправляем сообщение клиенту, что его смерти и фраги не нуждаются в сбросе.
				PrintToChat(client, "\x01\x04[Resetscore]\x01 Ваш \x05общий счёт\x01 равен \x030\x01!");
// Условие: для выполнения операции ниже, переменная rsm не должна быть равна 2, а переменная обнаружения смертей rd - необходима истинной.
			else if (rsm != 2 && rd)
// Отправляем сообщение клиенту, что его смерти не нуждаются в сбросе.
				PrintToChat(client, "\x01\x04[Resetdeath]\x01 У вас итак \x03нет \x05смертей\x01!");
		}
	}
}
Советы по улучшению кода в данном случае неуместны.
Поправки на ощутимые ошибки в объяснениях работы принимаются.
Может объясняю не лучшим образом, но, как я считаю, достаточно грамотным.
Если кому-то что-то не нравится - напишите и "разложите по полочкам" свой плагин. :rtfm:
 

Вложения

  • Resetscore+_v1.0.4 - полный разбор работы.sp
    10.8 КБ · Просмотры: 34

AlmazON

Не путать с самим yand3xmail
Сообщения
5,099
Реакции
2,755
Auto Respawn

Расшифровка кода для плагина воскрешения игроков (прототип DM) - Auto Respawn.

Настройки(CVAR's):
sm_autorespawn_time - задержка между смертью и воскрешением.
sm_endtime_autorespawn - максимальный срок воскрешений в каждом раунде.
PHP:
// Включение списков файлов всех структур, функций, вызовов и тегов, которые имеются в наличии.
#include <sourcemod>
// Подключаемая библиотека SourceMod API - cstrike.
#include <cstrike>

// Объявляем новый дескриптор TimeAutoRespawn для квара "sm_endtime_autorespawn".
new Handle:TimeAutoRespawn,
// Объявляем новый дескриптор RetryAutoRespawn для квара "sm_autorespawn_time".
	Handle:RetryAutoRespawn,
// Объявляем новый дескриптор RStimer для контроля таймера.
	Handle:RStimer,
// Объявляем новую переменную для хранения одного из 2 логических значений (сейчас по умолчанию - false или 0).
	bool:lock;

// Встроенное глобальное событие, функция которого - единождый вызов при полной инициализации плагина.
public OnPluginStart()
{
// Создаём квар "sm_autorespawn_time" и приравниваем его значение к ранее созданному дескриптору RetryAutoRespawn. Устанавливаем ему минимум 0,1.
	RetryAutoRespawn = CreateConVar("sm_autorespawn_time",		"0.1",		"Задержка до воскрешения после смерти (секунд).", 0, true, 0.1);
// Создаём квар "sm_endtime_autorespawn" и приравниваем его значение к ранее созданному дескриптору TimeAutoRespawn. Устанавливаем ему минимум 0,0.
	TimeAutoRespawn = CreateConVar("sm_endtime_autorespawn",	"60.0",		"Время окончания автореспауна от начала раунда.", 0, true, 0.0);

// Отлавливаем встроенное глобальное событие игры "round_start" с методом: после его начала и без переменных, и вызываем RoundStart.
	HookEvent("round_start", RoundStart, EventHookMode_PostNoCopy);
// Отлавливаем встроенное глобальное событие игры "round_end" с методом: после его начала и без переменных, и вызываем RoundEnd.
	HookEvent("round_end", RoundEnd, EventHookMode_PostNoCopy);
}

// Функция обратного вызова RoundStart.
public Action:RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
// Условный оператор if показывает, что для выполнения операции ниже величина переменной lock должна стать отрицанием её истинного значения.
	if (!lock)
	{
// Отлавливаем встроенное глобальное событие игры "player_death" с методом после его начала и вызываем PlayerDeath.
		HookEvent("player_death", PlayerDeath, EventHookMode_Post);
// Приравниваем переменную lock к значению true (истина).
		lock = true;
	}
// Создаём таймер, равный RStimer. Счётчик устанавливает возвращаемое значение TimeAutoRespawn с плавающей запятой, истечение которого вызывает Stop.
	RStimer = CreateTimer(GetConVarFloat(TimeAutoRespawn), Stop);
}

// ункция обратного вызова RoundEnd.
public Action:RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
{
// Условие: если RStimer не равен INVALID_HANDLE, значит таймер этого дескриптора активен и необходимо выполнить операцию ниже.
	if (RStimer != INVALID_HANDLE)
	{
// Убивает ("обнуляет" без вызова его функции) таймер RStimer.
		KillTimer(RStimer);
// Сбрасываем дескриптор таймера RStimer на INVALID_HANDLE, чтобы знать, что таймер больше не активен.
		RStimer = INVALID_HANDLE;
	}
}

// Функция обратного вызова PlayerDeath.
public Action:PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
{
//  Получаем индекс родившегося игрока для новой переменной client и с помощью неё же - индекс команды в переменной team.
	new client = GetClientOfUserId(GetEventInt(event, "userid")), team = GetClientTeam(client);
// Условие: если игрок в игре, индекс его команды выше 1 (не спектратор) и он мёртв, то выполнить операцию ниже.
	if (IsClientInGame(client) && team > 1 && !IsPlayerAlive(client))
// Создать таймер для задержки возрождения client'а на время значения RetryAutoRespawn с плавающей запятой и после вызвать RespawnPlayer.
		CreateTimer(GetConVarFloat(RetryAutoRespawn), RespawnPlayer, client, TIMER_FLAG_NO_MAPCHANGE);
}

// Функция обратного вызова RespawnPlayer для таймера из PlayerDeath.
public Action:RespawnPlayer(Handle:Timer, any:client)
{
// Готовим строку text, которая пока что содержит хлам, к заполнению символами, ограничив её ёмкость 40 символами кодировки UTF8.
	decl String:game[40];
// Получаем каталог с названием игры.
	GetGameFolderName(game, sizeof(game));
// Условие: если название идентично обозначению игры cstrike или csgo, то выполнить операцию ниже.
	if (StrEqual(game, "cstrike") || StrEqual(game, "csgo"))
// Возродить игрока с индексом из client.
		CS_RespawnPlayer(client);
// Если ни одно из предыдущих условий не соответствует действительности, то выполнить операцию ниже.
	else
	{
// Прекратить отлов всех трёх используемых в плагине встроенных событий игры, фактически деактивируя его.
		UnhookEvent("round_start", RoundStart, EventHookMode_PostNoCopy);
		UnhookEvent("round_end", RoundEnd, EventHookMode_PostNoCopy);
		UhEPD();
// Занести в журнал SM сообщение об ошибке плагина: "Игра не поддерживается!".
		LogError("The game is not supported!");
	}
}

// Функция обратного вызова Stop для таймера из RoundStart.
public Action:Stop(Handle:timer)
{
// Условный оператор if показывает, что для выполнения операции ниже величина переменной lock должна быть истинной.
	if (lock)
	{
// Вызвать (сокращаемую) часто используемую функцию UhEPD.
		UhEPD();
// Приравниваем переменную lock к значению false (ложь).
		lock = false;
	}
// Сбрасываем дескриптор таймера RStimer на INVALID_HANDLE, чтобы знать, что таймер итак не активен.
	RStimer = INVALID_HANDLE;
}

// Функция обратного вызова UhEPD.
UhEPD()
{
// Прекратить отлов встроенного глобального события игры "player_death", которое было вызвано до этого, т.е. прекратить исполнять PlayerDeath
	UnhookEvent("player_death", PlayerDeath, EventHookMode_Post);
}
Это и есть оригинал.
 

Вложения

  • AutoRespawn - полный разбор работы.sp
    7.9 КБ · Просмотры: 34

AlmazON

Не путать с самим yand3xmail
Сообщения
5,099
Реакции
2,755
Перенос строки "\n" в CS:GO или "экстра чат" в любой игре

Реализация функций (stock), как сделать "бесконечный чат", а также полностью рабочим символ переноса строки "\n" в CS:GO.

Общая длина текста фактически ограничена лишь пределом буфера строк самого SourceMod'а.
Количество символов "\n", максимума как бы и нет - конечный пользователь может делать перенос хоть через каждую букву.
Текст для переноса - до 192 символов (это предел чата).
Код потребляет ровно столько памяти, сколько требуется для всех переносов (но 192 символа для одного переноса - см.выше).
Дополнительно 192 символа отведено для переменных/строк (%d, %i, %s, %x и т.п.).
Также может работать, как обычный PrintToChat или PrintToChatAll, когда нет ни единого символа переноса "\n".
Цвета поддерживаются самой функцией! Приблизительно полная аналогия со стандартными нативами в целом.

В файле, с основными пояснениями, показаны действующие примеры использования для отправки сообщений игроку и всем подряд.
Текст получен с помощью лишь 1 строчки:
24a12eee81fd.jpg

cfaa0434452f.jpg
Внимание! Это не плагин, который выполняет вышеописанные функции, а лишь код, который можно использовать в любых плагинах.

Использование:
Копируем нужную stock функцию в свой плагин и просто подставляем ко всем "расширяемым" PrintToChat или PrintToChatAll приставку Extra.

P.S. Можете использовать данные функции ExtraPrintToChat и ExtraPrintToChatAll в своих плагинах, с вас лишь "Спасибо". :beer:
 

Вложения

  • ExtraPrintToChat&All_v2.0.0.sp
    2.3 КБ · Просмотры: 181

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #5
http://hlmod.ru/forum/novye-plaginy/31606-map-comments.html

PHP:
#pragma semicolon 1
#include <sourcemod>
#include <sdktools_stringtables>
#pragma newdecls required

public void OnMapStart() // Ловим старт карты
{
	DirectoryListing hDirectory = OpenDirectory("maps/"); // Открываем папку maps
	if (hDirectory != null) // Если папка успешно открыта
	{
		// Создаем переменные до цикла, т.к. в цикле каждый раз создавать не оптимально
		File hFile, hFile2;
		char sMap[128], sMapPath[128], sBuffer[256];
		FileType type;
		int i;
		while (hDirectory.GetNext(sMap, sizeof(sMap), type))  // GetNext будет получать имя файла/директории в текущем каталоге
		{
			if (type == FileType_File) // Если получили файл то ...
			{
				i = FindCharInString(sMap, '.');	// Ищем точку в имени файла чтобы определить расширение
				if(i != -1 && strcmp(sMap[i], ".bsp") == 0)	// Если файл имеет расширение bsp, тоесть это карта
				{
					sMap[i] = 0; // Обрезаем расширение
					
					FormatEx(sMapPath, sizeof(sMapPath), "maps/%s.txt", sMap); // Формируем новое расширение
					if(FileExists(sMapPath) == false)	// Если такого файла еще нет
					{
						hFile = OpenFile("maps/base.txt", "rt");	// Открываем наш base.txt, Флаги: (r - для чтения, t - текстовый докумен)
						hFile2 = OpenFile(sMapPath, "wt");	// Открываем файл куда будем записывать (его мы формировали), Флаги: (w - для записи, t - текстовый докумен)
						while(hFile.EndOfFile() == false && hFile.ReadLine(sBuffer, sizeof(sBuffer)))
						{
							// EndOfFile - проверяет не дошли ли мы до конца файла
							// ReadLine считывает строку с файла
							// Мы построчно считываем файл maps/base.txt и так же построчно записываем его содержимое в наш новый файл.
						//	LogMessage("ReadLine: '%s''", sBuffer);
							ReplaceString(sBuffer, sizeof(sBuffer), "\n", ""); // Удаляем перенос строки
							hFile2.WriteLine(sBuffer);	// Записываем строку в файл
						}

						// Удаляем переменные (чтобы не было утечек)
						delete hFile;
						delete hFile2;
					}
					
					AddFileToDownloadsTable(sMapPath); // Добавляем наш файл для загрузки игрокам
					
				}
				
			}
		}

		delete hDirectory;
	}
}
 

Hejter

xor ebx, ebx
Сообщения
1,759
Реакции
393
http://hlmod.ru/forum/novye-plaginy/31606-map-comments.html

PHP:
#pragma semicolon 1
#include <sourcemod>
#include <sdktools_stringtables>
#pragma newdecls required

public void OnMapStart() // Ловим старт карты
{
	DirectoryListing hDirectory = OpenDirectory("maps/"); // Открываем папку maps
	if (hDirectory != null) // Если папка успешно открыта
	{
		// Создаем переменные до цикла, т.к. в цикле каждый раз создавать не оптимально
		File hFile, hFile2;
		char sMap[128], sMapPath[128], sBuffer[256];
		FileType type;
		int i;
		while (hDirectory.GetNext(sMap, sizeof(sMap), type))  // GetNext будет получать имя файла/директории в текущем каталоге
		{
			if (type == FileType_File) // Если получили файл то ...
			{
				i = FindCharInString(sMap, '.');	// Ищем точку в имени файла чтобы определить расширение
				if(i != -1 && strcmp(sMap[i], ".bsp") == 0)	// Если файл имеет расширение bsp, тоесть это карта
				{
					sMap[i] = 0; // Обрезаем расширение
					
					FormatEx(sMapPath, sizeof(sMapPath), "maps/%s.txt", sMap); // Формируем новое расширение
					if(FileExists(sMapPath) == false)	// Если такого файла еще нет
					{
						hFile = OpenFile("maps/base.txt", "rt");	// Открываем наш base.txt, Флаги: (r - для чтения, t - текстовый докумен)
						hFile2 = OpenFile(sMapPath, "wt");	// Открываем файл куда будем записывать (его мы формировали), Флаги: (w - для записи, t - текстовый докумен)
						while(hFile.EndOfFile() == false && hFile.ReadLine(sBuffer, sizeof(sBuffer)))
						{
							// EndOfFile - проверяет не дошли ли мы до конца файла
							// ReadLine считывает строку с файла
							// Мы построчно считываем файл maps/base.txt и так же построчно записываем его содержимое в наш новый файл.
						//	LogMessage("ReadLine: '%s''", sBuffer);
							ReplaceString(sBuffer, sizeof(sBuffer), "\n", ""); // Удаляем перенос строки
							hFile2.WriteLine(sBuffer);	// Записываем строку в файл
						}

						// Удаляем переменные (чтобы не было утечек)
						delete hFile;
						delete hFile2;
					}
					
					AddFileToDownloadsTable(sMapPath); // Добавляем наш файл для загрузки игрокам
					
				}
				
			}
		}

		delete hDirectory;
	}
}

Оффтоп
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #9
Оффтоп
 

Truyn

King of clowns
Сообщения
1,591
Реакции
749
Еще бы парочку плагинов на новом синтаксисе кто нибудь бы разложил, я может и понимать что то начал бы
Сообщения автоматически склеены:

Это очень хорошая тема, я когда читаю уроки не выкупаю в силу своей тупости,а когда каждая строка расписана это вообще красота
 

Paranoiiik

хачю клиентмод
Сообщения
2,047
Реакции
1,491
Еще бы парочку плагинов на новом синтаксисе кто нибудь бы разложил, я может и понимать что то начал бы
Сообщения автоматически склеены:

Это очень хорошая тема, я когда читаю уроки не выкупаю в силу своей тупости,а когда каждая строка расписана это вообще красота
Написал бы, что конкретно непонятно. Может, кто-то пояснит
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,569
Реакции
5,071
@Truyn, кмк, уроки достаточно подробно расписаны.
Я учился по коду плагинов, справке по API и вики на AM.
Хотя пока ещё многим не пользовался. Например, базами данных и ADT.
 
Сверху Снизу