Yet another reboot of one of my older plugins. This plugin will allow you to modify just about any scenes, to prevent or cause certain scenes to be played out.
This plugin does nothing of its own other than provide forwards and natives for other plugins to use. See the include file for functions.
Description:
Provides forwards and natives for manipulation of scenes.
Commands:
No commands are implemented in this plugin.
Cvars:
Code:
// (L4D2 only) Whether the vocalize command will function once again as it did in L4D1, allowing players to type vocalize commands into their console. 0 = Disallow, 1 = Allow
sceneprocessor_jailbreak_vocalize 1
Change log:
Code:
Version 1.0.1 2014-06-26
Added 'OnVocalizeCommand' forward.
Fixed calling 'CancelScene' in SceneStage_SpawnedPost resulting in a server crash.
Version 1.0.0 2014-06-06
Initial release
#define MAX_SCENEFILE_LENGTH PLATFORM_MAX_PATH // Maximum length of scene files #define MAX_VOCALIZE_LENGTH 128 // Maximum length of vocalize strings
#define SCENE_INITIATOR_WORLD 0 /* All scenes created by the map (such as landmarks, "Down this way", "Through here") * and by the game (such as team mate actions, "Let me heal you up", "Help I'm falling") * are marked as started by the world, or entity index 0. */ #define SCENE_INITIATOR_PLUGIN -1 /* All scenes started by the Scene Processor are by default marked as started by plugin or * entity index -1. This can be changed in the PerformScene function call. */
#define DEFAULT_SCENE_PREDELAY 0.0 /* By default all scenes occur the instant they are created */ #define DEFAULT_SCENE_PITCH 1.0 /* By default all scenes have a pitch of 1.0, or as they are normally heard. 2.0 will make * it sounds twice as fast but in a high pitched voice. 0.5 will be half the normal speed * and with low pitched voice. */
enum SceneStages { SceneStage_Created = 1, /* When scene is created. Contains no information about the scene that is about to * be played out. Guaranteed to occur. */
SceneStage_Spawned, /* When scene is spawned. Everything is filled in but the pre-delay! Change scene * pitch and pre-delay here. */
SceneStage_SpawnedPost, /* Post scene spawned frame. Pre-delay is now filled out. Pre-delay can be changed * here if the scene originally has a pre-delay greater than 0.0. */
SceneStage_Started, /* When scene is started. Time stamp is set. */
SceneStage_Cancelled, /* When scene is cancelled. Happens if the Survivor was interrupted by another scene * or the scene was cancelled post starting. */
SceneStage_Completion, /* When scene is completed. Happens if the Survivor gets to fully perform the scene * with no interruptions or cancellation. Entity index is no longer valid but * information is retained. */
SceneStage_Killed /* When scene is killed. Entity index is no longer valid but information is retained * until end of forward. Guaranteed to occur */ }
/** * Called on scene stage changed. * * @param scene Scene entity index. * @param stage SceneStages value. * @noreturn */ forward OnSceneStageChanged(scene, SceneStages:stage);
/** * Called on vocalize command send. * * @remarks See SCENE_INITIATOR_WORLD and SCENE_INITIATOR_PLUGIN for * non-client initiated vocalizes. * * @param client Index of player sending vocalize command. * @param vocalize String of what player wish to vocalize, as typed. * @param initiator Index of entity initiated vocalize command. * @return Plugin_Stop to stop vocalize command. * Plugin_Continue to allow vocalize command. */ forward Action:OnVocalizeCommand(client, const String:vocalize[], initiator);
/** * Returns scene stage. * * @param scene Scene entity index. * @return SceneStages value or 0 if scene is not valid. */ native SceneStages:GetSceneStage(scene);
/** * Returns whether scene is valid as a scene. * * @remarks The scene entity index may still be invalid. * * @param scene Scene entity index. * @return True if a valid scene, false otherwise. */ stock bool:IsValidScene(scene) { return GetSceneStage(scene) != SceneStages:0 }
/** * Returns scene start time stamp, in engine time. * * @param scene Scene entity index. * @return Scene start engine time stamp. 0.0 if the scene has not started yet. */ native Float:GetSceneStartTimeStamp(scene);
/** * Returns scene actor. * * @param scene Scene entity index. * @return Actor of the scene. 0 if no one is playing it. */ native GetActorFromScene(scene);
/** * Returns actor's scene. * * @remarks Only returns scene if it is currently being played out. * * @param actor Client index of actor. * @return Scene entity index. INVALID_ENT_REFERENCE if actor currently aren't playing. */ native GetSceneFromActor(actor);
/** * Returns whether actor is already playing a scene. * * @param actor Client index of actor. * @return True if actor is busy, false otherwise. */ stock bool:IsActorBusy(actor) { return GetSceneFromActor(actor) != INVALID_ENT_REFERENCE }
/** * Returns scene initiator. * * @remarks See SCENE_INITIATOR_WORLD and SCENE_INITIATOR_PLUGIN for automated * scenes. * * @param scene Scene entity index. * @return Initiator of the scene. */ native GetSceneInitiator(scene);
/** * Returns scene file. * * @param scene Scene entity index. * @param dest Destination string buffer to copy to. * @param len Destination buffer length (includes null terminator). * @return Number of bytes written */ native GetSceneFile(scene, String:dest[], len);
/** * Returns scene vocalize. * * @remarks Will only contain the vocalize string if the scene was started by * the client them self. Otherwise empty. * * @param scene Scene entity index. * @param dest Destination string buffer to copy to. * @param len Destination buffer length (includes null terminator). * @return Number of bytes written */ native GetSceneVocalize(scene, String:dest[], len);
/** * Returns scene pre-delay. * * @remarks Sadly pre-delay is first fetched on SceneStage_SpawnedPost where * altering the pre-delay with SetScenePreDelay might already be too late. * * @param scene Scene entity index. * @return Scene pre-delay in seconds. */ native Float:GetScenePreDelay(scene);
/** * Sets scene pre-delay. * * @remarks Pre-delay can be set on SceneStage_Spawned and sometimes (!) on * SceneStage_SpawnedPost depending on whether the scene originally has a * pre-delay. Once SceneStage_Started fires pre-delay is no longer obeyed. * * @param scene Scene entity index. * @param preDelay Scene pre-delay in seconds. * @noreturn */ native SetScenePreDelay(scene, Float:preDelay);
/** * Returns scene pitch. * * @param scene Scene entity index. * @return Scene pitch. */ native Float:GetScenePitch(scene);
/** * Sets scene pitch * * @remarks Scene pitch can be set on SceneStage_Spawned and * SceneStage_SpawnedPost. Setting pitch post spawning results in weird * settings such as some clients hearing 1.13 as double as fast instead of 2.0. * * @param scene Scene entity index. * @param pitch Scene pitch. * @noreturn */ native SetScenePitch(scene, Float:pitch);
/** * Cancels scene. * * @remarks Can be used at all scene stages. Scene is validated before sending * the cancel input. * * @param scene Scene entity index. * @noreturn */ native CancelScene(scene);
/** * Performs a scene. * * @remarks Scene file is used before vocalize string however vocalize string * is still saved to scene entity index and can be retrieved for later usage. * If vocalize string is used, it is delayed for a short while in order to * work if used inside of OnSceneStageChanged. See PerformSceneEx to avoid * this delay. * * @param client Client index. * @param vocalize Vocalize string. * @param file Scene file. * @param preDelay Scene pre-delay. * @param pitch Scene pitch. * @param initiator Initiator of the scene. * @noreturn */ native PerformScene(client, const String:vocalize[], const String:file[] = "", Float:preDelay = DEFAULT_SCENE_PREDELAY, Float:pitch = DEFAULT_SCENE_PITCH, initiator = SCENE_INITIATOR_PLUGIN);
/** * Performs a scene. * * @remarks Works the same way as PerformScene however there is no delay when * using vocalize string. Not recommend to use inside OnSceneStageChanged if * vocalizes needs to get through. * * @param client Client index. * @param vocalize Vocalize string. * @param file Scene file. * @param preDelay Scene pre-delay. * @param pitch Scene pitch. * @param initiator Initiator of the scene. * @noreturn */ native PerformSceneEx(client, const String:vocalize[], const String:file[] = "", Float:preDelay = DEFAULT_SCENE_PREDELAY, Float:pitch = DEFAULT_SCENE_PITCH, initiator = SCENE_INITIATOR_PLUGIN);
A couple of example plugins on how to capture and use scenes.
Funny Pills
For some reason you can't stop laughing after eating your pills.
PHP Code:
/* Includes */
#include <sourcemod>
#include <sceneprocessor>
/* Plugin Information */
public Plugin:myinfo =
{
name = "Vocalize Funny Pills",
author = "Buster \"Mr. Zero\" Nielsen",
description = "I feel funny... Ha ha funny.",
version = "1.0.0",
url = "[email protected]"
}
/* Plugin Functions */
public OnPluginStart()
{
HookEvent("pills_used", PillsUsed_Event)
}
public PillsUsed_Event(Handle:event, const String:name[], bool:dontBroadcast)
{
new userid = GetEventInt(event, "userid")
new client = GetClientOfUserId(userid)
DEATH to death screamers!
Lets provide them with that death they scream so much about.
PHP Code:
/* Includes */
#include <sourcemod>
#include <sdktools>
#include <sceneprocessor>
/* Plugin Information */
public Plugin:myinfo =
{
name = "Vocalize Death Scream",
author = "Buster \"Mr. Zero\" Nielsen",
description = "DEATH to death screamers!",
version = "1.0.0",
url = "[email protected]"
}
/* Globals */
/* Plugin Functions */
public OnSceneStageChanged(scene, SceneStages:stage)
{
switch (stage)
{
case SceneStage_Started:
{
new client = GetActorFromScene(scene)
Hey Zero As mentioned in the Anti-Vocalize-Thread, Sceneprofessor is producing errors on my server. Heres the error.log. Please let me know if/how i can provide more information for you to debug this. I'd really enjoy to use your anti-vocalize-spam plugins.
Code:
L 08/09/2014 - 11:13:13: [SM] Plugin encountered error 21: Native is not bound
L 08/09/2014 - 11:13:13: [SM] Native "GetEngineVersion" reported:
L 08/09/2014 - 11:13:13: [SM] Displaying call stack trace for plugin "sceneprocessor.smx":
L 08/09/2014 - 11:13:13: [SM] [0] Line 140, sceneprocessor.sp::AskPluginLoad2()
L 08/09/2014 - 11:13:13: [SM] Unable to load plugin "sceneprocessor.smx": Native "GetSceneStage" was not found
At least update to the stable version of 1.5, if you insist running 1.5. Otherwise I see no reason why you would not update to 1.6. That should fix your error.
First, I'd like to sincerely thank you for your continued patience to all of my questions, not everybody takes the time to kindly and properly inform a stranger online
With that established...
I took your example code and used it to achieve a certain goal *cough*Fake Zoey*cough*.
However, I can't seem to get it to work as I want it to, here's part of the code...
Spoiler
PHP Code:
public OnSceneStageChanged(scene, SceneStages:stage) { if (stage != SceneStage_Spawned || GetSceneInitiator(scene) == SCENE_INITIATOR_PLUGIN /* Do not capture scenes spawned by the plugin, to prevent a loop */) { return }
decl String:scenefile[MAX_SCENEFILE_LENGTH]; GetSceneFile(scene, scenefile, sizeof(scenefile)); new float:preDelay = GetScenePreDelay(scene); new float:pitch = GetScenePitch(scene); new actor = GetActorFromScene(scene);
if (actor <= 0 || actor > MaxClients || !IsClientInGame(actor)) { return }
new i_RNG, i_Type; decl String:s_Model[64];
// Get survivor model GetEntPropString(actor, Prop_Data, "m_ModelName", s_Model, 64);
// Verifying if the Client is using Zoey's Character Model // If not, plugin won't affect the player if(!StrEqual(s_Model, "models/survivors/survivor_teenangst.mdl")) { return }
// Now we declare all of the scenes that INITIATOR_WORLD creates that we need to replace with Zoey's.
// Because some survivors have problems when clones exist, we use Nick, // as Multiple Nicks all vocalize properly
if(i_Type == 0) { i_RNG = GetRandomInt(1, 4); { switch (i_RNG) { case 1: s_Vocalize = "scenes/TeenGirl/IncapacitatedInitial01.vcd"; case 2: s_Vocalize = "scenes/TeenGirl/IncapacitatedInitial02.vcd"; case 3: s_Vocalize = "scenes/TeenGirl/IncapacitatedInitial03.vcd"; case 4: s_Vocalize = "scenes/TeenGirl/IncapacitatedInitial05.vcd"; } } }
else if(i_Type == 1) { i_RNG = GetRandomInt(1, 4); { switch (i_RNG) { case 1: s_Vocalize = "scenes/TeenGirl/IncapacitatedInjury01.vcd"; case 2: s_Vocalize = "scenes/TeenGirl/IncapacitatedInjury02.vcd"; case 3: s_Vocalize = "scenes/TeenGirl/IncapacitatedInjury03.vcd"; case 4: s_Vocalize = "scenes/TeenGirl/IncapacitatedInjury04.vcd"; } } }
else if(i_Type == 2) { i_RNG = GetRandomInt(1, 27); { switch (i_RNG) { case 1: s_Vocalize = "scenes/TeenGirl/Dying01.vcd"; case 2: s_Vocalize = "scenes/TeenGirl/Dying02.vcd"; case 3: s_Vocalize = "scenes/TeenGirl/Dying03.vcd"; case 4: s_Vocalize = "scenes/TeenGirl/Dying04.vcd"; case 5: s_Vocalize = "scenes/TeenGirl/Dying05.vcd"; case 6: s_Vocalize = "scenes/TeenGirl/Help01.vcd"; case 7: s_Vocalize = "scenes/TeenGirl/Help02.vcd" ; case 8: s_Vocalize = "scenes/TeenGirl/Help03.vcd" ; case 9: s_Vocalize = "scenes/TeenGirl/Help04.vcd" ; case 10: s_Vocalize = "scenes/TeenGirl/Help07.vcd" ; case 11: s_Vocalize = "scenes/TeenGirl/Help08.vcd" ; case 12: s_Vocalize = "scenes/TeenGirl/Help12.vcd"; case 13: s_Vocalize = "scenes/TeenGirl/Help13.vcd"; case 14: s_Vocalize = "scenes/TeenGirl/Help14.vcd"; case 15: s_Vocalize = "scenes/TeenGirl/Help15.vcd"; case 16: s_Vocalize = "scenes/TeenGirl/Help16.vcd"; case 17: s_Vocalize = "scenes/TeenGirl/Help17.vcd"; case 18: s_Vocalize = "scenes/TeenGirl/LedgeHangEnd02.vcd"; case 19: s_Vocalize = "scenes/TeenGirl/LedgeHangEnd06.vcd"; case 20: s_Vocalize = "scenes/TeenGirl/LedgeHangEnd17.vcd"; case 21: s_Vocalize = "scenes/TeenGirl/LedgeHangEnd21.vcd"; case 22: s_Vocalize = "scenes/TeenGirl/LedgeHangMiddle09.vcd"; case 23: s_Vocalize = "scenes/TeenGirl/LedgeHangStart03.vcd"; case 24: s_Vocalize = "scenes/TeenGirl/LedgeHangStart04.vcd"; case 25: s_Vocalize = "scenes/TeenGirl/LedgeHangStart05.vcd"; case 26: s_Vocalize = "scenes/TeenGirl/LedgeHangStart06.vcd"; case 27: s_Vocalize = "scenes/TeenGirl/LedgeHangStart10.vcd"; } } } else { PrintToChatAll( "i_Type was invalid!") return; }
if(actor > 0) /* Make sure the actor is valid, there are cases where the "world" uses vocalize lines, such as Whittalker or Virgil the talking boat. */ { CancelScene(scene); PerformSceneEx(actor, "", s_Vocalize, preDelay, pitch, SCENE_INITIATOR_PLUGIN) } }
This is just a snippet, there are more i_Types declared and replaced with switches, but I snipped them for the sake of condensation.
The scenes being looked out for are the EXACT scenes I have in my Talker Addon, however, sometimes the scenes are captured, and at other times, they're completely ignored?
Also, I don't know if this is important or not, but the following line give me a "Argument type mismatch" on compile;
PHP Code:
new float:preDelay = GetScenePreDelay(scene); new float:pitch = GetScenePitch(scene);
// And this line PerformSceneEx(actor, "", s_Vocalize, preDelay, pitch, SCENE_INITIATOR_PLUGIN)
I don't think I'm doing anything wrong, maybe it's how I set up the scene replacement?
I took your example code and used it to achieve a certain goal *cough*Fake Zoey*cough*.
While I am completely against this solution as it comes with a myriad of other problems, it should be possible. Do you mind me asking why you are not setting up a Linux dedicated server instead to completely side step this issue?
However I would probably change it so script would not have to continusly loop over strings but instead use a trie in this case since you already know which lines you wish to convert. I have typed up another loose example (e.g. not tested). Complete with debug function to hopefully help you out understand why it is not replacing lines.
Spoiler
PHP Code:
/* Includes */
#include <sourcemod>
#include <sceneprocessor>
/* Plugin Information */
public Plugin:myinfo = {
name = "Fake Zoey",
author = "DeathChaos25 & Buster \"Mr. Zero\" Nielsen",
description = "Implements a fake Zoey makeup to Nick",
version = "1.0.0",
url = "https://forums.alliedmods.net/showthread.php?t=241585"
}
/* Globals */
#define DEBUG 1 /* Change this to 0 to disable debug info to be printed */
#define DEBUG_TAG "Fake Zoey"
#define DEBUG_PRINT_FORMAT "[%s] %s"
public OnSceneStageChanged(scene, SceneStages:stage)
{
if (stage != SceneStage_Spawned || GetSceneInitiator(scene) == SCENE_INITIATOR_PLUGIN /* Do not capture scenes spawned by the plugin, to prevent a loop */)
return
new actor = GetActorFromScene(scene)
if (actor <= 0 || actor > MaxClients || !IsClientInGame(actor))
return
decl String:model[PLATFORM_MAX_PATH]
GetClientModel(actor, model, sizeof(model))
if (!StrEqual(model, MODEL_SURVIVOR_ZOEY))
return
decl String:sceneFile[MAX_SCENEFILE_LENGTH]
/* Never ever print uninitialized strings as it may contain anything
* from sensitive data to straight up crashing players game. Since the debug
* function prints 'newSceneFile' string if no replacement scene is found,
* we need to make sure the string at least is initialized which we do with
* the 'new' keyword. That insures an empty VALID string. Using the 'decl'
* keyword gives us a string very fast but said string have not been
* initialized first and may contain unsafe data for printing. Therefore
* use 'decl' for when you know what you are doing and need speed. 'new'
* if you are unsure whether the string will be initialized. Just a friendly
* tip :). */
new String:newSceneFile[MAX_SCENEFILE_LENGTH]
decl newSceneLen
GetSceneFile(scene, sceneFile, sizeof(sceneFile))
if (!GetTrieString(g_FakeCharacterLinesTrie, sceneFile, newSceneFile, sizeof(newSceneFile), newSceneLen) ||
newSceneLen == 0) {
#if DEBUG
Debug_PrintText("Not replacing scene (\"%s\") as no replacement scene (\"%s\", newSceneLen %d) can be found in trie.", sceneFile, newSceneFile, newSceneLen)
#endif
return
}
new Float:preDelay = GetScenePreDelay(scene)
new Float:pitch = GetScenePitch(scene)
#if DEBUG
Debug_PrintText("Cancelling old scene (\"%s\")...", sceneFile)
#endif
CancelScene(scene)
#if DEBUG
Debug_PrintText("Performing new scene (\"%s\", preDelay %.2f, pitch %.2f)...", newSceneFile, preDelay, pitch)
#endif
PerformSceneEx(actor, "", newSceneFile, preDelay, pitch /* SCENE_INITIATOR_PLUGIN is default and does not need to be specified */)
#if DEBUG
Debug_PrintText("Scene replaced for fake Zoey actor %N!", actor)
#endif
}
Also, I don't know if this is important or not, but the following line give me a "Argument type mismatch" on compile;
PHP Code:
new float:preDelay = GetScenePreDelay(scene);
new float:pitch = GetScenePitch(scene);
// And this line
PerformSceneEx(actor, "", s_Vocalize, preDelay, pitch, SCENE_INITIATOR_PLUGIN)
I don't think I'm doing anything wrong, maybe it's how I set up the scene replacement?
That is because you are using float: as a type. However that is a function to convert integers to floats. You want to use Float: (capitalized F) to mark variable as float data type.
PHP Code:
new myInterger = 3
new Float:myFloat = float(myInterger) /* Comes out as 3.0 */
Edit:
Also here are some tips if you are new to SourcePawn that I have personal found useful but feel free to skip it.
Over the years, I have come to learn that coding style is probably one of the more important things. If you look back at the various plugins I have made over the years is that I have slowly changed coding style, adopting various things from various authors of other plugins that I have learned from. However it often turns into a some kind of coding mosh pit when looking back and you have learned some new ways of writing code. I have personally found the Linux kernel coding style a really good style for SourcePawn. Of course I will not be a grammar nazi on this point and everyone got their own favourite style of writing, but as a piece of advise, I would suggest picking one and sticking to it. You will find it a lot easier to go back to previous scripts you have done in the past and bring them up to date.
If there is an easier way to accomplish the goal, then take it. SourcePawn can do some pretty cool stuff but over complicating a plugin for the sake of doing it the hard way, can be crippling difficult to debug later down the road. On that note I would also like to say that I personal prefer to do multiple plugins, each having 1000 lines, than one big plugin of 50000 lines. It is a lot easier on the eyes.
The API is your friend. Learn to love it. Search on it frequently. Some people are even nice enough to leave comments if some parameters happens to be confusingly named. Otherwise do not be afraid of asking in the scripting forum section, people are nice as long you search and read on the API first then ask a question.