Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - .

#1
Community Plugins / Simple Hash
Jul 29, 2018, 02:37 PM
This is basically an alternative to the official plugin that I implemented quickly after having some issues with the official plugin. Implements the same interface as the official plugin excluding the SHA224, SHA224, SHA384, SHA512, RIPEMD128, RIPEMD160, RIPEMD256, RIPEMD320 functions. In case you need those, feel free to use the official plugin.

The plugin only implements:

  • base64_encode
  • base64_decode
  • CRC32
  • KECCAK
  • MD5
  • SHA1
  • SHA256
  • SHA3
  • WHIRLPOOL

So if you use any of those and you have issues with the official plugin. Then you can use this as an alternative. Just drop the plugin into your server and should work without any changes to your script.

You can find the plugin here:


Please note that I haven't done any extensive testing on it.
#2
This is a topic where I'll be listing various features you might not be aware and which are likely to help your code be even more efficient.
#3
I was asked by someone new to the plugin how to proceed in making something like this. And because I thought it could be useful to more people I decided to make the snippet public. This example shows how the built-in command system can be used to create create such protection:

This is initialization stuff stuff that you usually do anyway:
// ------------------------------------------------------------------------------------------------
// We need to store 2 variables in the player .Data property so we can do it when he connects
// The 2 variables in question are 1) the last executed command name and the execution counter
SqCore.On().PlayerCreated.Connect(this, function(player, header, payload) {
    // Normally, everyone should store a table in the .Data property so that evereyone else can
    // store other stuff in that table. So we should have this somewhere:
    player.Data = { }; // making sure .Data is a table
    // Now we can create our variables in that table
    player.Data.LastCommand <- ""; // Empty for now
    player.Data.CommandExec <- 0; // Start from 0
});
// ------------------------------------------------------------------------------------------------
SqCore.On().PlayerCommand.Connect(this, function(player, command) {
    // Forward this command to the command manager
    g_Cmd.Run(player, command);
});

This is the actual example:
// ------------------------------------------------------------------------------------------------
// We need a command manager to be able to make and run commands.
g_Cmd <- SqCmd.Manager();
// ------------------------------------------------------------------------------------------------
// This is our global function we give to the command manager to tell whether someone is allowed
// or not to execute a certain function. If this function returns false, the command is ignored
function CmdGlobalPlayerAuth(player, command)
{
    // I guess we can use the default authority level to make the decision :/
    return (player.Authority >= command.Authority);
}
g_Cmd.BindAuth(getroottable(), CmdGlobalPlayerAuth);
// ------------------------------------------------------------------------------------------------
// This is a secondary function that we use as a proxy between the above authentication function
// Since each command can have it's own dedicate authentication function instead of just relying on a global one
// They only fall back to the global one if a dedicated one was not bound
function CmdSpamBlockPlayerAuth(player, command)
{
    // Grab the current time in seconds
    local tm = time();
    // We can store this local reference to avoid too much typing and table lookups
    // it'll be equal as doing command.Data each time we use cdata. except faster
    local cdata = command.Data;
    // Is there a temporary ban for this player on this command?
    if (cdata[player.ID] > tm)
    {
        player.Message("You are blocked from using this command for %d more second(s)!", cdata[player.ID] - tm);
        // Block the request to execute this command
        return false;
    }
    // We can store this local reference to avoid too much typing and table lookups
    // it'll be equal as doing player.Data each time we use pdata. except faster
    local pdata = player.Data;
    // Is the player trying to execute the last command again?
    if (pdata.LastCommand == command.Name)
    {
        // Increase the number that we use to tell how many times a command was used consecutively
        ++pdata.CommandExec;
    }
    else
    {
        // Store this command name so we can track it next time
        pdata.LastCommand = command.Name;
        // Reset the counter as this is a new command
        pdata.CommandExec = 0;
    }
    // Did the player executed this command more than 5 times consecutively?
    if (pdata.CommandExec > 5)
    {
        player.Message("You are blocked from using this command for 60 seconds as a result of spam!");
        // Add 60 seconds to the current time and set that as the cool down time
        cdata[player.ID] = tm + 60;
        Warning! if you sate that value too high and the player disconnects then connects back and someone else connects with his ID
        the new player gets the temporary ban while he can continue to use the command.
        If you need a better and more persistent protection than you need to be a little more creative than this.
        // Block the request to execute this command
        return false;
    }
    // If we reached there then fall back to the global function and let that decide the rest
    return CmdGlobalPlayerAuth(player, command)
}
// ------------------------------------------------------------------------------------------------
// Command instance
Test1 <- g_Cmd.Create("test1", "g", ["greeting"], 1, 1, -1, true, true);
// Stuff
Test1.Help = "This is a test command";
// We can use the .Data member within each command to store an array of integers/time-stamps
// that we can use to find out when is the next time that a player can use the command
Test1.Data = array(SqServer.GetMaxPlayers(), 0);
// Command function
Test1.BindExec(Test1, function(player, args)
{
    player.Message("You greeted with: %s", args.greeting);
    return true;
});
// Bind our spam detection function in the authentication stage
Test1.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
// Command instance
Test2 <- g_Cmd.Create("test2", "g", ["announcement"], 1, 1, -1, true, true);
// Stuff
Test2.Help = "This is a test command";
// Same stuff explained above ^
Test2.Data = array(SqServer.GetMaxPlayers(), 0);
// Bind our spam detection function in the authentication stage
Test2.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
Test2.BindExec(Test2, function(player, args)
{
    player.Message("You announcted that: %s", args.announcement);
    return true;
});
// ------------------------------------------------------------------------------------------------
// Command instance
Test3 <- g_Cmd.Create("test3", "g", ["message"], 1, 1, -1, true, true);
// Stuff
Test3.Help = "This is a test command";
// Same stuff explained above ^^
Test3.Data = array(SqServer.GetMaxPlayers(), 0);
// Bind our spam detection function in the authentication stage
Test3.BindAuth(getroottable(), CmdSpamBlockPlayerAuth);
// ------------------------------------------------------------------------------------------------
Test3.BindExec(Test3, function(player, args)
{
    player.Message("You said: %s", args.message);
    return true;
});

And this is a general purpose function bound to a command manager to report back errors:
/* ------------------------------------------------------------------------------------------------
 * General purpose error handler for a command manager.
 * Otherwise the player doesn't know why a command failed.
*/
g_Cmd.BindFail(getroottable(), function(type, msg, payload) {
    // Retrieve the player that executed the command
    local player = g_Cmd.Invoker;
    // See if the invoker even exists
    if (!player || typeof(player) != "SqPlayer")
    {
        return; // No one to report!
    }
    // pu this somewhere else in the script
    local _OWNER_CONTACT_ = "[email protected]";
    // Identify the error type
    switch (type)
    {
        // The command failed for unknown reasons
        case SqCmdErr.Unknown:
        {
            player.Message("Unable to execute the command for reasons unknown");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command failed to execute because there was nothing to execute
        case SqCmdErr.EmptyCommand:
        {
            player.Message("Cannot execute an empty command");
        } break;
        // The command failed to execute because the command name was invalid after processing
        case SqCmdErr.InvalidCommand:
        {
            player.Message("The specified command name is invalid");
        } break;
        // The command failed to execute because there was a syntax error in the arguments
        case SqCmdErr.SyntaxError:
        {
            player.Message("There was a syntax error in argument: %d", payload);
        } break;
        // The command failed to execute because there was no such command
        case SqCmdErr.UnknownCommand:
        {
            player.Message("The specified command does no exist");
        } break;
        // The command failed to execute because the it's currently suspended
        case SqCmdErr.ListenerSuspended:
        {
            player.Message("The requested command is currently suspended");
        } break;
        // The command failed to execute because the invoker does not have the proper authority
        case SqCmdErr.InsufficientAuth:
        {
            player.Message("You don't have the proper authority to execute this command");
        } break;
        // The command failed to execute because there was no callback to handle the execution
        case SqCmdErr.MissingExecuter:
        {
            player.Message("The specified command is not being processed");
        } break;
        // The command was unable to execute because the argument limit was not reached
        case SqCmdErr.IncompleteArgs:
        {
            player.Message("The specified command requires at least %d arguments", payload);
        } break;
        // The command was unable to execute because the argument limit was exceeded
        case SqCmdErr.ExtraneousArgs:
        {
            player.Message("The specified command requires no more than %d arguments", payload);
        } break;
        // Command was unable to execute due to argument type mismatch
        case SqCmdErr.UnsupportedArg:
        {
            player.Message("Argument %d requires a different type than the one you specified", payload);
        } break;
        // The command arguments contained more data than the internal buffer can handle
        case SqCmdErr.BufferOverflow:
        {
            player.Message("An internal error occurred and the execution was aborted");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command failed to complete execution due to a runtime exception
        case SqCmdErr.ExecutionFailed:
        {
            player.Message("The command failed to complete the execution properly");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The command completed the execution but returned a negative result
        case SqCmdErr.ExecutionAborted:
        {
            player.Message("The command execution was aborted and therefore had no effect");
        } break;
        // The post execution callback failed to execute due to a runtime exception
        case SqCmdErr.PostProcessingFailed:
        {
            player.Message("The command post-processing stage failed to complete properly");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // The callback that was supposed to deal with the failure also failed due to a runtime exception
        case SqCmdErr.UnresolvedFailure:
        {
            player.Message("Unable to resolve the failures during command execution");
            player.Message("=> Please contact the owner: %s", _OWNER_CONTACT_);
        } break;
        // Something bad happened and no one knows what
        default:
            SqLog.Inf("Command failed to execute because [%s][%s]", msg, ""+payload);
    }
});

The example uses some hard-coded values because that's what it is, an example. In reality, you shouldn't do that. Because it becomes annoying later to change.
#4
I got curious. Is there any public server that uses this plugin? And if there is. I have a few questions:

  • Does it crash often? I currently marked the plugin as still in beta because API changes may still occur. But I'm curious whether crashes may be a part of that reason as well.
  • Is the performance reasonable? Whether you encounter any performance bottlenecks and where. Assuming they're from the plugin and not the server itself.
  • Was the API flexible enough? When making your script. Was the provided API flexible enough to not constrain your design decisions?
  • What is your server name/IP? I would like to join for a few moments one day and see for myself.

Thanks for the input.
#5
Snippet Showroom / Password strength function
Apr 10, 2017, 10:06 PM
This was a request by someone and as the title says it's meant to make show how hard is a password to brute-force. It takes into account character diversity, repetition, succession and size to generate a strength score.

But what do I mean by that? Well, this is what I mean:
  • diversity: Is meant to award passwords that contain lowercase and uppercase letters as well as digits and symbols. More diversity means a greater score. Examples:
    • h%s4(k&^s yields a score of 45 (the following, even though not as diverse as this one, are awarded by the size and succession checks)
    • s62h356fs yields a score of 36
    • 374652942 yields a score of 32
    • ajshwfown yields a score of 41
  • repetition: Is meant to punish passwords that have repeating characters. Examples:
    • aabbccdde yields a score of 11
    • aaaaaaaaa yields a score of -4
    • 882222999 yields a score of 21
    • %%%^&&&(( yields a score of 23
  • succession: Is meant to punish passwords that contain successive characters. Examples:
    • abcdefghi yields a score of 3
    • 123456789 yields a score of 3
    • !"#$%&() yields a score of 7
    • abc123xyz yields a score of 22

As you can see, passwords that have a score greater than 10 are likely to be harder to brute-force. And those with scores greater than 20, 30, 40 are right there on the insane scale. Length also plays an important role.

What this doesn't check for, are dumb passwords like "passwords", "qwertyui", "asdfghj", "mypass". For these things, you must implement your own rainbow-table and do a manual check. There's only so much that a function like this can do.

Anyway, here's the snippet:
function PasswordStrength(p)
{
    // Ignore empty or dumb passwords
    if (!p || p.len() <= 1) return -999;
    // Preallocate all variables upfront
    local d = 0, u = 0, l = 0, s = 0, r = 0, a = array(0xFF, 0), t = p.len();
    // Classify characters
    foreach (c in p)
    {
        // Count repetition
        if (++a[c] > 1) ++r;
        // Count diversity
        else if (c >= '0' && c <= '9') ++d;
        else if (c >= 'A' && c <= 'Z') ++u;
        else if (c >= 'a' && c <= 'z') ++l;
        else if (c >= ' ' && c <= '/') ++s;
        else if (c >= ':' && c <= '@') ++s;
        else if (c >= '[' && c <= '`') ++s;
        else if (c >= '{' && c <= '~') ++s;
    }
    // Score diversity
    if (d > 0) t += d; else t -= 2;
    if (u > 0) t += u; else t -= 2;
    if (l > 0) t += l; else t -= 2;
    if (s > 0) t += s; else t -= 2;
    // Score repetition
    if ((p.len() - r) < 3) t -= r; else t += p.len();
    // Score succession
    for (local i = 2, j = p[0], k = p[1], x = abs(k - j), o = (x == 1).tointeger(), n = p.len();
            i < n;
            j = k, k = p[i], ++i, x = abs(k - j), o += (x == 1).tointeger())
    {
        if (x == 1) {
            if (o > 2) t -= 3;
            else if (o > 1) t -= 2;
            else if (o > 0) t -= 1;
        } else if (x > 3) t += 3, o = 0;
        else if (x > 2) t += 2, o = 0;
        else if (x > 1) t += 1, o = 0;
    }
    // Return resulted score
    return t;
}

NOTE: This is a dumb algorithm made on the fly. No books or guides were hurt in the process. Therefore, it may have weaknesses and is far from perfect. But it's something.
#6
Via the client or the server. I'm curious if there's a way to detect if a player has finished downloading the files from the server and they can be used.

I could've try a few things by myself silently but it wouldn't hurd to have these topics available for anyone else in the future.

EDIT: I think a Script::ScriptLoad() should theoretically do it but knowing VC:MP I'm pretty sure it's gonna mess that up. Gotta try that.

EDIT: Yep. That seems to do it.
#7
Basically, how do you avoid this efect:

#8
I've been trying to load a simple object in a server but for some reason I can't seem to be able to do it.

Everything I've tried failed.
  • I've used an older and hopefully compatible version of 7zip (9.20)
  • I've kept the texture name legth bellow 8 characters. I think the limit is 20? but I'm not sure.
  • I've made sure the .txd version is meant for vcmp compatibility.
  • And everything you can think of.

But it always fails to load the texture with the error:
Error in RwTexDictionaryStreamRead: #Native texture tag was not found. #1
Error in RwTexDictionaryBufferRead: RwTexDictionary is null.

The example object in question can be obtained here: https://ufile.io/b7f2e
#9
Spent some time tonight trying to finally fix the timers implementation. At the moment, I only have linux binaries. If anyone has Visual Studio installed and can provide Windows binaries that would be awesome. The repository can be found here. Remember to use the `newapi` branch when retrieving the source.

Features:
  • Timers limit is 1024. This can be increased to much more at compile time. The implementation is very fast and there wouldn't be any impact on performance.
  • Any type of value can be passed as a parameter to the timer. Literally anything you want. Which means you'll have to be careful because it keeps a strong reference to the given parameters.
  • Any type of function can be passed. This does not take function names but rather function objects. Meaning you pass the function directly. Either a regular function, native, or lambda.
  • Supports custom environments. You can force the function to run in different environments. Meaning you can create timers on methods of classes and so on.
  • You can associate custom objects or custom names/tags with each timer instance. Meaning you can associate certain information with the timer so you can later use it.

Unfortunately, I could not maintain backwards compatibility. However, you can make several workarounds in scripts to make them look like the old timers. Although I'd suggest you just update the code to use the new approach.



To create a timer, use the function `MakeTimer`. It uses a different name to not collide with the old one `NewTimer`. The syntax of the function is as following:
MakeTimer(environment, callback, interval, iterations, ...);
  • environment: This can be a table, class or class instance. And the function will run as if it was called on it. You can also use null and it defaults to the root table.
  • callback: This can be a function, lambda or a native function. NOTE: You pass the function by value not by name. Which means you can also pass anonymous functions.
  • interval: The number of milliseconds to wait between each call. Meaning how much time to wait before calling the function.
  • iterations: The number of times to be triggered before terminating itself. If you pass 1 then it calls the function once and then terminates itself. If you pass 0, it calls the function indefinitely.
  • After these parameters you can pas up to 14 values which will be forwarded to the function which you specified. Anything you add here will be forwarded in the same order.

The function returns the timer instance. The function also throws an error if something goes wrong. So you should use a try/catch block if you want your code to continue after that.

Example:
MakeTimer(this, function(separator, list) {
    foreach(index, value in list)
    {
        print(index + separator + value);
    }
}, 1000, 2, " - ", [32, 82, 21, 97]);

Outputs twice every 1000 milliseconds (one second):
[SCRIPT]  0 - 32
[SCRIPT]  1 - 82
[SCRIPT]  2 - 21
[SCRIPT]  3 - 97
[SCRIPT]  0 - 32
[SCRIPT]  1 - 82
[SCRIPT]  2 - 21
[SCRIPT]  3 - 97



To find a timer with a certain tag/name use `FindTimerByTag`. It takes the same parameters as the `format` function. Then it generates a string and looks for a timer with a tag/name similar to that string. Basically, you can pass any type of value here and it'll be converted to a string internally:
FindTimerByTag(value, ...);
The function returns the timer instance which matched the resulted string or throws an error if it couldn't find it.

Example:
MakeTimer(this, print, 1000, 1, "Hello!").SetTag("PrintTest");
FindTimerByTag("PrintTest").Terminate();

This shouldn't output "Hello!" after a second because I searched the timer and terminated it before it could finish.



Here is a list of the the member functions of the class:
string GetTag();
[Timer] SetTag(value, ...);
object GetEnv();
[Timer] SetEnv([table,class,instance] environment);
function GetFunc();
[Timer] SetFunc([function] callback);
object GetData();
[Timer] SetData([object] user_data);
integer GetInterval();
[Timer] SetInterval([integer] interval);
integer GetIterations();
[Timer] SetIterations([integer] iterations);
bool IsSuspended();
bool GetSuspended();
[Timer] SetSuspended([bool] toggle);
integer GetArgCount();
object GetArgument([integer] index);
null Terminate();

All of these methods throw errors if something goes wrong so I'd suggest you pay close attention. This doesn't just die silently.



The `Terminate` method terminates the timer regardless of how many iterations or time it has left. It only invalidates the timer and releases all references to any stored object such as the function itself or the environment. The timer instance is not cleaned until all references to it are released. Meaning it's not stored anywhere else in the script.

Which means that if you continue to use a timer instance after it was terminated you'll receive some errors thrown at you. Nothing bad, just letting you know that the timer you're attempting to use does not exist anymore.



The `GetTag` and `SetTag` can be used to retrieve or modify the associated tag. The tag is a string which acts like a name so you can search the tag by it. The `SetTag` function has the same syntax as the `format` function. Which means it has built in formatting support. The `SetTag`returns back the timer instance so that operations can be chained.

MakeTimer(this, print, 1000, 1, "Hello!").SetTag("PrintTest");
FindTimerByTag("PrintTest").SetTag("abc%s", "xyz");



The `GetEnv` and `SetEnv` allow you to retrieve or modify the function environment at any time after you've created the timer. The `SetEnv`returns back the timer instance so that operations can be chained.



The `GetFunc` and `SetFunc` allow you to retrieve or modify the callback function at any time after you've created the timer. The `SetFunc`returns back the timer instance so that operations can be chained.



The `GetData` and `SetData` allows you to store anything you want alongside the timer instance. Meaning you can associate certain values with a timer so you can later retrieve it, even from within the callback function if you want. The `SetData`returns back the timer instance so that operations can be chained.

Example:
MakeTimer(this, function() {
    local list = FindTimerByTag("ABC").GetData();
    foreach (index, value in list)
    {
        print(index + " - " + value);
    }
}, 1000, 1).SetTag("ABC").SetData([32, 54, 22]);

Outputs:
[SCRIPT]  0 - 32
[SCRIPT]  1 - 54
[SCRIPT]  2 - 22



The `GetInterval` and `SetInterval` allows you retrieve or modify the timer interval at any time after you've created the timer. The `SetInterval`returns back the timer instance so that operations can be chained.



The `GetIterations` and `SetIterations` allows you retrieve or modify the remaining iterations at any time after you've created the timer. The `SetIterations`returns back the timer instance so that operations can be chained.



The `GetSuspended` and `SetSuspended` allows you see if blocked or block the interval from forwarding calls at any time after you've created the timer without terminating the timer instance. The `IsSuspended` is an alias for `GetSuspended`. Setting this to true means that the function will not receive any calls even if the interval time elapsed. The `SetSuspended`returns back the timer instance so that operations can be chained.



The `GetArgCount` allows you to see how many arguments the timer will forward to the callback function.



The `GetArgument` allows you to retrieve arguments the timer will forward to the callback function.

Example:
local t = MakeTimer(this, function(v, a) {
     print(v+a);
}, 1000, 1, "Hello", "World!");
print(t.GetArgument(1));

Outputs:
[SCRIPT]  World!
[SCRIPT]  HelloWorld!

Indexing starts from 0, so 1 means the second argument.
#10
Videos & Screenshots / Imgui In Vc:mp
Dec 13, 2016, 09:40 PM
This is just a small project I've done tonight to attempt in an attempt to have a better GUI in VC:MP. This is just a couple hours of work with most of the time spent debugging. Mostly for the lolz and to satisfy some curiosity. Sorry for the bad quality but I have a sh!tty upload speed.

https://youtu.be/iEF1zBvUEZ8

NOTE: Since DirectX8 does not support Scissors, and no workaround was implemented, some clipping artifacts can be observed. Such as lines and/or text going outside the areas they're supposed to. This can be fixed with a bit of extra work. But meh.

IMGUI: https://github.com/ocornut/imgui
#11
This snippet shows how to create a simple IRC bot using the basic IRC module that accompanies the main plugin. It shows how registered types can be inherited to alter behavior and how to use the built in command system with other things and not just the game commands.

The snippet can be found here in order to preserve indentation: https://bitbucket.org/snippets/iSLC/n4yEj

It only comes with a few simple commands such as:
  • .eval code
  • .say text
  • .me text

But it's enough to get someone started on making their own IRC bot. Feel free to report issues on this topic.
#12
This topic will exist here to point out noteworthy additions and changes in the plug-in. Don't expect these changes to exist in the binaries immediately. There should be an edit with a note at the end of a post when binaries caught up with the changes.
#13
This is outdated. After reading this (it is still relevant!) go ahead and read this.

There are two types of events supported by the plugin. Global and local. Global events are emitted by the plugin core. Local plugins are emitted by instances of entities that you create. All events can be used as global but not all can be used as local. For example, you can't listen if an entity instance was created using the local event since it doesn't even exist for you to bind to that instance. But you can listen if it was destroyed using the local event since the instance exists already for you to bind to it.

A local event, once bound to a certain entity will only be triggered if only that entity emits that event. A global event, once bound to the plugin core will be emitted by anyone who triggers that event.

Binding to local events can be done through the SqCore.Bind function. Binding to local events can be done through the member function `Bind()` provided by each entity instance. They both have the same signature. Only the behavior is different.

If you're not a OOP enthusiast then local bindings will most likely be of no use to you. So using the global bindings is the best route to receive events from the plugin.

You can find a list of all events supported by the plug-in here. Some of them may not be documented yet. The one that are introduced by the plugin and have a different syntax and behavior than the official plugin have a higher priority to be documented.

Events can execute any callable function. Free functions, Anonymous functions, Lambdas or even Member functions which are the same thing as Free functions except with a class instance as the environment.

An event can only be assigned a single function to be called. If another function is bound, the previous one is no longer called. To stop listening for callbacks from events, you can bind a null value.



Binding a Free function to the server frame event:
local g_FrameCount = 0;

function onServerFrame(delta)
{
    printf("Server frame %d", ++g_FrameCount);
}

SqCore.Bind(SqEvent.ServerFrame, this, onServerFrame);



Binding an Anonymous function to the server frame event:
local g_FrameCount = 0;

SqCore.Bind(SqEvent.ServerFrame, this, function(delta)
{
    printf("Server frame %d", ++g_FrameCount);
});



Binding a Member function to the server frame event:
class Gamemode
{
    m_FrameCount = 0;

    function constructor()
    {
        SqCore.Bind(SqEvent.ServerFrame, this, onServerFrame);
    }

    function onServerFrame(delta)
    {
        printf("Server frame %d", ++m_FrameCount);
    }
}

GM <- Gamemode();

You can see that the event system is quite flexible and allows all sorts of designs to be achievable. Feel free to check the documentation for more.
#14
Binaries can be found here: https://discord.gg/JMcXBa3 (See: https://forum.vc-mp.org/?topic=4856.0) They are updated as soon as changes appear in the repository. And when someone has time or is generous enough to publish their builds. Just ask.

OUTDATED INFORMATION. READ CAREFULLY!

Folder structure:
  • dependencies
    This folder contains shared DLLs or files that the other plug-ins depend on and must be copied into the server directory (near the executable). Some of the other files (excluding DLLs) are provided as an example for a base setup.
  • dynamic
    This folder contains plug-in binaries that have been built to use the operating system shared libraries. On Windows they require the run-times from the dependencies directory. They have a smaller footprint and share code that can be shared meaning that they tend to use less memory and add less executable code to the server.
  • standalone
    This folder contains plug-in binaries that have been built with the runtime libraries embedded into them. They have a larger footprint and because of the duplicate code they tend to use more memory and add more executable code to the server.

Any file or folder suffixed with '-d' contains a debug version of the plug-in. They're only meant to track down bugs in plug-in because they include extra debug messages and debug information. As well as the location in source code where a certain exception is thrown. Update: Due to their size, debug binaries are no longer maintained.

The main Squirrel plug-in must always be loaded first in the server configuration file. Otherwise you'll get a warning and the server won't start.

The server will fail to start and most likely shut down with an exception message (to you it'll seem like a crash but it's the intended behavior) if there's an error in the script code. And this normal behavior because the server should not be running with broken code.

Some modules are still a work in progress. Only a portion of all the modules are available for download. And even some of these are still incomplete. Well, complete compared to the official ones but incomplete for my preferences.

Keep in mind that this is a beta release in order to allow people to report bugs and get used to it. This plug-in requires extensive knowledge of programming to be properly used. If you've never read any programming books and just started for the sake of doing it. Don't get your hopes up and stick to the official plug-in.

Documentation is still a work in progress and it's just too much to document by myself. Documentation will be added here with time. At the moment, only a fraction of the plugin is documented. And some of the information there can be outdated. So be careful what you read from there. Feel free to contribute though.
#15
Support / Adjustments to the plugin SDK
Aug 19, 2016, 06:00 PM
I'm reserving this topic for future requests and probably suggestions to the plugin SKD that could be of use. Please do not post in this topic if you're not a developer or you have no idea what you're talking about.



Bulk retrieval of entity positions:
/*
Returns:
    Number of remaining entities if GetLastError() is equal to vcmpErrorBufferTooSmall
    Number of found entities that were saved in the buffer.

GetLastError:
vcmpErrorNoSuchEntity - Wrong entity pool? Does not have a position?
vcmpErrorBufferTooSmall - One of the specified buffers were too small
*/
int32_t (*GetAllEntityPositions) (vcmpEntityPool entityPool, int32_t* idBuffer, uint32_t idBufferSize, float * posBuffer, uint32_t posBufferSize);

/* Possible checks to be done internally:
    posBufferSize /= 3; // (x,y,z)
    if (posBufferSize < idBufferSize) generate vcmpErrorBufferTooSmall;
*/

Rather than validating each entity and requesting it's size all the time. Should save some time. To be able to create efficient functions for situations like in this topic. And probably a few others.

The identifiers of the active entities are put into idBuffer and their position is put into posBuffer.



More to be suggested as I recall them. I used to have plenty of requests but I seem to have forgot them for now.
#16
Sorry, but this topic was not fair to everyone. And since the devs allow all kinds of topics, why not.

What is accepted: Music title or direct link to streaming source.

NOTE: THIS IS ONLY FOR WHAT YOU'RE LISTENING RIGHT NOW. NOT YOUR FAVORITE!
#17
I've encountered this issue recently. Where plugin commands are not send on Linux. Which means that their API is not registered with my host Squirrel plugin.

It's as if I'm not even sending that command:


How it looks on windows:


The commands are executed during the OnServerInitialise callback when all plugins have been loaded. There's a command executed before that (also ignored) which tells the other plugins to retrieve the Squirrel API and check if they're compatible with the host plugin. And then the one which tells the other plugins to register their API into the obtained Squirrel virtual machine.

Am I doing something wrong? If I remember correctly, the official plugin uses the same mechanism. Except I haven't tested there to be sure since their source is private after the last SDK update.
#18
I was able to make an utility (for personal use) which injects a list of scripts into the client then saves the compiled closure to a file in order to secure my client-side scripts. Then I placed the compiled scripts in the 'store/script' folder. And connected to the server. The client downloaded the compiled files and then I tried to load them. However, they were read as strings/text and obviously that's not going to work. Could you implement a dofile() function like the on from the standard library which handles both compiled and regular scripts? Basically, you only need to check if the first 2 bytes of the file are equal to SQ_BYTECODE_STREAM_TAG. And if they are then treat it as a compiled script. Details are in the standard IO library.
#19
This evolved as a dumb chat on IRC and questions the possibility of delivering the VC:MP server as a DLL. Basically, you compile the server as a DLL and export a simple function that can be used to retrieve the interfaces required to work with the server.

Considering an API like the plugins:
#define SERVER_API_VER 7

// enumes etc.

typedef void (*SDK_OnFrame) (float elapsed);
// more callbacks...

typedef struct {
  unsigned int            uStructSize;
  SDK_OnFrame             OnFrame;
  // more callbacks...
} ServerCallbacks;

typedef int32_t (*SDK_LogOutput) (const char * fmt, ...);

typedef int32_t (*SDK_ProcArg) (int32_t argc, char **argv);
typedef int32_t (*SDK_BindHost) (const char * addr);
typedef int32_t (*SDK_BindPort) (uint16_t, addr);
typedef int32_t (*SDK_SetStorage) (const char * folderpath);
typedef int32_t (*SDK_SetLogFilter) (uint32_t filter);
typedef int32_t (*SDK_SetLogOutput) (SDK_LogOutput output);
typedef uint32_t (*SDK_GetServerVersion) (void);
typedef int32_t (*SDK_StartServer) (void);
typedef int32_t (*SDK_StopServer) (void);
typedef int32_t (*SDK_SetCallbacks) (ServerCallbacks * callbacks);
// more api...

typedef struct {
  unsigned int            uStructSize;
  SDK_ProcArg             ProcArg;
  SDK_BindHost            BindHost;
  SDK_BindPort            BindPort;
  SDK_SetStorage          SetStorage;
  SDK_SetLog              SetLog;
  SDK_GetServerVersion    GetServerVersion;
  SDK_StartServer         StartServer;
  SDK_StopServer          StopServer;
  SDK_SetCallbacks        SetCallbacks;
  // more api...
} ServerFuncs;

The server would export a function that accepts those structures and would fill them with it's internal function pointers:
export void InitServerAPI(ServerFuncs * func, int apiver)
{
    if (apiver != SERVER_API_VER)
    {
        return; // throw/report w/e
    }

    func->ProcArg           = VCAPI_ProcArg;
    func->BindHost          = VCAPI_BindHost;
    func->BindPort          = VCAPI_BindPort;
    func->SetStorage        = VCAPI_SetStorage;
    func->SetLogFilter      = VCAPI_SetLogFilter;
    func->SetLogOutput      = VCAPI_SetLogOutput;
    func->GetServerVersion  = VCAPI_GetServerVersion;
    func->StartServer       = VCAPI_StartServer;
    func->StopServer        = VCAPI_StopServer;
    func->SetCallbacks      = VCAPI_SetCallbacks;
}

And then in my code I'd load the DLL into my process, find the InitServerAPI symbol, call it to obtain the API and work with it from there.

And leave plugin system implementation to the user if necessary. Of course the developers could even provide an open source C/C++ program that starts the server and even provides a plugin system which would be the default choice if you don't want to implement it yourself. An approach like this would allow the user to have more control of the server. Maybe even allow the user to control transferred resources and other stuff.

Keeps the important parts closed source from people to mess with but also gives a decent amount of control to the user.

Another benefit I could see from this is that you don't have to embed the programming languages inside the server (which is very hard for mature and popular languages like Python/Perl/Ruby etc.). Instead, embed the server inside programming languages since most programming languages allow some kind plugin/module systems. I could easily embed the server inside Node.js I'd want something like that. Not to mention that embedding in other languages would be a lot easier.

I could even build a GUI for the server to make it easier to debug or develop since most popular languages have some decent IDEs, it would be much easier to debug the server if scripting for it. The possibilities are endless.



But the problem is that I'm not sure how the server is implemented and if something like this would be possible without extreme changes and a lot of effort.
#20
Description
Again. The client crashes if a textdraw is shown to a player on spawn

Reproducible
Always

What you were doing when the bug happened
This started as a joke on IRC (log bellow). Eventually reaching to the sweet spot that causes this behavior.

What you think caused the bug
I couldn't possibly know that.



Take the following example code:
local str = "";

for (local n = 0; n < 8; ++n)
{
    str += " YOU NO 60 FPS! ";
}

function onServerStart()
{
    text <- CreateTextdraw(str, 16, 16, 0x00FF00FF);
}

function onPlayerSpawn(player)
{
    text.ShowForPlayer(player);
}

Place it into a simple script file -> Load it as the gamemode -> Start the server -> Connect to the server -> Try to spawn. Your client should crash.



IRC log. Irrelevant but was promised in description.
01:14:48 <Murdock> autokick low fps!
01:15:08 <Kirollos> wow
01:15:13 <Kirollos> you want to autokick all pakis?
01:15:32 <Murdock> if plr.FPS != 60 then plr.Kick
01:15:41 <Kirollos> wow
01:15:41 <Murdock> how?
01:15:43 <Kirollos> !=
01:14:20 <SLC> you slipped there Murdock :P
01:14:32 <SLC> if you have 61 fps. you get kicked :P
01:16:17 <Kirollos> ikr D:
01:16:48 <Murdock> nono my script are fine u always angry SLC dont judge me just working script give me
01:15:43 * SLC puts hand in his pocket and prepares to pull something out...
01:16:04 <SLC> SUPRISE! it's an empty hand
01:16:22 <SLC> out of scripts
01:18:09 <Kirollos> surprise mafaka
01:17:43 <SLC> you know what'll be funny? if the player has FPS < 60.
create a string with repeated "YOU NO 60 FPS!" and draw it in the middle of the screen
01:18:08 <SLC> enough to occupy most of the screen
01:20:09 <Murdock> that would be a nice suggestion for the extremewhiteycity server
01:18:48 <SLC> that will cause the FPS to got even lower and eventually make him leave
01:20:45 <Murdock> now stormeus will have to hadd textdraw.Scale
01:20:51 <Murdock> -h
01:20:06 <SLC> hmm, let's see how many characters can there be in a textdraw :D
01:22:50 <Murdock> hmm, let's see if i can grab any snack around me
01:22:48 <SLC> making it a bright green
01:28:21 <SLC> hmm, doesn't show it
01:30:42 <SLC> Murdock, guess what XD
01:32:35 <Murdock> ?
01:30:59 <SLC> I just found a new way to crash the client XD
01:31:33 <SLC> gonna try it on the official plugin to see if it does the same
01:37:24 <SLC> lol, it even steals the mouse and you need to close it by keyboard XD
01:38:08 <SLC> I'm gonna send you a piece of code to confirm it's not just me