VCMP Typewriter Animated Announcement System GUI

Started by Mohamed Boubekri, Mar 11, 2026, 09:06 PM

Previous topic - Next topic

Mohamed Boubekri

Hey everyone, just a simple animated announcement system, type word by word.


Server-Side

enum StreamType
{
    Announce = 0x1
}
function onPlayerCommand( player, cmd, text )
{
if (cmd=="announce")
{
if (!text) return MessagePlayer("[#ff0000]Please use /announce <text>",player);
 for(local i = 0; i < GetMaxPlayers(); ++i)
 {
    local plr = FindPlayer(i);
    if(!plr) continue;

    SendDataToClient(plr, StreamType.Announce, text);
 }
}
else MessagePlayer("[#ff0000]Invalid cmd",player);
}
function onClientScriptData( player )
{
    local stream = Stream.ReadByte();
    switch ( stream )
    {
        case StreamType.Announce:
          for(local i = 0; i < GetMaxPlayers(); ++i)
          {
            local plr = FindPlayer(i);
            if(!plr) continue;
 
            PlaySound(plr.World, 50003, plr.Pos);
          }
    break;
    }

function SendDataToClient( player, ... )
{
    if( vargv[0] )
    {
        local    byte = vargv[0],
                len = vargv.len();
               
        if( 1 > len ) devprint( "ToClent <" + byte + "> No params specified." );
        else
        {
            Stream.StartWrite();
            Stream.WriteByte( byte );

            for( local i = 1; i < len; i++ )
            {
                switch( typeof( vargv[i] ) )
                {
                    case "integer": Stream.WriteInt( vargv[i] ); break;
                    case "string": Stream.WriteString( vargv[i] ); break;
                    case "float": Stream.WriteFloat( vargv[i] ); break;
                }
            }
           
            if( player == null ) Stream.SendStream( null );
            else if( typeof( player ) == "instance" ) Stream.SendStream( player );
            else devprint( "ToClient <" + byte + "> Player is not online." );
        }
    }
    else devprint( "ToClient: Even the byte wasn't specified..." );
}

Client-Side

enum StreamType
{
    Announce = 0x1
}

screenX <- GUI.GetScreenSize().X;
screenY <- GUI.GetScreenSize().Y;

local announceQueue = [0,1], typedChars = 0, typingTimer, announceSprite, announceLabel;

Timer <- {
 Timers = {}

 function Create(environment, listener, interval, repeat, ...)
 {
  vargv.insert(0, environment);

  local TimerInfo = {
  Environment = environment,
  Listener = listener,
  Interval = interval,
  Repeat = repeat,
  Args = vargv,
  LastCall = Script.GetTicks(),
  CallCount = 0
  };

  local hash = split(TimerInfo.tostring(), ":")[1].slice(3, -1).tointeger(16);
  Timers.rawset(hash, TimerInfo);

  return hash;
 }

 function Destroy(hash)
 {
  if (Timers.rawin(hash))
  {
  Timers.rawdelete(hash);
  }
 }

 function Exists(hash)
 {
  return Timers.rawin(hash);
 }

 function Fetch(hash)
 {
  return Timers.rawget(hash);
 }

 function Clear()
 {
  Timers.clear();
 }

 function Process()
 {
  local CurrTime = Script.GetTicks();

  foreach (hash, tm in Timers)
  {
  if (tm != null)
  {
    if (CurrTime - tm.LastCall >= tm.Interval)
    {
    tm.CallCount++;
    tm.LastCall = CurrTime;

    tm.Listener.pacall(tm.Args);

    if (tm.Repeat != 0 && tm.CallCount >= tm.Repeat)
      Timers.rawdelete(hash);
    }
  }
  }
 }
};

function Script::ScriptProcess()
{
 Timer.Process();
}

function Server::ServerData( stream )
{
    local type = stream.ReadByte();

    switch( type )
    {
        case StreamType.Announce:
        {
            local announceText = stream.ReadString();

            if (typingTimer == null)
            {
                if ( announceText.len() > 90 )
                    return Console.Print("You can't announce more than 90 characters");

                typingTimer = "lock";

                Console.Print("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");

                announceQueue.insert(0,announceText);

                local centerY = screenY / 2, startY = centerY * 0.3;

                announceSprite = GUISprite( "ann.png", VectorScreen( screenX / 2, startY ) );
                announceSprite.Size = VectorScreen( screenX * 0.05, screenY * 0.06 );
                announceSprite.Alpha = 255;

                SendDataToServer(StreamType.Announce);   

                Timer.Create( this, MoveAnnouncementUp, 50, 15 );       
                Timer.Create( this, CreateAnnouncementLabel, 750, 1 );
                Timer.Create( this, StartHorizontalMovement, 750, 1 );
            }
            else Console.Print("Wait until the first announcement disappears");
        }
        break;
    }
}

function MoveAnnouncementUp()
{
    local offset = screenY * -0.009;
    announceSprite.Pos.Y += offset;   
}

function StartHorizontalMovement()
{
    Timer.Create( this, MoveAnnouncementLeft, 40, 15 );
}

function MoveAnnouncementLeft()
{
    local offset = screenX * -0.032;
    announceSprite.Pos.X += offset;   
}

function CreateAnnouncementLabel()
{
    announceLabel = GUILabel(VectorScreen( screenX * 0.047, screenY * 0.013 ), Colour( 0, 0, 0 ), "");
    announceLabel.FontSize = screenY * 0.02;

    announceSprite.AddChild(announceLabel);

    typingTimer = Timer.Create( this, TypewriterEffect, 90, announceQueue[0].len() );
    Timer.Create( this, DestroyAnnouncement, 10000, 1 );
}

function TypewriterEffect()
{
    local pos = typedChars += 1, prev = pos-1, text = announceQueue[0];
    announceLabel.Text += text.slice(prev,pos);

    local shakeX = screenX * 0.002; // shake effect when typing
    local shakeY = screenY * 0.002;

    if (pos % 2 == 0)
    {
        announceSprite.Pos.X += shakeX;
        announceSprite.Pos.Y += shakeY;
    }
    else
    {
        announceSprite.Pos.X -= shakeX;
        announceSprite.Pos.Y -= shakeY;
    }
}

function DestroyAnnouncement()
{
 announceQueue.clear();
 announceLabel = null;
 announceSprite = null;
 typedChars = 0;

 Timer.Destroy(typingTimer);
 typingTimer = null;
}

function SendDataToServer( ... )
{
    if( vargv[0] )
    {
        local byte = vargv[0],
        len = vargv.len();
               
        if( 1 > len ) Console.Print( "ToClient <" + byte + "> No params specified." );
        else
        {
            local stream = Stream();
            stream.WriteByte( byte );

            for( local i = 1; i < len; i++ )
            {
                switch( typeof( vargv[i] ) )
                {
                    case "integer": stream.WriteInt( vargv[i] ); break;
                    case "string": stream.WriteString( vargv[i] ); break;
                    case "float": stream.WriteFloat( vargv[i] ); break;
                }
            }
           
            Server.SendData( stream );
        }
    }
    else Console.Print( "ToClient: Not even the byte was specified..." );
}

Download sound and sprite from here and put them in store folder. Enjoy !

Credits:
ysc3839 - Client Timers
| What now ? | Not yet ! |
Morrocan:- [ 🇲🇦 ].