Скомпилируйте плагин

gam19980

Участник
Сообщения
61
Реакции
3
Скомпилируйте, пожалуйста плагин
C-подобный:
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *                              Ultimate Mapchooser - Rock The Vote                              *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
 
#pragma semicolon 1

#include <sourcemod>
#include <umc-core>
#include <umc_utils>
#include <csgo_colors>

#undef REQUIRE_PLUGIN

//Auto update
#include <updater>
#if AUTOUPDATE_DEV
    #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/dev/updateinfo-umc-rockthevote.txt"
#else
    #define UPDATE_URL "http://www.ccs.neu.edu/home/steell/sourcemod/ultimate-mapchooser/updateinfo-umc-rockthevote.txt"
#endif

//Plugin Information
public Plugin:myinfo =
{
    name        = "[UMC] Rock The Vote",
    author      = "Steell",
    description = "Extends Ultimate Mapchooser to provide RTV map voting.",
    version     = PL_VERSION,
    url         = "http://forums.alliedmods.net/showthread.php?t=134190"
};

//Changelog:
/*
3.3.2 (3/4/2012)
Updated UMC Logging functionality
Added ability to view the current mapcycle of all modules

3.3.1 (12/13/11)
Updated sm_umc_rtv_postvoteaction cvar to allow for normal RTV votes after a vote has taken place.
*/

        ////----CONVARS-----/////
new Handle:cvar_filename             = INVALID_HANDLE;
new Handle:cvar_scramble             = INVALID_HANDLE;
new Handle:cvar_vote_time            = INVALID_HANDLE;
new Handle:cvar_strict_noms          = INVALID_HANDLE;
new Handle:cvar_runoff               = INVALID_HANDLE;
new Handle:cvar_runoff_sound         = INVALID_HANDLE;
new Handle:cvar_runoff_max           = INVALID_HANDLE;
new Handle:cvar_vote_allowduplicates = INVALID_HANDLE;
new Handle:cvar_vote_threshold       = INVALID_HANDLE;
new Handle:cvar_fail_action          = INVALID_HANDLE;
new Handle:cvar_runoff_fail_action   = INVALID_HANDLE;
new Handle:cvar_rtv_enable           = INVALID_HANDLE;
new Handle:cvar_rtv_changetime       = INVALID_HANDLE;
new Handle:cvar_rtv_delay            = INVALID_HANDLE;
new Handle:cvar_rtv_minplayers       = INVALID_HANDLE;
new Handle:cvar_rtv_postaction       = INVALID_HANDLE;
new Handle:cvar_rtv_needed           = INVALID_HANDLE;
new Handle:cvar_rtv_interval         = INVALID_HANDLE;
new Handle:cvar_rtv_mem              = INVALID_HANDLE;
new Handle:cvar_rtv_catmem           = INVALID_HANDLE;
new Handle:cvar_rtv_type             = INVALID_HANDLE;
new Handle:cvar_rtv_dontchange       = INVALID_HANDLE;
new Handle:cvar_rtv_startsound       = INVALID_HANDLE;
new Handle:cvar_rtv_endsound         = INVALID_HANDLE;
new Handle:cvar_voteflags            = INVALID_HANDLE;
new Handle:cvar_enterflags           = INVALID_HANDLE;
new Handle:cvar_enterbonusflags      = INVALID_HANDLE;
new Handle:cvar_enterbonusamt        = INVALID_HANDLE;
        ////----/CONVARS-----/////

//Mapcycle KV
new Handle:map_kv = INVALID_HANDLE;       
new Handle:umc_mapcycle = INVALID_HANDLE;

//Memory queues.
new Handle:vote_mem_arr = INVALID_HANDLE;
new Handle:vote_catmem_arr = INVALID_HANDLE;

//Array of players who have RTV'd
new Handle:rtv_clients = INVALID_HANDLE;

//Stores whether or not players have seen the long RTV message.
new bool:rtv_message[MAXPLAYERS+1];

//Keeps track of a delay before we are able to RTV.
new Float:rtv_delaystart;

//How many people are required to trigger an RTV.
new rtv_threshold;

//Flags
new bool:rtv_completed;    //Has an rtv been completed?
new bool:rtv_enabled;      //Is RTV enabled right now?
new bool:vote_completed;   //Has UMC completed a vote?
new bool:rtv_inprogress;   //Is the rtv vote in progress?

//Sounds to be played at the start and end of votes.
new String:vote_start_sound[PLATFORM_MAX_PATH], String:vote_end_sound[PLATFORM_MAX_PATH],
    String:runoff_sound[PLATFORM_MAX_PATH];


//************************************************************************************************//
//                                        SOURCEMOD EVENTS                                        //
//************************************************************************************************//

//Called when the plugin is finished loading.
public OnPluginStart()
{
    cvar_enterbonusflags = CreateConVar(
        "sm_umc_rtv_enteradminflags_bonusflags",
        "",
        "If specified, players with any of these admin flags will be given bonus RTV entrance votes, of the amount determined by \"sm_umc_rtv_enteradminflags_bonusamt\"."
    );

    cvar_enterbonusamt = CreateConVar(
        "sm_umc_rtv_enteradminflags_bonusamt",
        "2",
        "The amount of entrance votes to be given to players who have at least one of the admin flags specified by \"sm_umc_rtv_enteradminflags_bonusflags\".",
        0, true, 1.0
    );

    cvar_voteflags = CreateConVar(
        "sm_umc_rtv_voteadminflags",
        "",
        "Specifies which admin flags are necessary for a player to participate in a vote. If empty, all players can participate."
    );
    
    cvar_enterflags = CreateConVar(
        "sm_umc_rtv_enteradminflags",
        "",
        "Specifies which admin flags are necessary for a player to enter RTV. If empty, all players can participate."
    );

    cvar_fail_action = CreateConVar(
        "sm_umc_rtv_failaction",
        "0",
        "Specifies what action to take if the vote doesn't reach the set theshold.\n 0 - Do Nothing,\n 1 - Perform Runoff Vote",
        0, true, 0.0, true, 1.0
    );
    
    cvar_runoff_fail_action = CreateConVar(
        "sm_umc_rtv_runoff_failaction",
        "0",
        "Specifies what action to take if the runoff vote reaches the maximum amount of runoffs and the set threshold has not been reached.\n 0 - Do Nothing,\n 1 - Change Map to Winner",
        0, true, 0.0, true, 1.0
    );

    cvar_runoff_max = CreateConVar(
        "sm_umc_rtv_runoff_max",
        "0",
        "Specifies the maximum number of maps to appear in a runoff vote.\n 1 or 0 sets no maximum.",
        0, true, 0.0
    );

    cvar_vote_allowduplicates = CreateConVar(
        "sm_umc_rtv_allowduplicates",
        "1",
        "Allows a map to appear in the vote more than once. This should be enabled if you want the same map in different categories to be distinct.",
        0, true, 0.0, true, 1.0
    );
    
    cvar_vote_threshold = CreateConVar(
        "sm_umc_rtv_threshold",
        "0",
        "If the winning option has less than this percentage of total votes, a vote will fail and the action specified in \"sm_umc_rtv_failaction\" cvar will be performed.",
        0, true, 0.0, true, 1.0
    );

    cvar_runoff = CreateConVar(
        "sm_umc_rtv_runoffs",
        "0",
        "Specifies a maximum number of runoff votes to run for any given vote.\n 0 disables runoff votes.",
        0, true, 0.0
    );
    
    cvar_runoff_sound = CreateConVar(
        "sm_umc_rtv_runoff_sound",
        "",
        "If specified, this sound file (relative to sound folder) will be played at the beginning of a runoff vote. If not specified, it will use the normal start vote sound."
    );
    
    cvar_rtv_catmem = CreateConVar(
        "sm_umc_rtv_groupexclude",
        "0",
        "Specifies how many past map groups to exclude from RTVs.",
        0, true, 0.0
    );
    
    cvar_rtv_startsound = CreateConVar(
        "sm_umc_rtv_startsound",
        "",
        "Sound file (relative to sound folder) to play at the start of a vote."
    );
    
    cvar_rtv_endsound = CreateConVar(
        "sm_umc_rtv_endsound",
        "",
        "Sound file (relative to sound folder) to play at the completion of a vote."
    );
    
    cvar_strict_noms = CreateConVar(
        "sm_umc_rtv_nominate_strict",
        "0",
        "Specifies whether the number of nominated maps appearing in the vote for a map group should be limited by the group's \"maps_invote\" setting.",
        0, true, 0.0, true, 1.0
    );

    cvar_rtv_interval = CreateConVar(
        "sm_umc_rtv_interval",
        "240",
        "Time (in seconds) after a failed RTV before another can be held.",
        0, true, 0.0
    );

    cvar_rtv_dontchange = CreateConVar(
        "sm_umc_rtv_dontchange",
        "1",
        "Adds a \"Don't Change\" option to RTVs.",
        0, true, 0.0, true, 1.0
    );

    cvar_rtv_postaction = CreateConVar(
        "sm_umc_rtv_postvoteaction",
        "0",
        "What to do with RTVs after another UMC vote has completed.\n 0 - Allow, success = instant change,\n 1 - Deny,\n 2 - Hold a normal RTV vote",
        0, true, 0.0, true, 2.0
    );

    cvar_rtv_minplayers = CreateConVar(
        "sm_umc_rtv_minplayers",
        "0",
        "Number of players required before RTV will be enabled.",
        0, true, 0.0, true, float(MAXPLAYERS)
    );

    cvar_rtv_delay = CreateConVar(
        "sm_umc_rtv_initialdelay",
        "30",
        "Time (in seconds) before first RTV can be held.",
        0, true, 0.0
    );

    cvar_rtv_changetime = CreateConVar(
        "sm_umc_rtv_changetime",
        "0",
        "When to change the map after a successful RTV:\n 0 - Instant,\n 1 - Round End,\n 2 - Map End",
        0, true, 0.0, true, 2.0
    );

    cvar_rtv_needed = CreateConVar(
        "sm_umc_rtv_percent",
        "0.65",
        "Percentage of players required to trigger an RTV vote.",
        0, true, 0.0, true, 1.0
    );

    cvar_rtv_enable = CreateConVar(
        "sm_umc_rtv_enabled",
        "1",
        "Enables RTV.",
        0, true, 0.0, true, 1.0
    );

    cvar_rtv_type = CreateConVar(
        "sm_umc_rtv_type",
        "0",
        "Controls RTV vote type:\n 0 - Maps,\n 1 - Groups,\n 2 - Tiered Vote (vote for a group, then vote for a map from the group).",
        0, true, 0.0, true, 2.0
    );

    cvar_vote_time = CreateConVar(
        "sm_umc_rtv_duration",
        "20",
        "Specifies how long a vote should be available for.",
        0, true, 10.0
    );

    cvar_filename = CreateConVar(
        "sm_umc_rtv_cyclefile",
        "umc_mapcycle.txt",
        "File to use for Ultimate Mapchooser's map rotation."
    );

    cvar_rtv_mem = CreateConVar(
        "sm_umc_rtv_mapexclude",
        "4",
        "Specifies how many past maps to exclude from RTVs. 1 = Current Map Only",
        0, true, 0.0
    );

    cvar_scramble = CreateConVar(
        "sm_umc_rtv_menuscrambled",
        "0",
        "Specifies whether vote menu items are displayed in a random order.",
        0, true, 0.0, true, 1.0
    );

    //Create the config if it doesn't exist, and then execute it.
    AutoExecConfig(true, "umc-rockthevote");
    
    //Register the rtv command, hooks "!rtv" and "/rtv" in chat.
    RegConsoleCmd("sm_rtv", Command_RTV);
    RegConsoleCmd("sm_rockthevote", Command_RTV);
    
    //Make listeners for player chat. Needed to recognize chat commands ("rtv", etc.)
    AddCommandListener(OnPlayerChat, "say");
    AddCommandListener(OnPlayerChat, "say2"); //Insurgency Only
    AddCommandListener(OnPlayerChat, "say_team");

    //Hook all necessary cvar changes
    HookConVarChange(cvar_rtv_mem,    Handle_RTVMemoryChange);
    HookConVarChange(cvar_rtv_enable, Handle_RTVChange);
    HookConVarChange(cvar_rtv_needed, Handle_ThresholdChange);
    
    //Initialize our memory arrays
    new numCells = ByteCountToCells(MAP_LENGTH);
    vote_mem_arr    = CreateArray(numCells);
    vote_catmem_arr = CreateArray(numCells);
    
    //Initialize rtv array
    rtv_clients = CreateArray();
    
    //Load the translations file
    LoadTranslations("ultimate-mapchooser.phrases");

#if AUTOUPDATE_ENABLE
    if (LibraryExists("updater"))
    {
        Updater_AddPlugin(UPDATE_URL);
    }
#endif
}


#if AUTOUPDATE_ENABLE
//Called when a new API library is loaded. Used to register UMC auto-updating.
public OnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "updater"))
    {
        Updater_AddPlugin(UPDATE_URL);
    }
}
#endif


//************************************************************************************************//
//                                           GAME EVENTS                                          //
//************************************************************************************************//

//Called after all config files were executed.
public OnConfigsExecuted()
{
    DEBUG_MESSAGE("Executing RTV OnConfigsExecuted")
    
    //We have not completed an RTV.
    rtv_completed = false;
    rtv_enabled = false;
    rtv_inprogress = false;
    
    //UMC hasn't done any votes.
    vote_completed = false;
    
    //Set the amount of time required before players are able to RTV.
    rtv_delaystart = GetConVarFloat(cvar_rtv_delay);
    
    new bool:reloaded = ReloadMapcycle();
    
    //Setup RTV if...
    //    ...the RTV cvar is enabled.
    if (reloaded && GetConVarBool(cvar_rtv_enable))
    {
        rtv_enabled = true;
    
        //Set RTV threshold.
        UpdateRTVThreshold();
        
        //Make timer to activate RTV (player's cannot RTV before this timer finishes).
        MakeRTVTimer();
    }
    
    //Setup vote sounds.
    SetupVoteSounds();
    
    //Grab the name of the current map.
    decl String:mapName[MAP_LENGTH];
    GetCurrentMap(mapName, sizeof(mapName));
    
    decl String:groupName[MAP_LENGTH];
    UMC_GetCurrentMapGroup(groupName, sizeof(groupName));
    
    if (reloaded && StrEqual(groupName, INVALID_GROUP, false))
    {
        KvFindGroupOfMap(umc_mapcycle, mapName, groupName, sizeof(groupName));
    }
    
    //Add the map to all the memory queues.
    new mapmem = GetConVarInt(cvar_rtv_mem);
    new catmem = GetConVarInt(cvar_rtv_catmem);
    AddToMemoryArray(mapName, vote_mem_arr, mapmem);
    AddToMemoryArray(groupName, vote_catmem_arr, (mapmem > catmem) ? mapmem : catmem);
    
    if (reloaded)
        RemovePreviousMapsFromCycle();
}


//Called when a client enters the server. Required for updating the RTV threshold.
public OnClientPutInServer(client)
{
    //Update the RTV threshold if...
    //    ...RTV is enabled.
    if (GetConVarBool(cvar_rtv_enable))
        UpdateRTVThreshold();
}


//Called when a player types in chat. Required to handle user commands.
public Action:OnPlayerChat(client, const String:command[], argc)
{
    //Return immediately if...
    //    ...nothing was typed.
    if (argc == 0) return Plugin_Continue;

    //Get what was typed.
    decl String:text[13];
    GetCmdArg(1, text, sizeof(text));
    
    //Handle RTV client-command if...
    //    ...RTV is enabled AND
    //    ...the client typed a valid RTV command AND
    //    ...the required number of clients for RTV hasn't been reached already AND
    //    ...the client isn't the console.
    if (StrEqual(text, "rtv", false) || StrEqual(text, "rockthevote", false))
    {
        AttemptRTV(client);
    }
    return Plugin_Continue;
}


//Called after a client has left the server. Required for updating the RTV threshold.
public OnClientDisconnect_Post(client)
{
    //Remove this client from people who have seen the extended RTV message.
    rtv_message[client] = false;
    
    new index;
    //Remove the client from the RTV array if...
    //    ...the client is in the array to begin with.
    while ((index = FindValueInArray(rtv_clients, client)) != -1)
        RemoveFromArray(rtv_clients, index);

    //Recalculate the RTV threshold.
    UpdateRTVThreshold();
    
    //Start RTV if...
    //    ...we haven't had an RTV already AND
    //    ...the new amount of players on the server as passed the required threshold.
    if (!rtv_completed && GetArraySize(rtv_clients) >= rtv_threshold)
    {
        CGOPrintToChatAll("\x03[UMC]\x01 %t", "Player Disconnect RTV");
        StartRTV();
    }
}


//Called at the end of a map.
public OnMapEnd()
{
    DEBUG_MESSAGE("Executing RTV OnMapEnd")
    
    //Empty array of clients who have entered RTV.
    ClearArray(rtv_clients);
}


//************************************************************************************************//
//                                            COMMANDS                                            //
//************************************************************************************************//

//sm_rtv or sm_rockthevote
public Action:Command_RTV(client, args)
{
    AttemptRTV(client);
    return Plugin_Handled;
}


//************************************************************************************************//
//                                              SETUP                                             //
//************************************************************************************************//

//Parses the mapcycle file and returns a KV handle representing the mapcycle.
Handle:GetMapcycle()
{
    //Grab the file name from the cvar.
    decl String:filename[PLATFORM_MAX_PATH];
    GetConVarString(cvar_filename, filename, sizeof(filename));
    
    //Get the kv handle from the file.
    new Handle:result = GetKvFromFile(filename, "umc_rotation");
    
    //Log an error and return empty handle if...
    //    ...the mapcycle file failed to parse.
    if (result == INVALID_HANDLE)
    {
        LogError("SETUP: Mapcycle failed to load!");
        return INVALID_HANDLE;
    }
    
    //Success!
    return result;
}


//Sets up the vote sounds.
SetupVoteSounds()
{
    //Grab sound files from cvars.
    GetConVarString(cvar_rtv_startsound, vote_start_sound, sizeof(vote_start_sound));
    GetConVarString(cvar_rtv_endsound, vote_end_sound, sizeof(vote_end_sound));
    GetConVarString(cvar_runoff_sound, runoff_sound, sizeof(runoff_sound));
    
    //Gotta cache 'em all!
    CacheSound(vote_start_sound);
    CacheSound(vote_end_sound);
    CacheSound(runoff_sound);
}


//Reloads the mapcycle. Returns true on success, false on failure.
bool:ReloadMapcycle()
{
    if (umc_mapcycle != INVALID_HANDLE)
    {
        CloseHandle(umc_mapcycle);
        umc_mapcycle = INVALID_HANDLE;
    }
    if (map_kv != INVALID_HANDLE)
    {
        CloseHandle(map_kv);
        map_kv = INVALID_HANDLE;
    }
    umc_mapcycle = GetMapcycle();
    
    return umc_mapcycle != INVALID_HANDLE;
}


//
RemovePreviousMapsFromCycle()
{
    map_kv = CreateKeyValues("umc_rotation");
    KvCopySubkeys(umc_mapcycle, map_kv);
    FilterMapcycleFromArrays(map_kv, vote_mem_arr, vote_catmem_arr, GetConVarInt(cvar_rtv_catmem));
}

//************************************************************************************************//
//                                          CVAR CHANGES                                          //
//************************************************************************************************//

//Called when the cvar to enable RTVs has been changed.
public Handle_RTVChange(Handle:convar, const String:oldVal[], const String:newVal[])
{
    //If the new value is 0, we ignore the change until next map.
    
    //Update (in this case set) the RTV threshold if...
    //    ...the new value of the changed cvar is 1.
    if (StringToInt(newVal) == 1)
        UpdateRTVThreshold();
}


//Called when the number of excluded previous maps from RTVs has changed.
public Handle_RTVMemoryChange(Handle:convar, const String:oldValue[], const String:newValue[])
{
    //Trim the memory array for RTVs.
        //We pass 1 extra to the argument in order to account for the current map, which should
        //always be excluded.
    TrimArray(vote_mem_arr, StringToInt(newValue));
}


//Called when the cvar specifying the required RTV threshold percentage has changed.
public Handle_ThresholdChange(Handle:cvar, const String:oldVal[], const String:newVal[])
{
    //Recalculate the required threshold.
    UpdateRTVThreshold();
    
    //Start an RTV if...
    //    ...the amount of clients who have RTVd is greater than the new RTV threshold.
    if (GetArraySize(rtv_clients) >= rtv_threshold)
        StartRTV();
}


//************************************************************************************************//
//                                          ROCK THE VOTE                                         //
//************************************************************************************************//

//Tries to enter the given client into RTV.
AttemptRTV(client)
{
    //Get the number of clients who have RTV'd.
    new size = GetArraySize(rtv_clients);

    if (!rtv_enabled || !GetConVarBool(cvar_rtv_enable) || size >= rtv_threshold || client == 0)
        return;
        
    decl String:flags[64];
    GetConVarString(cvar_enterflags, flags, sizeof(flags));
    
    if (!ClientHasAdminFlags(client, flags))
    {
        CGOPrintToChat(
            client,
            "\x03[UMC]\x01 %t",
            "No RTV Admin"
        );
        return;
    }
        
    new clients = GetRealClientCount();
    new minPlayers = GetConVarInt(cvar_rtv_minplayers);
    
    DEBUG_MESSAGE("Checking RTV Entry Validity")
    
    //Print a message if...
    //    ...an RTV has already been completed OR
    //    ...a vote has already been completed and RTVs after votes aren't allowed.
    if (rtv_completed || (vote_completed && GetConVarInt(cvar_rtv_postaction) == 1))
    {
        DEBUG_MESSAGE("RTV Already Completed? -- %s", rtv_completed ? "true" : "false")
        DEBUG_MESSAGE("Vote Already Completed? -- %s", vote_completed ? "true" : "false")
        DEBUG_MESSAGE("Change Map On Post? -- %s", GetConVarBool(cvar_rtv_postaction) ? "true" : "false")
        
        CGOPrintToChat(client, "\x03[UMC]\x01 %t", "No RTV Nextmap");
        return;
    }
    //Otherwise, print a message if...
    //    ...the number of players on the server is less than the minimum required to RTV.
    else if (clients < minPlayers)
    {
        CGOPrintToChat(
            client,
            "\x03[UMC]\x01 %t",
            "No RTV Player Count",
                minPlayers - clients
        );
        return;
    }
    //Otherwise, print a message if...
    //    ...it is too early to RTV.
    else if (rtv_delaystart > 0)
    {
        CGOPrintToChat(client, "\x03[UMC]\x01 %t", "No RTV Time", rtv_delaystart);
        return;
    }
    //Otherwise, accept RTV command if...
    //    ...the client hasn't already RTV'd.
    else if (FindValueInArray(rtv_clients, client) == -1)
    {
        //Get the flags for bonus RTV entrance points
        GetConVarString(cvar_enterbonusflags, flags, sizeof(flags));
        
        //Calc the amount of entrance points for this user
        new amt = strlen(flags) > 0 && ClientHasAdminFlags(client, flags)
            ? GetConVarInt(cvar_enterbonusamt)
            : 1;

        //Apply entrance points
        size += amt;
        while (amt-- > 0)
        {
            //Add client to RTV array.
            PushArrayCell(rtv_clients, client);
        }
        
        //Get the name of the client.
        decl String:name[MAX_NAME_LENGTH];
        GetClientName(client, name, sizeof(name));
        
        //Display an RTV message to a client for...
        //    ...each client on the server.
        for (new i = 1; i <= MaxClients; i++)
        {
            if (IsClientInGame(i))
            {
                //Display initial (long) RTV message if...
                //    ...the client hasn't seen it yet.
                if (!rtv_message[i])
                {
                    //Remember that the client has now seen this message.
                    rtv_message[i] = true;
                    CGOPrintToChat(
                        i,
                        "\x03[UMC]\x01 %t %t (%t)",
                        "RTV Entered",
                            name,
                        "RTV Info Msg",
                        "More Required",
                            rtv_threshold - size
                    );
                }
                else //Otherwise, print the standard message.
                {
                    CGOPrintToChat(
                        i,
                        "\x03[UMC]\x01 %t (%t)",
                        "RTV Entered",
                            name,
                        "More Required",
                            rtv_threshold - size
                    );
                }
            }
        }
        
        //Start RTV if...
        //    ...the new size has surpassed the threshold required to RTV.
        if (size >= rtv_threshold)
        {
            //Start the vote if...
            //    ...there isn't one happening already.
            if (!IsVoteInProgress())
            {
                CGOPrintToChatAll("\x03[UMC]\x01 %t", "RTV Start");
                StartRTV();
            }
            else //Otherwise, display a message.
            {
                CGOPrintToChat(client, "\x03[UMC]\x01 %t", "Vote In Progress");
                MakeRetryVoteTimer(StartRTV);
            }
        }
    }
    //Otherwise, display a message to the client if...
    //    ...the client has already RTV'd.
    else if (FindValueInArray(rtv_clients, client) != -1)
    {
        CGOPrintToChat(
            client,
            "\x03[UMC]\x01 %t (%t)",
            "RTV Already Entered",
            "More Required",
                rtv_threshold - size
        );
    }
}


//Creates the RTV timer. While this timer is active, players are not able to RTV.
MakeRTVTimer()
{
    DEBUG_MESSAGE("Enabling RTV; setting rtv_completed flag to false")
    //We are re-enabling RTV at this point.
    rtv_completed = false;
    
    if (rtv_delaystart > 0)
    {
        //Log a message
        LogUMCMessage("RTV will be made available in %.f seconds.", rtv_delaystart);
        
        //Create timer that lasts every second.
        CreateTimer(
            1.0,
            Handle_RTVTimer,
            INVALID_HANDLE,
            TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT
        );
    }
    else
    {
        //rtv_enabled = true;
        LogUMCMessage("RTV is now available.");
    }
}


//Callback for the RTV timer, is called every second the timer is running.
public Action:Handle_RTVTimer(Handle:timer)
{
    //Continue ticking if...
    //    ...there is still time left on the counter.
    if (--rtv_delaystart >= 0.0)
        return Plugin_Continue;

    //If there isn't time left on the counter...
    
    //rtv_enabled = true;
    
    LogUMCMessage("RTV is now available.");
    
    //Stop the timer.
    return Plugin_Stop;
}


//Recalculated the RTV threshold based off of the given playercount.
UpdateRTVThreshold()
{
    decl String:flags[64];
    GetConVarString(cvar_voteflags, flags, sizeof(flags));
    new count = GetClientWithFlagsCount(flags);
    rtv_threshold = (count > 1)
                    ? RoundToCeil(float(count) * GetConVarFloat(cvar_rtv_needed))
                    : 1;
}


//Starts an RTV.
public StartRTV()
{
    LogUMCMessage("Starting RTV.");
    
    //Clear the array of clients who have entered RTV.
    ClearArray(rtv_clients);
    
    DEBUG_MESSAGE("Disabling RTV; setting rtv_completed flag to true")
    rtv_completed = true;
    
    new postAction = GetConVarInt(cvar_rtv_postaction);
    
    //Change the map immediately if...
    //    ...there has already been an end-of-map vote AND
    //    ...the cvar that handles RTV actions after end-of-map votes specifies to change the map.
    if (vote_completed && postAction == 0)
    {
        //Get the next map set by the vote.
        decl String:temp[MAP_LENGTH];
        GetNextMap(temp, sizeof(temp));
        
        LogUMCMessage("End of map vote has already been completed, changing map.");
        
        //Change to it.
        ForceChangeInFive(temp, "RTV");
    }
    //Otherwise, build the RTV vote if...
    //    ...a vote hasn't already been completed.
    else if (!vote_completed || postAction == 2)
    {
        //Do nothing if...
        //    ...there is a vote already in progress.
        if (IsVoteInProgress())
        {
            LogUMCMessage("There is a vote already in progress, cannot start a new vote.");
            MakeRetryVoteTimer(StartRTV);
            return;
        }
        
        rtv_inprogress = true;
        
        decl String:flags[64];
        GetConVarString(cvar_voteflags, flags, sizeof(flags));
        DEBUG_MESSAGE("Vote Flags: \"%s\"", flags)
        
        new clients[MAXPLAYERS+1];
        new numClients;
        GetClientsWithFlags(flags, clients, sizeof(clients), numClients);
        
        //Start the UMC vote.
        new bool:result = UMC_StartVote(
            "core",
            map_kv,                                                     //Mapcycle
            umc_mapcycle,                                               //Complete Mapcycle
            UMC_VoteType:GetConVarInt(cvar_rtv_type),                   //Vote Type (map, group, tiered)
            GetConVarInt(cvar_vote_time),                               //Vote duration
            GetConVarBool(cvar_scramble),                               //Scramble
            vote_start_sound,                                           //Start Sound
            vote_end_sound,                                             //End Sound
            false,                                                      //Extend option
            0.0,                                                        //How long to extend the timelimit by,
            0,                                                          //How much to extend the roundlimit by,
            0,                                                          //How much to extend the fraglimit by,
            GetConVarBool(cvar_rtv_dontchange),                         //Don't Change option
            GetConVarFloat(cvar_vote_threshold),                        //Threshold
            UMC_ChangeMapTime:GetConVarInt(cvar_rtv_changetime),        //Success Action (when to change the map)
            UMC_VoteFailAction:GetConVarInt(cvar_fail_action),          //Fail Action (runoff / nothing)
            GetConVarInt(cvar_runoff),                                  //Max Runoffs
            GetConVarInt(cvar_runoff_max),                              //Max maps in the runoff
            UMC_RunoffFailAction:GetConVarInt(cvar_runoff_fail_action), //Runoff Fail Action
            runoff_sound,                                               //Runoff Sound
            GetConVarBool(cvar_strict_noms),                            //Nomination Strictness
            GetConVarBool(cvar_vote_allowduplicates),                   //Ignore Duplicates
            clients,
            numClients
        );
        
        if (!result)
        {
            LogUMCMessage("Could not start UMC vote.");
        }
    }
}


//************************************************************************************************//
//                                   ULTIMATE MAPCHOOSER EVENTS                                   //
//************************************************************************************************//

//Called when a vote fails, either due to Don't Change or no votes.
public UMC_OnVoteFailed()
{
    if (rtv_inprogress)
    {
        DEBUG_MESSAGE("RTV Failed. Setting completion flags to false.")
        rtv_inprogress = false;
        vote_completed = false;
        rtv_delaystart = GetConVarFloat(cvar_rtv_interval);
        MakeRTVTimer();
    }
}


//Called when UMC has set a next map.
public UMC_OnNextmapSet(Handle:kv, const String:map[], const String:group[], const String:display[])
{
    DEBUG_MESSAGE("Next Map has been set by UMC. Setting vote_completion flag to true")
    vote_completed = true;
    rtv_inprogress = false;
}


//Called when UMC requests that the mapcycle should be reloaded.
public UMC_RequestReloadMapcycle()
{
    if (!ReloadMapcycle())
        rtv_enabled = false;
    else
        RemovePreviousMapsFromCycle();
}


//Called when UMC requests that the mapcycle is printed to the console.
public UMC_DisplayMapCycle(client, bool:filtered)
{
    PrintToConsole(client, "Module: Rock The Vote");
    if (filtered)
    {
        new Handle:filteredMapcycle = UMC_FilterMapcycle(
            map_kv, umc_mapcycle, false, true
        );
        PrintKvToConsole(filteredMapcycle, client);
        CloseHandle(filteredMapcycle);
    }
    else
    {
        PrintKvToConsole(umc_mapcycle, client);
    }
}

Инклуд
C-подобный:
//Header file for Ultimate Mapchooser v3.0 by Steell.
#if defined _umc_core_included
    #endinput
#endif
#define _umc_core_included

#pragma semicolon 1

#define PL_VERSION "3.4.6-dev"
#define UMC_DEBUG 1
#define AUTOUPDATE_ENABLE 0
#define AUTOUPDATE_DEV 0

//SourceMod Malarky
public SharedPlugin:__pl_umccore =
{
    name = "umccore",
    file = "umc-core.smx",
#if defined REQUIRE_PLUGIN
    required = 1,
#else
    required = 0,
#endif
};

public __pl_umccore_SetNTVOptional()
{
    MarkNativeAsOptional("UMC_AddWeightModifier");
    MarkNativeAsOptional("UMC_StartVote");
    MarkNativeAsOptional("UMC_GetCurrentMapGroup");
    MarkNativeAsOptional("UMC_GetRandomMap");
    MarkNativeAsOptional("UMC_SetNextMap");
    MarkNativeAsOptional("UMC_IsMapNominated");
    MarkNativeAsOptional("UMC_NominateMap");
    MarkNativeAsOptional("UMC_CreateValidMapArray");
    MarkNativeAsOptional("UMC_CreateValidMapGroupArray");
    MarkNativeAsOptional("UMC_IsMapValid");
    //MarkNativeAsOptional("UMC_IsGroupValid");
    MarkNativeAsOptional("UMC_FilterMapcycle");
    MarkNativeAsOptional("UMC_IsVoteInProgress");
    MarkNativeAsOptional("UMC_StopVote");
    MarkNativeAsOptional("UMC_RegisterVoteManager");
    MarkNativeAsOptional("UMC_UnregisterVoteManager");
    MarkNativeAsOptional("UMC_VoteManagerVoteCompleted");
    MarkNativeAsOptional("UMC_VoteManagerVoteCancelled");
    MarkNativeAsOptional("UMC_VoteManagerClientVoted");
    MarkNativeAsOptional("UMC_FormatDisplayString");
    MarkNativeAsOptional("UMC_IsNewVoteAllowed");
}

#define INVALID_GROUP ""
#define MAP_LENGTH PLATFORM_MAX_PATH

#define MAP_TRIE_MAP_KEY   "map"
#define MAP_TRIE_GROUP_KEY "group"

#define DONT_CHANGE_OPTION "?DontChange?"
#define EXTEND_MAP_OPTION "?Extend?"

enum UMC_VoteType
{
    VoteType_Map = 0,
    VoteType_Group,
    VoteType_Tier,
};

enum UMC_ChangeMapTime
{
    ChangeMapTime_Now = 0,
    ChangeMapTime_RoundEnd,
    ChangeMapTime_MapEnd,
};

enum UMC_VoteFailAction
{
    VoteFailAction_Nothing = 0,
    VoteFailAction_Runoff,
};

enum UMC_RunoffFailAction
{
    RunoffFailAction_Nothing = 0,
    RunoffFailAction_Accept,
};

enum UMC_VoteResponse
{
    VoteResponse_Success = 0,
    VoteResponse_Runoff,
    VoteResponse_Tiered,
    VoteResponse_Fail,
};


/**
 * Called when a nomination was removed from UMC.
 *
 * @param map  The name of the map
 * @param client    The client that the nomination belonged to.
 *
 * @noreturn
 */
//Commented out to compile on SourceMod 1.7 or newer
//forward OnNominationRemoved(const String:map[], client);


/**
 * Called when a map's weight is being reweighted. Allows plugin to modify the
 * weight by calling UMC_AddWeightModifier.
 *
 * @param kv    The mapcycle being used.
 * @param map   The name of the map we're reweighting.
 * @param group The group that the map belongs to.
 * @noreturn
 */
forward UMC_OnReweightMap(Handle:kv, const String:map[], const String:group[]);


/**
 * Called when a group's weight is being reweighted.
 *
 * @param kv    The mapcycle being used
 * @param group The group being reweighted.
 *
 * @noreturn
 */
forward UMC_OnReweightGroup(Handle:kv, const String:group[]);


/**
 * Adds a MapWeightModifier to UMC's internal modifier list.
 *
 * @param amt  Amount to modify the currently weighted map's weight with.
 *
 * @noreturn
 */
native UMC_AddWeightModifier(Float:amt);


/**
 * Called when a map is being considered for exclusion.
 *
 * @param kv    The mapcycle being used.
 * @param map   The name of the map being considered.
 * @param group The group that the map belongs to.
 * @param isNomination  Specifies whether this is a nominated map or not.
 * @param forMapChange  Whether or not this map will be used this game frame for a mapchange.
 *
 * @return  Plugin_Continue to keep the map, Plugin_Stop to exclude the map.
 */
forward Action:UMC_OnDetermineMapExclude(Handle:kv, const String:map[], const String:group[],
    bool:isNomination, bool:forMapChange);
 
 
/**
 * Starts a UMC map vote.
 *
 * @param id
 * @param mapcycle  The mapcycle to use for the vote.
 * @param originalMapcycle  Original mapcycle, contains all information the user has defined.
 * @param type  The type of vote.
 * @param time  How long the vote should remain acive (in seconds).
 * @param scramble  Determines if the vote menu is scrambled.
 * @param startSound    Sound to be played when the vote starts (must be precached already).
 * @param endSound      Sound to be played when the vote ends (must be precached already).
 * @param extendOption  Determines if an "Extend Map" option is displayed.
 * @param timestep       How long to extend the time limit.
 * @param roundstep      How long to extend the round limit.
 * @param fragstep       How long to extend the frag limit.
 * @param dontChangeOption  Determines if a "Don't Change" option is displayed.
 * @param threshold Percentage winning vote must exceed in order for the vote to change the map.
 * @param successAction Action to be taken if the vote is successful (threshold was exceeded).
 * @param failAction    Action to be taken if the vote is unsuccessful (threshold wasn't exceeded).
 * @param maxRunoffs    Maximum amount of runoffs that will be performed. 0 = no max
 * @param maxRunoffMaps Maximum amount of maps that appear in runoffs. 0 = no max
 * @param runoffFailAction  Action to be taken should the maximum amount of runoffs is reached and there is still no winning map.
 * @param runoffSound   Sound to be played when the runoff vote starts (must be precached already).
 * @param nominationStrictness  Specifies how nominations should be handled.
 * @param allowDuplicates   Determines if the same map (in different groups) can appear in the vote.
 * @param clients   String of flags required for players to see the vote.
 * @param numClients    Number of clients to read from clients array.
 * @param runExclusion  Determines if UMC will filter the mapcycle through the Map Exclusion system.
 *
 * @return True on success, false on failure.
 */
native bool:UMC_StartVote(const String:id[], Handle:mapcycle, Handle:originalMapcycle,
    UMC_VoteType:type, time, bool:scramble, const String:startSound[], const String:endSound[],
    bool:extendOption, Float:timestep, roundstep, fragstep, bool:dontChangeOption, Float:threshold,
    UMC_ChangeMapTime:successAction, UMC_VoteFailAction:failAction, maxRunoffs, maxRunoffMaps,
    UMC_RunoffFailAction:runoffFailAction, const String:runoffSound[], bool:nominationStrictness,
    bool:allowDuplicates, const clients[], numClients, bool:runExclusion=true);
    
    
/**
 * Fetches a random map from a given map group in a given mapcycle.
 *
 * @param mapcycle  The mapcycle to use for the vote.
 * @param originalMapcycle  Original mapcycle, contains all information the user has defined.
 * @param group Group to look for a map in. If INVALID_GROUP then it will look in all groups.
 * @param buffer    Buffer to store the fetched map in.
 * @param size  Max size of the buffer.
 * @param groupBuffer   Buffer to store the group of the map in.
 * @param gBufferSize   Max size of the group buffer.
 * @param isNomination Whether or not this map should be treated as a nomination.
 * @param forMapChange Whether or not this map will be used this game frame for a mapchange.
 *
 * @return True on success, false on failure.
 */
native bool:UMC_GetRandomMap(Handle:mapcycle, Handle:originalMapcycle, const String:group[],
                             String:buffer[], size, String:groupBuffer[], gBufferSize,
                             bool:isNomination, bool:forMapChange);
                            
                            
/**
 * Sets the next map and map group for UMC.
 *
 * @param mapcycle  Mapcycle containing the map and map group. Should contain all information the user has defined.
 * @param map   The name of the map
 * @param group The name of the map group containing the map.
 * @param when  When the map should be changed to the set next map.
 *
 * @noreturn
 */
native UMC_SetNextMap(Handle:mapcycle, const String:map[], const String:group[],
                      UMC_ChangeMapTime:when);


/**
 * Determines if the given map from the given group is nominated.
 *
 * @param map   The name of the map.
 * @param group The name of the map group containing the map.
 *
 * @return True if the map is nominated, false otherwise.
 */
native bool:UMC_IsMapNominated(const String:map[], const String:group[]);


/**
 * Nominates a map for UMC.
 *
 * @param mapcycle  Mapcycle containing the nomination. Should contain all information the user has defined.
 * @param map   The name of the map.
 * @param group The name of the map group containing the map.
 * @param client    The client who is nominating this map.
 * @param nominationGroup   The group which this nomination should be associated with. If INVALID_GROUP, it will be associated with the given group.
 *
 * @return True on success, false otherwise.
 */
native bool:UMC_NominateMap(Handle:mapcycle, const String:map[], const String:group[], client,
                            const String:nominationGroup[]=INVALID_HANDLE);
 

/**
 * Determines if the given group is valid (has at least 1 valid map in it) for the given mapcycle.
 *
 * @param mapcycle  Mapcycle containing the group. Should contain all information the user has defined.
 * @param group The group to be checked.
 *
 * @return True if the group is valid, false otherwise.
 */
//native bool:UMC_IsGroupValid(Handle:mapcycle, const String:group[]);


/**
 * Determines if the given map is valid for the given mapcycle.
 *
 * @param mapcycle  Mapcycle containing the map. Should contain all information the user has defined.
 * @param map   The name of the map.
 * @param group The name of the group containing the map.
 * @param isNom
 * @param forMapChange
 *
 * @return True if the map is valid, false otherwise.
 */
native bool:UMC_IsMapValid(Handle:mapcycle, const String:map[], const String:group[], bool:isNom,
                           bool:forMapChange);


/**
 * Returns a copy of the given mapcycle with all invalid maps and groups filtered out.
 *
 * @param mapcycle  Mapcycle to filter. Should contain all information the user has defined.
 * @param isNomination  Are we filtering for nomination?
 * @param forMapChange  Are we filtering for maps we will be using this frame for a map change?
 *
 * @return Handle to the new mapcycle KV.
 */
native Handle:UMC_FilterMapcycle(Handle:mapcycle, Handle:originalMapcycle, bool:isNomination,
                                 bool:forMapChange);


/**
 * Returns an array consisting of all the valid maps in the given mapcycle.
 *
 * @param mapcycle  Mapcycle to look for maps in.
 * @param originalMapcycle  Original mapcycle, contains all information that the user has defined.
 * @param group Group to select maps from. If INVALID_GROUP, all groups are looked in.
 * @param isNom Are these maps to be considered nominations?
 * @param forMapChange  Are any of these maps going to be used to change the map this game frame?
 *
 * @return Handle to the new array containing all of the valid map tries.
 */
native Handle:UMC_CreateValidMapArray(Handle:mapcycle, Handle:originalMapcycle,
                                      const String:group[], bool:isNom, bool:forMapChange);


/**
 * Returns an array consisting of all the valid groups in the given mapcycle. A valid group is a
 * group with 1 or more valid maps in it.
 *
 * @param kv    Mapcycle to look for maps in.
 * @param originalMapcycle  Original mapcycle, contains all information that the user has defined.
 * @param isNom Are the maps in these groups to be considered nominations?
 * @param forMapChange  Are any of the maps in these groups going to be used to change the map this game frame?
 *
 * @return Handle to the new array containing all of the valid map groups.
 */
native Handle:UMC_CreateValidMapGroupArray(Handle:kv, Handle:originalMapcycle, bool:isNom,
                                           bool:forMapChange);


/**
 * Called when has extended the map.
 *
 * @noreturn
 */
forward UMC_OnMapExtended();


/**
 * Called when a vote has failed. This can be due to the Don't Change option winning the vote or the
 * winning map not winning by a high enough percentage.
 *
 * @noreturn
 */
forward UMC_OnVoteFailed(); //UMC_FailReason:reason);


/**
 * Called immediately after UMC has set a next map.
 *
 * @param kv    The mapcycle used for the vote.
 * @param map   The name of the winning map.
 * @param group The group of the winning map.
 * @param display    The "display name" of the winning map.
 *
 * @noreturn
 */
forward UMC_OnNextmapSet(Handle:kv, const String:map[], const String:group[],
                         const String:display[]);


/**
 * Called when the "sm_umc_reload" command is used. If your plugin has its own mapcycle, you should
 * reload it in this forward.
 *
 * @noreturn
 */
forward UMC_RequestReloadMapcycle();


/**
 * Retrieves the name of the current map group.
 *
 * @param buffer    Buffer to store the group name in.
 * @param size  Maximum length of the buffer.
 *
 * @noreturn
 */
native UMC_GetCurrentMapGroup(String:buffer[], size);


/**
 * Determines if UMC currently has a vote in progress.
 *
 * @param id
 *
 * @return True if vote is in progress, false otherwise.
 */
native bool:UMC_IsVoteInProgress(const String:id[]);


/**
 * Stops a UMC vote that is currently in progress.
 *
 * @param id
 *
 * @return True on success, false otherwise.
 */
native bool:UMC_StopVote(const String:id[]);


/**
 * Callback for when a vote is to be started by UMC. Note that all Handles will be freed after the
 * call is complete, so if you want to store them, make sure to clone them first with CloneHandle.
 *
 * @param duration
 * @param vote_items
 * @param clients
 * @param extend
 * @param dontChange
 * @param startSound
 *
 * @return Plugin_Continue on success, Plugin_Stop to prevent the vote.
 */
functag UMC_VoteHandler Action:public(duration, Handle:vote_items, const clients[], numClients,
                                      const String:startSound[]);

/**
 *
 */
functag UMC_VoteCancelledHandler public();

functag UMC_VoteInProgressHandler bool:public();

/**
 * Registers a new VoteManager for UMC votes.
 *
 * @param id    Unique string identifier for this VoteManager.
 * @param callback  Function to be called when a vote is ready to be started.
 * @param cancelCallback  Function to be called when a vote is cancelled.
 * @param progressCallback  Function to be called to check if a vote is in progress.  Set to INVALID_FUNCTION to use IsVoteInProgress()
 *
 * @noreturn
 */
native UMC_RegisterVoteManager(const String:id[], UMC_VoteHandler:mapCallback,
                               UMC_VoteHandler:groupCallback,
                               UMC_VoteCancelledHandler:cancelCallback,
                               UMC_VoteInProgressHandler:progressCallback=INVALID_FUNCTION);


/**
 *
 */
functag UMC_VoteResponseHandler public(UMC_VoteResponse:response, const String:param[]);

/**
 * Notifies UMC that the current vote has completed.
 *
 * @param id Id of the Vote Manager which has completed.
 * @param voteOptions adt_array of options in the vote
 * @param callback Callback for processing the results of the vote.
 *
 * @noreturn
 */
native UMC_VoteManagerVoteCompleted(const String:id[], Handle:voteOptions,
                                    UMC_VoteResponseHandler:callback);


/**
 * Notifies UMC that the current vote has been cancelled by the vote Handler. This will cause the
 * UMC_VoteCancelledHandler provided to UMC_RegisterVoteManager to be called as well.
 *
 * @param id ID of the Vote Manager to cancel.
 */
native UMC_VoteManagerVoteCancelled(const String:id[]);
                                    

/**
 * Unregisters a Vote Manager from UMC.
 *
 * @param id ID of the Vote Manager to unregister.
 */
native UMC_UnregisterVoteManager(const String:id[]);


/**
 * Called when UMC requests a Map Cycle to be displayed.
 *
 * @param client Client to display the Map Cycle to.
 * @param filtered If true, it is requested that mapcycle only shows maps which pass UMC exclusion.
 */
forward UMC_DisplayMapCycle(client, bool:filtered);


/**
 *
 */
forward UMC_VoteStarted(const String:voteManagerId[], const clients[], numClients,
                        Handle:voteOptions);


/**
 *
 */
forward UMC_VoteEnded(const String:voteManagerId[], UMC_VoteResponse:reason);


/**
 *
 */
forward UMC_ClientVoted(const String:voteManagerId[], client, Handle:option);


/**
 *
 */
native UMC_VoteManagerClientVoted(const String:id[], client, Handle:option);


/**
 *
 */
forward UMC_OnFormatTemplateString(String:template[], maxlen, Handle:kv, const String:map[],
                                   const String:group[]);


/**
 *
 */
native UMC_FormatDisplayString(String:display[], maxlen, Handle:mapcycle, const String:map[],
                               const String:group[]);

/**
 * Is a new vote allowed on this vote manager?
 *
 * @param id  ID of the Vote Manager to check. Defaults to "core".
 * @return  True if we can start a new vote, false otherwise.
 */
native bool:UMC_IsNewVoteAllowed(const String:id[]="core");

Ошибки компилятора
C-подобный:
//// umc-rockthevote.sp
//
// ******\include\umc-core.inc(407) : error 418: deprecated syntax; see https://wiki.alliedmods.net/SourcePawn_Transitional_Syntax#Typedefs
// ******\include\umc-core.inc(407) : error 001: expected token: "-identifier-", but found "public"
// ******\include\umc-core.inc(407) : error 122: expected type expression
 

Grey83

не пишу плагины с весны 2022
Сообщения
8,555
Реакции
5,035

Вложения

  • изображение_2022-10-20_201319.png
    изображение_2022-10-20_201319.png
    12.5 КБ · Просмотры: 35
Сверху Снизу