Browse Source

Add natives for creating attributes

nosoop 1 year ago
parent
commit
510dff7c4d
7 changed files with 361 additions and 2 deletions
  1. 1 0
      AMBuilder
  2. 8 1
      econmanager.h
  3. 6 0
      mmsplugin.cpp
  4. 176 0
      natives.cpp
  5. 71 0
      natives.h
  6. 98 0
      scripting/include/tf_econ_dynamic.inc
  7. 1 1
      smsdk_config.h

+ 1 - 0
AMBuilder

@@ -6,6 +6,7 @@ projectName = 'tf2econdynamic'
 sourceFiles = [
   'mmsplugin.cpp',
   'econmanager.cpp',
+  'natives.cpp',
 ]
 
 ###############

+ 8 - 1
econmanager.h

@@ -47,7 +47,7 @@ public:
  */
 class AutoKeyValues {
 	public:
-	AutoKeyValues() : m_pKeyValues{nullptr} {}
+	AutoKeyValues() : m_pKeyValues{new KeyValues("auto")} {}
 	
 	AutoKeyValues(KeyValues *pKeyValues) : m_pKeyValues{pKeyValues->MakeCopy()} {}
 	AutoKeyValues(const AutoKeyValues &other) : m_pKeyValues{other.m_pKeyValues->MakeCopy()} {}
@@ -59,6 +59,13 @@ class AutoKeyValues {
 		// }
 	}
 	
+	void Assign(KeyValues *pKeyValues) {
+		if (m_pKeyValues) {
+			m_pKeyValues->deleteThis();
+		}
+		m_pKeyValues = pKeyValues->MakeCopy();
+	}
+	
 	KeyValues *operator->() const { return m_pKeyValues; }
 	operator KeyValues *() const { return m_pKeyValues; }
 	

+ 6 - 0
mmsplugin.cpp

@@ -9,6 +9,7 @@
 #include <stdio.h>
 #include "mmsplugin.h"
 #include "econmanager.h"
+#include "natives.h"
 
 #include <filesystem.h>
 
@@ -33,6 +34,9 @@ bool DynSchema::SDK_OnLoad(char *error, size_t maxlen, bool late)
 {
 	SH_ADD_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &DynSchema::Hook_LevelInitPost, true);
 
+	sharesys->AddNatives(myself, g_EconAttributeNatives);
+	g_EconInjectedAttributeType = g_pHandleSys->CreateType("EconInjectedAttribute", &g_EconInjectedAttributeHandler, 0, NULL, NULL, myself->GetIdentity(), NULL);
+
 	/* Prepare our manager */
 	if (!g_EconManager.Init(error, maxlen)) {
 		return false;
@@ -43,6 +47,8 @@ bool DynSchema::SDK_OnLoad(char *error, size_t maxlen, bool late)
 
 void DynSchema::SDK_OnUnload() {
 	SH_REMOVE_HOOK_MEMFUNC(IServerGameDLL, LevelInit, gamedll, this, &DynSchema::Hook_LevelInitPost, true);
+	
+	g_pHandleSys->RemoveType(g_EconInjectedAttributeType, myself->GetIdentity());
 }
 
 bool DynSchema::Hook_LevelInitPost(const char *pMapName, char const *pMapEntities,

+ 176 - 0
natives.cpp

@@ -0,0 +1,176 @@
+/**
+ * vim: set ts=4 sw=4 tw=99 noet :
+ * ======================================================
+ * TF2 Dynamic Schema Injector
+ * Written by nosoop
+ * ======================================================
+ */
+
+#include <stdio.h>
+#include "mmsplugin.h"
+#include "natives.h"
+#include "econmanager.h"
+
+
+HandleType_t g_EconInjectedAttributeType{};
+EconInjectedAttributeHandler g_EconInjectedAttributeHandler;
+
+void EconInjectedAttributeHandler::OnHandleDestroy(HandleType_t type, void* object) {
+	if (type == g_EconInjectedAttributeType) {
+		delete reinterpret_cast<AutoKeyValues*>(object);
+	}
+}
+
+cell_t sm_EconAttributeCreate(IPluginContext *pContext, const cell_t *params) {
+	AutoKeyValues *pContainer = new AutoKeyValues();
+	return g_pHandleSys->CreateHandle(g_EconInjectedAttributeType, pContainer,
+			pContext->GetIdentity(), myself->GetIdentity(), NULL);
+}
+
+// void EconInjectedAttribute.SetClass(const char[] attrClass)
+cell_t sm_EconAttributeSetClass(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	char *value;
+	pContext->LocalToString(params[2], &value);
+	(*pContainer)->SetString("attribute_class", value);
+	
+	return 0;
+}
+
+// void EconInjectedAttribute.SetName(const char[] attrName)
+cell_t sm_EconAttributeSetName(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	char *value;
+	pContext->LocalToString(params[2], &value);
+	(*pContainer)->SetString("name", value);
+	
+	return 0;
+}
+
+// void EconInjectedAttribute.SetDescriptionFormat(const char[] attrDescFmt)
+cell_t sm_EconAttributeSetDescriptionFormat(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	char *value;
+	pContext->LocalToString(params[2], &value);
+	(*pContainer)->SetString("description_format", value);
+	
+	return 0;
+}
+
+// void EconInjectedAttribute.SetCustom(const char[] key, const char[] value)
+cell_t sm_EconAttributeSetCustomKeyValue(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	char *key, *value;
+	pContext->LocalToString(params[2], &key);
+	pContext->LocalToString(params[3], &value);
+	(*pContainer)->SetString(key, value);
+	
+	return 0;
+}
+
+// void EconInjectedAttribute.SetDefIndex(int attrdef)
+cell_t sm_EconAttributeSetDefIndex(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	(*pContainer)->SetName(std::to_string(params[2]).c_str());
+	
+	return 0;
+}
+
+// void EconInjectedAttribute.ClearDefIndex()
+cell_t sm_EconAttributeClearDefIndex(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	(*pContainer)->SetName("auto");
+	
+	return 0;
+}
+
+// bool EconInjectedAttribute.Register()
+cell_t sm_EconAttributeRegister(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	
+	if (!g_EconManager.RegisterAttribute(*pContainer) || !g_EconManager.InsertOrReplaceAttribute(*pContainer)) {
+		return false;
+	}
+	return true;
+}
+
+// void EconInjectedAttribute.Clear();
+cell_t sm_EconAttributeClear(IPluginContext *pContext, const cell_t *params) {
+	HandleSecurity sec(pContext->GetIdentity(), myself->GetIdentity());
+	HandleError err;
+	
+	Handle_t hndl = static_cast<Handle_t>(params[1]);
+	
+	AutoKeyValues *pContainer = nullptr;
+	if ((err = g_pHandleSys->ReadHandle(hndl, g_EconInjectedAttributeType, &sec, (void**) &pContainer)) != HandleError_None) {
+		return pContext->ThrowNativeError("Invalid EconInjectedAttribute handle %x (error: %d)", hndl, err);
+	}
+	
+	KeyValues::AutoDelete empty("auto");
+	pContainer->Assign(empty);
+	
+	return 0;
+}

+ 71 - 0
natives.h

@@ -0,0 +1,71 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * TF2 Econ Dynamic Extension
+ * Copyright (C) 2023 nosoop.  All rights reserved.
+ * =============================================================================
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, version 3.0, as published by the
+ * Free Software Foundation.
+ * 
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, AlliedModders LLC gives you permission to link the
+ * code of this program (as well as its derivative works) to "Half-Life 2," the
+ * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
+ * by the Valve Corporation.  You must obey the GNU General Public License in
+ * all respects for all other code used.  Additionally, AlliedModders LLC grants
+ * this exception to all derivative works.  AlliedModders LLC defines further
+ * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
+ * or <http://www.sourcemod.net/license.php>.
+ *
+ * Version: $Id$
+ */
+
+#ifndef _INCLUDE_SOURCEMOD_EXTENSION_NATIVES_H_
+#define _INCLUDE_SOURCEMOD_EXTENSION_NATIVES_H_
+/**
+ * @file natives.h
+ * @brief Declares native bindings for SourcePawn.
+ */
+ 
+class EconInjectedAttributeHandler : public IHandleTypeDispatch {
+public:
+	void OnHandleDestroy(HandleType_t type, void* object);
+};
+
+extern HandleType_t g_EconInjectedAttributeType;
+extern EconInjectedAttributeHandler g_EconInjectedAttributeHandler;
+
+cell_t sm_EconAttributeCreate(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeSetClass(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeSetName(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeSetDescriptionFormat(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeSetCustomKeyValue(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeSetDefIndex(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeClearDefIndex(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeRegister(IPluginContext *pContext, const cell_t *params);
+cell_t sm_EconAttributeClear(IPluginContext *pContext, const cell_t *params);
+
+const sp_nativeinfo_t g_EconAttributeNatives[] = {
+	{ "EconInjectedAttribute.EconInjectedAttribute", sm_EconAttributeCreate },
+	{ "EconInjectedAttribute.SetClass", sm_EconAttributeSetClass },
+	{ "EconInjectedAttribute.SetName", sm_EconAttributeSetName },
+	{ "EconInjectedAttribute.SetDescriptionFormat", sm_EconAttributeSetDescriptionFormat },
+	{ "EconInjectedAttribute.SetCustom", sm_EconAttributeSetCustomKeyValue },
+	{ "EconInjectedAttribute.SetDefIndex", sm_EconAttributeSetDefIndex },
+	{ "EconInjectedAttribute.ClearDefIndex", sm_EconAttributeClearDefIndex },
+	{ "EconInjectedAttribute.Register", sm_EconAttributeRegister },
+	{ "EconInjectedAttribute.Clear", sm_EconAttributeClear },
+	
+	{NULL, NULL},
+};
+
+#endif //_INCLUDE_SOURCEMOD_EXTENSION_NATIVES_H_

+ 98 - 0
scripting/include/tf_econ_dynamic.inc

@@ -0,0 +1,98 @@
+/**
+ * An InjectedAttribute is an abstraction over the KeyValues representation of an attribute
+ * definition.
+ */
+methodmap EconInjectedAttribute < Handle {
+	/**
+	 * Creates a new, empty EconInjectedAttribute handle.
+	 * 
+	 * The attribute is not injected into the schema until EconInjectedAttribute.Register() is
+	 * called.
+	 * 
+	 * This handle should be freed by the calling plugin.
+	 */
+	public native EconInjectedAttribute();
+	
+	/**
+	 * Sets the attribute class.  All values under the same attribute class are combined.
+	 * This is required.
+	 */
+	public native void SetClass(const char[] attrClass);
+	
+	/**
+	 * Sets the attribute name.  Only one instance of this per game entity can be active.
+	 * If this is empty or NULL_STRING, it will be set to the attribute class on registration.
+	 */
+	public native void SetName(const char[] attrName);
+	
+	/**
+	 * Sets the description format.  This determines how multiple values with the same attribute
+	 * class are combined.
+	 * 
+	 * Generally, "value_is_additive" and "value_is_percentage" are the main ones that matter,
+	 * but being precise is ideal in case you want to use the description format when displaying
+	 * values to players as with actual schema attributes.
+	 */
+	public native void SetDescriptionFormat(const char[] attrDescFmt);
+	
+	/**
+	 * Sets a custom key / value pair.
+	 * 
+	 * Note that "injected" is a key reserved by the plugin that is set to 1 when registered,
+	 * overriding any other values set.
+	 * 
+	 * The game has set other keys in the schema in the past, and it may use new ones in the
+	 * future.
+	 */
+	public native void SetCustom(const char[] key, const char[] value);
+	
+	/**
+	 * Sets the attribute's definition index.
+	 * 
+	 * By default, the extension will automatically allocate a previously unused index.  As the
+	 * schema may be reloaded between maps, there is no guarantee that an attribute will
+	 * continue to exist at a specific index.  If an index is manually specified and a
+	 * attribute now exists at the index, the injection of the attribute will fail.
+	 * 
+	 * Plugins are expected to either perform a lookup by attribute name, or preferably, use the
+	 * hook-style natives with the attribute class.
+	 * 
+	 * This native is solely provided for backwards compatibility with schema formats that
+	 * manually specify indices.
+	 */
+	public native void SetDefIndex(int attrdef);
+	
+	/**
+	 * Clears the attribute's specified definition index.
+	 */
+	public native void ClearDefIndex();
+	
+	/**
+	 * Stores a copy of the attribute properties in the core plugin to be injected.
+	 * 
+	 * This operation does not clear existing values on the EconInjectedAttribute, so you can
+	 * create multiple attributes by repeatedly calling EconInjectedAttribute.SetName() and
+	 * EconInjectedAttribute.Register().
+	 * 
+	 * If an attribute with the same name already exists and is not an injected attribute
+	 * managed by the core plugin, this will fail.
+	 * 
+	 * The attribute definition index may be reallocated if a new copy of the schema is loaded
+	 * in and the index is occupied by a non-injected attribute.
+	 */
+	public native bool Register();
+	
+	/**
+	 * Imports the properties of an attribute by name to the EconInjectedAttribute handle,
+	 * replacing all values currently set.
+	 * 
+	 * @return    True if the attribute exists, otherwise false.
+	 */
+	// TODO this isn't implemented yet
+	// public native bool Import(const char[] name);
+	
+	/**
+	 * Resets the container to its initial state.
+	 */
+	public native void Clear();
+}

+ 1 - 1
smsdk_config.h

@@ -60,7 +60,7 @@
 
 /** Enable interfaces you want to use here by uncommenting lines */
 //#define SMEXT_ENABLE_FORWARDSYS
-//#define SMEXT_ENABLE_HANDLESYS
+#define SMEXT_ENABLE_HANDLESYS
 //#define SMEXT_ENABLE_PLAYERHELPERS
 //#define SMEXT_ENABLE_DBMANAGER
 #define SMEXT_ENABLE_GAMECONF