From 445663c1106b994d6eb29baea26467abb4631ecc Mon Sep 17 00:00:00 2001 From: Alienmario Date: Sun, 15 Dec 2024 03:16:22 +0100 Subject: [PATCH] API overhaul - Added api example plugin - Sound types are no longer hardcoded, extend as you wish - Added custom per-model properties support - Added forward `ModelChooser_OnConfigLoaded` - Added natives `ModelChooser_GetCurrentModelProperty`, `ModelChooser_OpenChooser`, `ModelChooser_PlayRandomSound` `ModelChooser_GetProperty`, `ModelChooser_GetModelList`, `ModelChooser_GetSoundMap` --- .github/workflows/plugin.yml | 1 + configs-bms/player_models.cfg | 7 + configs-hl2mp/player_models.cfg | 7 + scripting/include/model_chooser.inc | 43 ---- scripting/include/model_chooser/natives.inc | 77 ------- scripting/include/modelchooser.inc | 75 +++++++ .../{model_chooser => modelchooser}/anims.inc | 8 +- .../commands.inc | 10 +- .../config.inc | 93 +++++---- .../globals.inc | 1 + .../{model_chooser => modelchooser}/menu.inc | 145 +++++++------ scripting/include/modelchooser/natives.inc | 186 +++++++++++++++++ .../sounds.inc | 8 +- .../structs.inc | 191 ++++++++++++++---- .../{model_chooser => modelchooser}/utils.inc | 12 -- .../viewmodels.inc | 0 scripting/modelchooser_api_example.sp | 53 +++++ scripting/ultimate_modelchooser.sp | 50 +++-- 18 files changed, 648 insertions(+), 319 deletions(-) delete mode 100644 scripting/include/model_chooser.inc delete mode 100644 scripting/include/model_chooser/natives.inc create mode 100644 scripting/include/modelchooser.inc rename scripting/include/{model_chooser => modelchooser}/anims.inc (87%) rename scripting/include/{model_chooser => modelchooser}/commands.inc (91%) rename scripting/include/{model_chooser => modelchooser}/config.inc (87%) rename scripting/include/{model_chooser => modelchooser}/globals.inc (98%) rename scripting/include/{model_chooser => modelchooser}/menu.inc (82%) create mode 100644 scripting/include/modelchooser/natives.inc rename scripting/include/{model_chooser => modelchooser}/sounds.inc (86%) rename scripting/include/{model_chooser => modelchooser}/structs.inc (59%) rename scripting/include/{model_chooser => modelchooser}/utils.inc (94%) rename scripting/include/{model_chooser => modelchooser}/viewmodels.inc (100%) create mode 100644 scripting/modelchooser_api_example.sp diff --git a/.github/workflows/plugin.yml b/.github/workflows/plugin.yml index c1aaf9a..f520af7 100644 --- a/.github/workflows/plugin.yml +++ b/.github/workflows/plugin.yml @@ -62,6 +62,7 @@ jobs: - name: Create package run: | + rm plugins/modelchooser_api_example.smx OUT="/tmp/build" SM="${OUT}/addons/sourcemod" mkdir -p $SM/configs diff --git a/configs-bms/player_models.cfg b/configs-bms/player_models.cfg index 0322dba..bd7852f 100644 --- a/configs-bms/player_models.cfg +++ b/configs-bms/player_models.cfg @@ -59,6 +59,13 @@ "path" "models/example.ext" "path" "models/another.ext" } + + // Extensibility support for plugin API + "custom" + { + "key" "value" + } + } // ---------------------------------------------------------------------------------------- diff --git a/configs-hl2mp/player_models.cfg b/configs-hl2mp/player_models.cfg index bba8118..a5c8093 100644 --- a/configs-hl2mp/player_models.cfg +++ b/configs-hl2mp/player_models.cfg @@ -75,6 +75,13 @@ "path" "models/example.ext" "path" "models/another.ext" } + + // Extensibility support for plugin API + "custom" + { + "key" "value" + } + } // ---------------------------------------------------------------------------------------- diff --git a/scripting/include/model_chooser.inc b/scripting/include/model_chooser.inc deleted file mode 100644 index 660282a..0000000 --- a/scripting/include/model_chooser.inc +++ /dev/null @@ -1,43 +0,0 @@ -#if defined _model_chooser_included - #endinput -#endif -#define _model_chooser_included - -#define MODELCHOOSER_LIBRARY "ModelChooser" - -public SharedPlugin __pl_model_chooser = -{ - name = MODELCHOOSER_LIBRARY, - file = "ultimate_modelchooser.smx", -#if defined REQUIRE_PLUGIN - required = 1, -#else - required = 0, -#endif -}; - -#if !defined REQUIRE_PLUGIN -public void __pl_model_chooser_SetNTVOptional() -{ - MarkNativeAsOptional("ModelChooser_GetCurrentModelName"); - MarkNativeAsOptional("ModelChooser_GetCurrentModelPath"); - MarkNativeAsOptional("ModelChooser_UnlockModel"); - MarkNativeAsOptional("ModelChooser_LockModel"); - MarkNativeAsOptional("ModelChooser_SelectModel"); - MarkNativeAsOptional("ModelChooser_IsClientChoosing"); -} -#endif - -forward void ModelChooser_OnModelChanged(int client, const char[] modelName); - -native bool ModelChooser_GetCurrentModelName(int client, char[] modelName, int maxLength); - -native bool ModelChooser_GetCurrentModelPath(int client, char[] modelPath, int maxLength); - -native bool ModelChooser_UnlockModel(int client, char[] modelName, bool select = false); - -native bool ModelChooser_LockModel(int client, char[] modelName); - -native bool ModelChooser_SelectModel(int client, char[] modelName); - -native bool ModelChooser_IsClientChoosing(int client); diff --git a/scripting/include/model_chooser/natives.inc b/scripting/include/model_chooser/natives.inc deleted file mode 100644 index fbb8ec8..0000000 --- a/scripting/include/model_chooser/natives.inc +++ /dev/null @@ -1,77 +0,0 @@ -#pragma semicolon 1 -#pragma newdecls required - -public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) -{ - CreateNative("ModelChooser_GetCurrentModelName", Native_GetCurrentModelName); - CreateNative("ModelChooser_GetCurrentModelPath", Native_GetCurrentModelPath); - CreateNative("ModelChooser_UnlockModel", Native_UnlockModel); - CreateNative("ModelChooser_LockModel", Native_LockModel); - CreateNative("ModelChooser_SelectModel", Native_SelectModel); - CreateNative("ModelChooser_IsClientChoosing", Native_IsClientChoosing); - RegPluginLibrary("ModelChooser"); - return APLRes_Success; -} - -public any Native_GetCurrentModelName(Handle plugin, int numParams) -{ - PlayerModel model; - - if (GetSelectedModel(GetNativeCell(1), model)) - { - SetNativeString(2, model.name, GetNativeCell(3)); - return true; - } - return false; -} - -public any Native_GetCurrentModelPath(Handle plugin, int numParams) -{ - PlayerModel model; - - if (GetSelectedModel(GetNativeCell(1), model)) - { - SetNativeString(2, model.path, GetNativeCell(3)); - return true; - } - return false; -} - -public any Native_UnlockModel(Handle plugin, int numParams) -{ - char modelName[MAX_MODELNAME]; - GetNativeString(2, modelName, sizeof(modelName)); - String_ToUpper(modelName, modelName, sizeof(modelName)); - int client = GetNativeCell(1); - - UnlockModel(client, modelName); - if (GetNativeCell(3)) - { - return SelectModelByName(client, modelName); - } - return true; -} - -public any Native_LockModel(Handle plugin, int numParams) -{ - char modelName[MAX_MODELNAME]; - GetNativeString(2, modelName, sizeof(modelName)); - String_ToUpper(modelName, modelName, sizeof(modelName)); - - LockModel(GetNativeCell(1), modelName); - return true; -} - -public any Native_SelectModel(Handle plugin, int numParams) -{ - char modelName[MAX_MODELNAME]; - GetNativeString(2, modelName, sizeof(modelName)); - String_ToUpper(modelName, modelName, sizeof(modelName)); - - return SelectModelByName(GetNativeCell(1), modelName); -} - -public any Native_IsClientChoosing(Handle plugin, int numParams) -{ - return menuSelection[GetNativeCell(1)].index != -1; -} \ No newline at end of file diff --git a/scripting/include/modelchooser.inc b/scripting/include/modelchooser.inc new file mode 100644 index 0000000..1f47653 --- /dev/null +++ b/scripting/include/modelchooser.inc @@ -0,0 +1,75 @@ +#if defined _model_chooser_included + #endinput +#endif +#define _model_chooser_included + +#define MODELCHOOSER_LIBRARY "ModelChooser" + +public SharedPlugin __pl_model_chooser = +{ + name = MODELCHOOSER_LIBRARY, + file = "ultimate_modelchooser.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public void __pl_model_chooser_SetNTVOptional() +{ + MarkNativeAsOptional("ModelChooser_GetCurrentModelName"); + MarkNativeAsOptional("ModelChooser_GetCurrentModelPath"); + MarkNativeAsOptional("ModelChooser_GetCurrentModelProperty"); + MarkNativeAsOptional("ModelChooser_UnlockModel"); + MarkNativeAsOptional("ModelChooser_LockModel"); + MarkNativeAsOptional("ModelChooser_SelectModel"); + MarkNativeAsOptional("ModelChooser_IsClientChoosing"); + MarkNativeAsOptional("ModelChooser_OpenChooser"); + MarkNativeAsOptional("ModelChooser_PlayRandomSound"); + MarkNativeAsOptional("ModelChooser_GetProperty"); + MarkNativeAsOptional("ModelChooser_GetModelList"); + MarkNativeAsOptional("ModelChooser_GetSoundMap"); +} +#endif + +/* Forwards */ + +forward void ModelChooser_OnConfigLoaded(); + +forward void ModelChooser_OnModelChanged(int client, const char[] modelName); + +/* Natives */ + +native bool ModelChooser_GetCurrentModelName(int client, char[] modelName, int maxLength); + +native bool ModelChooser_GetCurrentModelPath(int client, char[] modelPath, int maxLength); + +native bool ModelChooser_GetCurrentModelProperty(int client, const char[] key, char[] value, int maxLength); + +native bool ModelChooser_UnlockModel(int client, const char[] modelName, bool select = false); + +native bool ModelChooser_LockModel(int client, const char[] modelName); + +native bool ModelChooser_SelectModel(int client, const char[] modelName); + +native bool ModelChooser_IsClientChoosing(int client); + +native bool ModelChooser_OpenChooser(int client, bool printErrorMsg); + +native bool ModelChooser_PlayRandomSound(int client, const char[] soundType, bool toAll = false, bool stopLast = true, int pitch = 100, float volume = 1.0); + +native bool ModelChooser_GetProperty(const char[] modelName, const char[] key, char[] value, int maxLength); + +#if defined MODELCHOOSER_RAWDOG_API + +#include +#include +#include + +native ModelList ModelChooser_GetModelList(); + +native SoundMap ModelChooser_GetSoundMap(); + +#endif diff --git a/scripting/include/model_chooser/anims.inc b/scripting/include/modelchooser/anims.inc similarity index 87% rename from scripting/include/model_chooser/anims.inc rename to scripting/include/modelchooser/anims.inc index bccc41f..dd9d817 100644 --- a/scripting/include/model_chooser/anims.inc +++ b/scripting/include/modelchooser/anims.inc @@ -61,12 +61,8 @@ int GetCustomSequenceForAnim(int client, PLAYER_ANIM playerAnim, float &playback float time = GetGameTime(); if (time > nextJumpSound[client]) { - { - SoundPack soundPack; - GetSoundPack(model, soundPack); - PlayRandomSound(soundPack.jumpSounds, client, client, SNDCHAN_STATIC, true, JUMP_PITCH_MIN, JUMP_PITCH_MAX, JUMP_VOL); - nextJumpSound[client] = time + model.jumpSndParams.cooldown.Rand(); - } + PlayRandomSound(GetSoundPack(model).GetSoundList("jump"), client, client, SNDCHAN_STATIC, true, JUMP_PITCH_MIN, JUMP_PITCH_MAX, JUMP_VOL); + nextJumpSound[client] = time + model.jumpSndParams.cooldown.Rand(); } } else if (playerAnim == PLAYER_WALK) diff --git a/scripting/include/model_chooser/commands.inc b/scripting/include/modelchooser/commands.inc similarity index 91% rename from scripting/include/model_chooser/commands.inc rename to scripting/include/modelchooser/commands.inc index 215b88f..b84bbf6 100644 --- a/scripting/include/model_chooser/commands.inc +++ b/scripting/include/modelchooser/commands.inc @@ -18,7 +18,7 @@ public Action Command_UnlockModel(int client, int args) return Plugin_Handled; } - char arg1[65], arg2[MAX_MODELNAME]; + char arg1[65], arg2[MODELCHOOSER_MAX_NAME]; GetCmdArg(1, arg1, sizeof(arg1)); GetCmdArg(2, arg2, sizeof(arg2)); @@ -42,8 +42,7 @@ public Action Command_UnlockModel(int client, int args) String_ToUpper(arg2, arg2, sizeof(arg2)); - PlayerModel model; - if (modelList.FindByName(arg2, model) == -1) + if (modelList.FindByName(arg2) == -1) { ReplyToCommand(client, "Model named %s doesn't exist!", arg2); return Plugin_Handled; @@ -74,7 +73,7 @@ public Action Command_LockModel(int client, int args) return Plugin_Handled; } - char arg1[65], arg2[MAX_MODELNAME]; + char arg1[65], arg2[MODELCHOOSER_MAX_NAME]; GetCmdArg(1, arg1, sizeof(arg1)); GetCmdArg(2, arg2, sizeof(arg2)); @@ -98,8 +97,7 @@ public Action Command_LockModel(int client, int args) String_ToUpper(arg2, arg2, sizeof(arg2)); - PlayerModel model; - if (modelList.FindByName(arg2, model) == -1) + if (modelList.FindByName(arg2) == -1) { ReplyToCommand(client, "Model named %s doesn't exist!", arg2); return Plugin_Handled; diff --git a/scripting/include/model_chooser/config.inc b/scripting/include/modelchooser/config.inc similarity index 87% rename from scripting/include/model_chooser/config.inc rename to scripting/include/modelchooser/config.inc index 6c9bb6e..535fdb8 100644 --- a/scripting/include/model_chooser/config.inc +++ b/scripting/include/modelchooser/config.inc @@ -32,6 +32,9 @@ void LoadConfig() while (kv.GotoNextKey()); } delete kv; + + Call_StartForward(fwdOnConfigLoaded); + Call_Finish(); } void ParseModels(KeyValues kv) @@ -73,7 +76,6 @@ void ParseModels(KeyValues kv) } kv.GetString("sounds", model.sounds, sizeof(model.sounds)); - String_ToUpper(model.sounds, model.sounds, sizeof(model.sounds)); ParseInterval(kv, model.jumpSndParams.cooldown, "jumpSoundTime"); ParseInterval(kv, model.hurtSndHP, "hurtSoundHP", HURT_SOUND_HP, HURT_SOUND_HP); @@ -100,6 +102,13 @@ void ParseModels(KeyValues kv) ParseFileItems(kv, false); kv.GoBack(); } + + model.customProperties = new StringMap(); + if (kv.GetDataType("custom") == KvData_None && kv.JumpToKey("custom")) + { + ParseCustomProperties(kv, model.customProperties); + kv.GoBack(); + } modelList.PushArray(model); } @@ -194,53 +203,36 @@ void ParseSounds(KeyValues kv) { if (kv.GotoFirstSubKey()) { + char soundPack[MODELCHOOSER_MAX_NAME]; do { - SoundPack soundPack; - char name[MAX_SOUNDSNAME]; - if (kv.GetSectionName(name, sizeof(name))) + if (kv.GetSectionName(soundPack, sizeof(soundPack))) { - if (kv.JumpToKey("Hurt")) - { - soundPack.hurtSounds = ParseFileItems(kv, true, "sound"); - kv.GoBack(); - } - else soundPack.hurtSounds = CreateArray(); - - if (kv.JumpToKey("Death")) - { - soundPack.deathSounds = ParseFileItems(kv, true, "sound"); - kv.GoBack(); - } - else soundPack.deathSounds = CreateArray(); - - if (kv.JumpToKey("View")) - { - soundPack.viewSounds = ParseFileItems(kv, true, "sound"); - kv.GoBack(); - } - else soundPack.viewSounds = CreateArray(); - - if (kv.JumpToKey("Select")) - { - soundPack.selectSounds = ParseFileItems(kv, true, "sound"); - kv.GoBack(); - } - else soundPack.selectSounds = CreateArray(); - - if (kv.JumpToKey("Jump")) - { - soundPack.jumpSounds = ParseFileItems(kv, true, "sound"); - kv.GoBack(); - } - else soundPack.jumpSounds = CreateArray(); + soundMap.AddSoundPack(soundPack, ParseSoundPack(kv)); + } + } + while (kv.GotoNextKey()); + kv.GoBack(); + } +} - String_ToUpper(name, name, sizeof(name)); - soundMap.SetArray(name, soundPack, sizeof(SoundPack)); +SoundPack ParseSoundPack(KeyValues kv) +{ + SoundPack soundPack = new SoundPack(); + if (kv.GotoFirstSubKey()) + { + char soundType[MODELCHOOSER_MAX_NAME]; + do + { + if (kv.GetSectionName(soundType, sizeof(soundType))) + { + soundPack.AddSoundList(soundType, view_as(ParseFileItems(kv, true, "sound"))); } - } while (kv.GotoNextKey()); + } + while (kv.GotoNextKey()); kv.GoBack(); } + return soundPack; } ArrayList ParseFileItems(KeyValues kv, bool precache, const char[] folderType = "") @@ -305,6 +297,25 @@ ArrayList ParseDelimitedIntList(KeyValues kv, const char[] key) return list; } +void ParseCustomProperties(KeyValues kv, StringMap map) +{ + if (kv.GotoFirstSubKey(false)) + { + char key[256], value[4096]; + do + { + if (kv.GetSectionName(key, sizeof(key))) + { + String_ToUpper(key, key, sizeof(key)); + kv.GetString(NULL_STRING, value, sizeof(value)); + map.SetString(key, value); + } + } + while (kv.GotoNextKey(false)); + kv.GoBack(); + } +} + void ParseInterval(KeyValues kv, Interval interval, const char[] key, float defualtMin = 0.0, float defaultMax = 0.0) { char val[32]; diff --git a/scripting/include/model_chooser/globals.inc b/scripting/include/modelchooser/globals.inc similarity index 98% rename from scripting/include/model_chooser/globals.inc rename to scripting/include/modelchooser/globals.inc index 59d2fd2..6951eb1 100644 --- a/scripting/include/model_chooser/globals.inc +++ b/scripting/include/modelchooser/globals.inc @@ -58,6 +58,7 @@ Handle callUpdateAcknowledgedFramecount; PersistentPreferences persistentPreferences[MAX_TEAMS]; // Forwards +GlobalForward fwdOnConfigLoaded; GlobalForward fwdOnModelChanged; // Cvars diff --git a/scripting/include/model_chooser/menu.inc b/scripting/include/modelchooser/menu.inc similarity index 82% rename from scripting/include/model_chooser/menu.inc rename to scripting/include/modelchooser/menu.inc index 7186c61..69c1e3d 100644 --- a/scripting/include/model_chooser/menu.inc +++ b/scripting/include/modelchooser/menu.inc @@ -6,7 +6,7 @@ bool IsInMenu(int client) return (menuSelection[client].index != -1); } -bool PreEnterCheck(int client) +bool PreEnterCheck(int client, bool printError = true) { if (!client) { @@ -14,22 +14,22 @@ bool PreEnterCheck(int client) } if (selectableModels[client] == null || !selectableModels[client].Length) { - PrintToChat(client, "[ModelChooser] No models are available."); + if (printError) PrintToChat(client, "[ModelChooser] No models are available."); return false; } if (!IsPlayerAlive(client)) { - PrintToChat(client, "[ModelChooser] You need to be alive to use models."); + if (printError) PrintToChat(client, "[ModelChooser] You need to be alive to use models."); return false; } if (IsInMenu(client)) { - PrintToChat(client, "[ModelChooser] You are already changing models, dummy :]"); + if (printError) PrintToChat(client, "[ModelChooser] You are already changing models, dummy :]"); return false; } if (GetEntityFlags(client) & FL_ATCONTROLS || Client_IsInThirdPersonMode(client)) { - PrintToChat(client, "[ModelChooser] You cannot change models currently."); + if (printError) PrintToChat(client, "[ModelChooser] You cannot change models currently."); return false; } return true; @@ -103,9 +103,7 @@ void ExitModelChooser(int client, bool silent = false, bool cancel = false) { if (!silent) { - SoundPack soundPack; - GetSoundPack(model, soundPack); - PlayRandomSound(soundPack.selectSounds, client); + PlayRandomSound(GetSoundPack(model).GetSoundList("select"), client); } activeSelection[client] = menuSelection[client]; @@ -144,9 +142,7 @@ void OnMenuModelSelection(int client, bool initial = false, bool scrolling = fal if (!scrolling && !menuSelection[client].locked) { - SoundPack soundPack; - GetSoundPack(model, soundPack); - PlayRandomSound(soundPack.viewSounds, client, _, SNDCHAN_BODY); + PlayRandomSound(GetSoundPack(model).GetSoundList("view"), client, _, SNDCHAN_BODY); } RefreshModel(client); @@ -391,69 +387,9 @@ public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float { ExitModelChooser(client); } - else + else if (tMenuInit[client] == null) { - bool scrolling = delay[client] != MENU_SCROLL_DELAY_MAX; - - if (selectableModels[client].Length > 1) - { - if (buttons & IN_ATTACK && !(lastButtonsAdjusted[client] & IN_ATTACK)) - { - if (--menuSelection[client].index < 0) - { - menuSelection[client].index = selectableModels[client].Length - 1; - } - OnMenuModelSelection(client, false, scrolling); - } - if (buttons & IN_ATTACK2 && !(lastButtonsAdjusted[client] & IN_ATTACK2)) - { - if (++menuSelection[client].index >= selectableModels[client].Length) - { - menuSelection[client].index = 0; - } - OnMenuModelSelection(client, false, scrolling); - } - } - - if (menuSelection[client].skinCount > 1) - { - if (buttons & IN_MOVELEFT && !(lastButtonsAdjusted[client] & IN_MOVELEFT)) - { - if (--menuSelection[client].skin < 0) - { - menuSelection[client].skin = menuSelection[client].skinCount - 1; - } - OnMenuSkinSelection(client); - } - if (buttons & IN_MOVERIGHT && !(lastButtonsAdjusted[client] & IN_MOVERIGHT)) - { - if (++menuSelection[client].skin >= menuSelection[client].skinCount) - { - menuSelection[client].skin = 0; - } - OnMenuSkinSelection(client); - } - } - - if (menuSelection[client].bodyCount > 1) - { - if (buttons & IN_BACK && !(lastButtonsAdjusted[client] & IN_BACK)) - { - if (--menuSelection[client].body < 0) - { - menuSelection[client].body = menuSelection[client].bodyCount - 1; - } - OnMenuBodySelection(client); - } - if (buttons & IN_FORWARD && !(lastButtonsAdjusted[client] & IN_FORWARD)) - { - if (++menuSelection[client].body >= menuSelection[client].bodyCount) - { - menuSelection[client].body = 0; - } - OnMenuBodySelection(client); - } - } + MenuSelectionThink(client, buttons, lastButtonsAdjusted[client], delay[client] != MENU_SCROLL_DELAY_MAX); } float time = GetGameTime(); @@ -474,4 +410,67 @@ public void OnPlayerRunCmdPost(int client, int buttons, int impulse, const float lastButtonsAdjusted[client] = lastButtons[client]; } } +} + +void MenuSelectionThink(int client, int buttons, int oldButtons, bool scrolling) +{ + if (selectableModels[client].Length > 1) + { + if (buttons & IN_ATTACK && !(oldButtons & IN_ATTACK)) + { + if (--menuSelection[client].index < 0) + { + menuSelection[client].index = selectableModels[client].Length - 1; + } + OnMenuModelSelection(client, false, scrolling); + } + if (buttons & IN_ATTACK2 && !(oldButtons & IN_ATTACK2)) + { + if (++menuSelection[client].index >= selectableModels[client].Length) + { + menuSelection[client].index = 0; + } + OnMenuModelSelection(client, false, scrolling); + } + } + + if (menuSelection[client].skinCount > 1) + { + if (buttons & IN_MOVELEFT && !(oldButtons & IN_MOVELEFT)) + { + if (--menuSelection[client].skin < 0) + { + menuSelection[client].skin = menuSelection[client].skinCount - 1; + } + OnMenuSkinSelection(client); + } + if (buttons & IN_MOVERIGHT && !(oldButtons & IN_MOVERIGHT)) + { + if (++menuSelection[client].skin >= menuSelection[client].skinCount) + { + menuSelection[client].skin = 0; + } + OnMenuSkinSelection(client); + } + } + + if (menuSelection[client].bodyCount > 1) + { + if (buttons & IN_BACK && !(oldButtons & IN_BACK)) + { + if (--menuSelection[client].body < 0) + { + menuSelection[client].body = menuSelection[client].bodyCount - 1; + } + OnMenuBodySelection(client); + } + if (buttons & IN_FORWARD && !(oldButtons & IN_FORWARD)) + { + if (++menuSelection[client].body >= menuSelection[client].bodyCount) + { + menuSelection[client].body = 0; + } + OnMenuBodySelection(client); + } + } } \ No newline at end of file diff --git a/scripting/include/modelchooser/natives.inc b/scripting/include/modelchooser/natives.inc new file mode 100644 index 0000000..fb2d1d1 --- /dev/null +++ b/scripting/include/modelchooser/natives.inc @@ -0,0 +1,186 @@ +#pragma semicolon 1 +#pragma newdecls required + +public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) +{ + CreateNative("ModelChooser_GetCurrentModelName", Native_GetCurrentModelName); + CreateNative("ModelChooser_GetCurrentModelPath", Native_GetCurrentModelPath); + CreateNative("ModelChooser_GetCurrentModelProperty", Native_GetCurrentModelProperty); + CreateNative("ModelChooser_UnlockModel", Native_UnlockModel); + CreateNative("ModelChooser_LockModel", Native_LockModel); + CreateNative("ModelChooser_SelectModel", Native_SelectModel); + CreateNative("ModelChooser_IsClientChoosing", Native_IsClientChoosing); + CreateNative("ModelChooser_OpenChooser", Native_OpenChooser); + CreateNative("ModelChooser_PlayRandomSound", Native_PlayRandomSound); + CreateNative("ModelChooser_GetProperty", Native_GetProperty); + CreateNative("ModelChooser_GetModelList", Native_GetModelList); + CreateNative("ModelChooser_GetSoundMap", Native_GetSoundMap); + RegPluginLibrary("ModelChooser"); + return APLRes_Success; +} + +public any Native_GetCurrentModelName(Handle plugin, int numParams) +{ + PlayerModel model; + + if (GetSelectedModel(GetNativeCell(1), model)) + { + SetNativeString(2, model.name, GetNativeCell(3)); + return true; + } + return false; +} + +public any Native_GetCurrentModelPath(Handle plugin, int numParams) +{ + PlayerModel model; + + if (GetSelectedModel(GetNativeCell(1), model)) + { + SetNativeString(2, model.path, GetNativeCell(3)); + return true; + } + return false; +} + +public any Native_GetCurrentModelProperty(Handle plugin, int numParams) +{ + PlayerModel model; + + if (GetSelectedModel(GetNativeCell(1), model)) + { + int keySize; GetNativeStringLength(2, keySize); keySize++; + int valSize = GetNativeCell(4); + char[] key = new char[keySize]; + char[] val = new char[valSize]; + GetNativeString(2, key, keySize); + String_ToUpper(key, key, keySize); + if (model.customProperties.GetString(key, val, valSize)) + { + SetNativeString(3, val, valSize); + return true; + } + } + return false; +} + +public any Native_UnlockModel(Handle plugin, int numParams) +{ + char modelName[MODELCHOOSER_MAX_NAME]; + GetNativeString(2, modelName, sizeof(modelName)); + String_ToUpper(modelName, modelName, sizeof(modelName)); + int client = GetNativeCell(1); + + UnlockModel(client, modelName); + if (GetNativeCell(3)) + { + return SelectModelByName(client, modelName); + } + return true; +} + +public any Native_LockModel(Handle plugin, int numParams) +{ + char modelName[MODELCHOOSER_MAX_NAME]; + GetNativeString(2, modelName, sizeof(modelName)); + String_ToUpper(modelName, modelName, sizeof(modelName)); + + LockModel(GetNativeCell(1), modelName); + return true; +} + +public any Native_SelectModel(Handle plugin, int numParams) +{ + char modelName[MODELCHOOSER_MAX_NAME]; + GetNativeString(2, modelName, sizeof(modelName)); + String_ToUpper(modelName, modelName, sizeof(modelName)); + + return SelectModelByName(GetNativeCell(1), modelName); +} + +public any Native_IsClientChoosing(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + return IsInMenu(client); +} + +public any Native_OpenChooser(Handle plugin, int numParams) +{ + int client = GetNativeCell(1); + bool printError = GetNativeCell(2); + if (PreEnterCheck(client, printError)) + { + EnterModelChooser(client); + return true; + } + return false; +} + +public any Native_PlayRandomSound(Handle plugin, int numParams) +{ + PlayerModel model; + int client = GetNativeCell(1); + + if (GetSelectedModel(client, model)) + { + char soundType[MODELCHOOSER_MAX_NAME]; + GetNativeString(2, soundType, sizeof(soundType)); + + SoundPack soundPack = GetSoundPack(model, false); + if (!soundPack) + return false; + + bool toAll = GetNativeCell(3); + bool stopLast = GetNativeCell(4); + int pitch = GetNativeCell(5); + float volume = GetNativeCell(6); + + if (stopLast) + { + StopSound(client, SNDCHAN_BODY, lastPlayedSound[client]); + StopSound(client, SNDCHAN_STATIC, lastPlayedSound[client]); + } + + return PlayRandomSound(soundPack.GetSoundList(soundType), client, + .channel = SNDCHAN_STATIC, .toAll = toAll, .pitchMin = pitch, .pitchMax = pitch, .volume = volume + ); + } + return false; +} + +public any Native_GetProperty(Handle plugin, int numParams) +{ + char modelName[MODELCHOOSER_MAX_NAME]; + GetNativeString(1, modelName, sizeof(modelName)); + if (modelList) + { + int i = modelList.FindByName(modelName); + if (i != -1) + { + StringMap customProperties = modelList.Get(i, PlayerModel::customProperties); + + int keySize; GetNativeStringLength(2, keySize); keySize++; + int valSize = GetNativeCell(4); + char[] key = new char[keySize]; + char[] val = new char[valSize]; + GetNativeString(2, key, keySize); + String_ToUpper(key, key, keySize); + if (customProperties.GetString(key, val, valSize)) + { + SetNativeString(3, val, valSize); + return true; + } + } + } + return false; +} + +public any Native_GetModelList(Handle plugin, int numParams) +{ + return modelList; +} + +public any Native_GetSoundMap(Handle plugin, int numParams) +{ + return soundMap; +} diff --git a/scripting/include/model_chooser/sounds.inc b/scripting/include/modelchooser/sounds.inc similarity index 86% rename from scripting/include/model_chooser/sounds.inc rename to scripting/include/modelchooser/sounds.inc index 4269af6..ec09e6d 100644 --- a/scripting/include/model_chooser/sounds.inc +++ b/scripting/include/modelchooser/sounds.inc @@ -13,9 +13,7 @@ public void Sounds_EventPlayerHurt(Event event, int client) { if (0 < health <= model.hurtSndHP.Rand()) { - SoundPack soundPack; - GetSoundPack(model, soundPack); - PlayRandomSound(soundPack.hurtSounds, client, client, SNDCHAN_STATIC, true, HURT_PITCH_MIN, HURT_PITCH_MAX); + PlayRandomSound(GetSoundPack(model).GetSoundList("hurt"), client, client, SNDCHAN_STATIC, true, HURT_PITCH_MIN, HURT_PITCH_MAX); playedHurtSoundAt[client] = health; } } @@ -58,8 +56,6 @@ public MRESReturn Hook_DeathSound(int client, DHookParam hParams) { StopSound(client, SNDCHAN_BODY, lastPlayedSound[client]); StopSound(client, SNDCHAN_STATIC, lastPlayedSound[client]); - SoundPack soundPack; - GetSoundPack(model, soundPack); int target = client; int m_hRagdoll = GetEntPropEnt(client, Prop_Send, "m_hRagdoll"); @@ -68,7 +64,7 @@ public MRESReturn Hook_DeathSound(int client, DHookParam hParams) target = m_hRagdoll; } - if (PlayRandomSound(soundPack.deathSounds, client, target, SNDCHAN_STATIC, true, HURT_PITCH_MIN, HURT_PITCH_MAX)) + if (PlayRandomSound(GetSoundPack(model).GetSoundList("death"), client, target, SNDCHAN_STATIC, true, HURT_PITCH_MIN, HURT_PITCH_MAX)) return MRES_Supercede; } return MRES_Ignored; diff --git a/scripting/include/model_chooser/structs.inc b/scripting/include/modelchooser/structs.inc similarity index 59% rename from scripting/include/model_chooser/structs.inc rename to scripting/include/modelchooser/structs.inc index d0c0791..5b6d089 100644 --- a/scripting/include/model_chooser/structs.inc +++ b/scripting/include/modelchooser/structs.inc @@ -1,6 +1,16 @@ #pragma semicolon 1 #pragma newdecls required +#define MODELCHOOSER_MAX_NAME 64 + +#if !defined MAX_TEAMS +#define MAX_TEAMS 32 // Max number of teams in a game +#endif + +#if !defined MAX_TEAM_NAME_LENGTH +#define MAX_TEAM_NAME_LENGTH 32 // Max length of a team's name +#endif + enum struct Interval { float min; @@ -17,30 +27,96 @@ enum struct SoundParams Interval cooldown; } -enum struct SoundPack +methodmap SoundList < ArrayList { - ArrayList hurtSounds; - ArrayList deathSounds; - ArrayList viewSounds; - ArrayList selectSounds; - ArrayList jumpSounds; + public SoundList() + { + return view_as(new ArrayList()); + } + + public void Precache() + { + int len = this.Length; + char path[PLATFORM_MAX_PATH]; + + for (int i = 0; i < len; i++) + { + this.GetString(i, path, sizeof(path)); + PrecacheSound(path, true); + } + } +} + +methodmap SoundPack < StringMap +{ + public SoundPack() + { + return view_as(new StringMap()); + } - void Close() + public void AddSoundList(const char[] soundType, SoundList soundList) { - delete this.hurtSounds; - delete this.deathSounds; - delete this.viewSounds; - delete this.selectSounds; - delete this.jumpSounds; + char soundTypeNorm[MODELCHOOSER_MAX_NAME]; + _modelchooser_normalize_name(soundType, soundTypeNorm); + + if (!this.SetValue(soundTypeNorm, soundList, false)) + { + ThrowError("Sound type \"%s\" already added", soundTypeNorm); + } } - void Precache() + public SoundList GetSoundList(const char[] soundType) + { + if (!this.Size) + return null; + + char soundTypeNorm[MODELCHOOSER_MAX_NAME]; + _modelchooser_normalize_name(soundType, soundTypeNorm); + + SoundList soundList; + this.GetValue(soundTypeNorm, soundList); + return soundList; + } + + public void Clear() + { + StringMapSnapshot snapshot = this.Snapshot(); + SoundList soundList; + for (int i = 0; i < snapshot.Length; i++) + { + int keySize = snapshot.KeyBufferSize(i); + char[] key = new char[keySize]; + snapshot.GetKey(i, key, keySize); + if (this.GetValue(key, soundList)) + { + soundList.Close(); + } + } + snapshot.Close(); + view_as(this).Clear(); + } + + public void Close() + { + this.Clear(); + view_as(this).Close(); + } + + public void Precache() { - PrecacheSoundsInList(this.hurtSounds); - PrecacheSoundsInList(this.deathSounds); - PrecacheSoundsInList(this.viewSounds); - PrecacheSoundsInList(this.selectSounds); - PrecacheSoundsInList(this.jumpSounds); + StringMapSnapshot snapshot = this.Snapshot(); + SoundList soundList; + for (int i = 0; i < snapshot.Length; i++) + { + int keySize = snapshot.KeyBufferSize(i); + char[] key = new char[keySize]; + snapshot.GetKey(i, key, keySize); + if (this.GetValue(key, soundList)) + { + soundList.Precache(); + } + } + snapshot.Close(); } } @@ -51,6 +127,30 @@ methodmap SoundMap < StringMap return view_as(new StringMap()); } + public void AddSoundPack(const char[] name, SoundPack pack) + { + char nameNorm[MODELCHOOSER_MAX_NAME]; + _modelchooser_normalize_name(name, nameNorm); + + if (!this.SetValue(nameNorm, pack, false)) + { + ThrowError("Sound pack \"%s\" already added", nameNorm); + } + } + + public SoundPack GetSoundPack(const char[] name) + { + if (!this.Size) + return null; + + char nameNorm[MODELCHOOSER_MAX_NAME]; + _modelchooser_normalize_name(name, nameNorm); + + SoundPack soundPack; + this.GetValue(nameNorm, soundPack); + return soundPack; + } + public void Clear() { StringMapSnapshot snapshot = this.Snapshot(); @@ -60,13 +160,21 @@ methodmap SoundMap < StringMap int keySize = snapshot.KeyBufferSize(i); char[] key = new char[keySize]; snapshot.GetKey(i, key, keySize); - this.GetArray(key, soundPack, sizeof(SoundPack)); - soundPack.Close(); + if (this.GetValue(key, soundPack)) + { + soundPack.Close(); + } } snapshot.Close(); view_as(this).Clear(); } + public void Close() + { + this.Clear(); + view_as(this).Close(); + } + public void Precache() { StringMapSnapshot snapshot = this.Snapshot(); @@ -76,8 +184,10 @@ methodmap SoundMap < StringMap int keySize = snapshot.KeyBufferSize(i); char[] key = new char[keySize]; snapshot.GetKey(i, key, keySize); - this.GetArray(key, soundPack, sizeof(SoundPack)); - soundPack.Precache(); + if (this.GetValue(key, soundPack)) + { + soundPack.Precache(); + } } snapshot.Close(); } @@ -144,10 +254,10 @@ enum struct PlayerAnimation enum struct PlayerModel { - char name[MAX_MODELNAME]; + char name[MODELCHOOSER_MAX_NAME]; char path[PLATFORM_MAX_PATH]; char team[MAX_TEAM_NAME_LENGTH]; - char sounds[MAX_SOUNDSNAME]; + char sounds[MODELCHOOSER_MAX_NAME]; char vmBodyGroups[256]; SoundParams jumpSndParams; Interval hurtSndHP; @@ -158,6 +268,7 @@ enum struct PlayerModel ArrayList skins; ArrayList bodyGroups; + StringMap customProperties; PlayerAnimation anim_idle; PlayerAnimation anim_walk; @@ -171,6 +282,7 @@ enum struct PlayerModel { this.skins.Close(); this.bodyGroups.Close(); + this.customProperties.Close(); this.anim_idle.Close(); this.anim_walk.Close(); this.anim_run.Close(); @@ -187,7 +299,11 @@ enum struct PlayerModel int GetTeamNum() { - int team = String_IsNumeric(this.team) ? StringToInt(this.team) : FindTeamByName(this.team); + int team; + if (!StringToIntEx(this.team, team)) + { + team = FindTeamByName(this.team); + } if (team < 0 || team >= MAX_TEAMS) { if (GetTeamCount() > 2) @@ -241,16 +357,12 @@ methodmap ModelList < ArrayList view_as(this).Clear(); } - public int FindByName(const char[] modelName, PlayerModel model) + public int FindByName(const char[] modelName) { - int len = this.Length; - for (int i = 0; i < len; i++) - { - this.GetArray(i, model); - if (StrEqual(model.name, modelName, false)) - return i; - } - return -1; + char nameNorm[MODELCHOOSER_MAX_NAME]; + _modelchooser_normalize_name(modelName, nameNorm); + + return this.FindString(nameNorm, PlayerModel::name); } public void Precache() @@ -310,7 +422,7 @@ enum struct PersistentPreferences char name[32]; char suffix[4]; - if (team > TEAM_SPECTATOR) + if (team > 1) { FormatEx(suffix, sizeof(suffix), "#%d", team); } @@ -325,3 +437,12 @@ enum struct PersistentPreferences this.body = new Cookie(name, "Stores player model body type preference", CookieAccess_Protected); } } + +void _modelchooser_normalize_name(const char[] name, char nameNorm[MODELCHOOSER_MAX_NAME]) +{ + strcopy(nameNorm, sizeof(nameNorm), name); + for (int i = 0; nameNorm[i] != '\0'; i++) + { + nameNorm[i] = CharToUpper(nameNorm[i]); + } +} \ No newline at end of file diff --git a/scripting/include/model_chooser/utils.inc b/scripting/include/modelchooser/utils.inc similarity index 94% rename from scripting/include/model_chooser/utils.inc rename to scripting/include/modelchooser/utils.inc index c075893..acdeeae 100644 --- a/scripting/include/model_chooser/utils.inc +++ b/scripting/include/modelchooser/utils.inc @@ -1,18 +1,6 @@ #pragma semicolon 1 #pragma newdecls required -stock void PrecacheSoundsInList(ArrayList soundList) -{ - int len = soundList.Length; - char path[PLATFORM_MAX_PATH]; - - for (int i = 0; i < len; i++) - { - soundList.GetString(i, path, sizeof(path)); - PrecacheSound(path, true); - } -} - Action Hook_BlockDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype) { return Plugin_Handled; diff --git a/scripting/include/model_chooser/viewmodels.inc b/scripting/include/modelchooser/viewmodels.inc similarity index 100% rename from scripting/include/model_chooser/viewmodels.inc rename to scripting/include/modelchooser/viewmodels.inc diff --git a/scripting/modelchooser_api_example.sp b/scripting/modelchooser_api_example.sp new file mode 100644 index 0000000..43abc81 --- /dev/null +++ b/scripting/modelchooser_api_example.sp @@ -0,0 +1,53 @@ +#include + +#define MODELCHOOSER_RAWDOG_API /* Enable deep access? */ +#undef REQUIRE_PLUGIN /* Is ModelChooser plugin dependency required or optional? */ +#include + +public void OnLibraryAdded(const char[] name) +{ + if (StrEqual(name, MODELCHOOSER_LIBRARY)) + { + PrintToServer("ModelChooser plugin is running"); + // useModelChooser = true; + } +} + +public void OnLibraryRemoved(const char[] name) +{ + if (StrEqual(name, MODELCHOOSER_LIBRARY)) + { + PrintToServer("ModelChooser plugin has unloaded"); + // useModelChooser = false; + } +} + +public void ModelChooser_OnModelChanged(int client, const char[] modelName) +{ + PrintToServer("%N changed model to %s", client, modelName); + + char value[512]; + if (ModelChooser_GetCurrentModelProperty(client, "test", value, sizeof(value))) + { + PrintToServer("Value of test property: %s", value); + } + else + { + PrintToServer("There is no custom 'test' property on this model"); + } +} + +public void ModelChooser_OnConfigLoaded() +{ + PrintToServer("Model config loaded"); + + ModelList modelList = ModelChooser_GetModelList(); + PlayerModel model; + + int size = modelList.Length; + for (int i = 0; i < size; i++) + { + modelList.GetArray(i, model); + PrintToServer("Model at index %d is named %s", i, model.name); + } +} \ No newline at end of file diff --git a/scripting/ultimate_modelchooser.sp b/scripting/ultimate_modelchooser.sp index 9e981a8..af36705 100644 --- a/scripting/ultimate_modelchooser.sp +++ b/scripting/ultimate_modelchooser.sp @@ -22,8 +22,6 @@ public Plugin myinfo = url = "https://github.com/Alienmario/ModelChooser" }; -#define MAX_MODELNAME 64 -#define MAX_SOUNDSNAME 64 #define MAX_ANIM_NAME 128 #define HURT_SOUND_HP 45.0 @@ -39,16 +37,16 @@ public Plugin myinfo = #define FALLBACK_MODEL "models/error.mdl" int DEFAULT_HUD_COLOR[] = {150, 150, 150, 150}; -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include public void OnPluginStart() { @@ -59,6 +57,7 @@ public void OnPluginStart() downloads = new SmartDM_FileSet(); persistentPreferences[TEAM_UNASSIGNED].Init(TEAM_UNASSIGNED); + fwdOnConfigLoaded = new GlobalForward("ModelChooser_OnConfigLoaded", ET_Ignore, Param_Cell, Param_String); fwdOnModelChanged = new GlobalForward("ModelChooser_OnModelChanged", ET_Ignore, Param_Cell, Param_String); RegConsoleCmd("sm_models", Command_Model); @@ -205,7 +204,7 @@ public void OnClientConnected(int client) { ResetClientModels(client); ResetUnlockedModels(client); - delete tMenuInit[client]; + tMenuInit[client] = null; clientInitChecks[client] = 3; currentTeam[client] = 0; } @@ -397,7 +396,7 @@ void InitClientModels(int client) PersistentPreferences prefs; prefs = GetPreferences(client); - char modelName[MAX_MODELNAME]; + char modelName[MODELCHOOSER_MAX_NAME]; prefs.model.Get(client, modelName, sizeof(modelName)); // first select by team based preferences @@ -448,10 +447,11 @@ ArrayList BuildSelectableModels(int client) bool SelectModelByName(int client, const char[] modelName, int skin = 0, int body = 0) { - PlayerModel model; - int index = modelList.FindByName(modelName, model); + int index = modelList.FindByName(modelName); if (index != -1) { + PlayerModel model; + modelList.GetArray(index, model); int clIndex = selectableModels[client].FindValue(index); if (clIndex != -1 && !IsModelLocked(model, client)) { @@ -525,12 +525,12 @@ bool IsModelLocked(const PlayerModel model, int client) return (model.locked && !unlockedModels[client].GetValue(model.name, client)); } -void UnlockModel(int client, char modelName[MAX_MODELNAME]) +void UnlockModel(int client, char modelName[MODELCHOOSER_MAX_NAME]) { unlockedModels[client].SetValue(modelName, true); } -void LockModel(int client, char modelName[MAX_MODELNAME]) +void LockModel(int client, char modelName[MODELCHOOSER_MAX_NAME]) { unlockedModels[client].Remove(modelName); } @@ -559,9 +559,19 @@ PersistentPreferences GetPreferencesByTeam(int team) return prefs; } -void GetSoundPack(const PlayerModel model, SoundPack soundPack) +SoundPack GetSoundPack(const PlayerModel model, bool emptyDefault = true) { - soundMap.GetArray(model.sounds, soundPack, sizeof(SoundPack)); + SoundPack soundPack = soundMap.GetSoundPack(model.sounds); + if (soundPack || !emptyDefault) + { + return soundPack; + } + static SoundPack emptySoundPack; + if (!emptySoundPack) + { + emptySoundPack = new SoundPack(); + } + return emptySoundPack; } void ForceFullUpdate(int client)