Jump to content

Mod Examples: Difference between revisions

From fCraft Wiki
Minor edit to force page regen
 
(15 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This page demonstrates examples of modifying and extending fCraft.
This page demonstrates examples of modifying and extending fCraft via source code modification.
 
==Hooking up custom code==
{{warning|This method of modding fCraft's code directly will be obsoleted if/when the plugin system is done}}
Until the proper Plugin API is finished, I recommend minimizing edits to fCraft's existing files, and trying to contain all custom code in your own namespace/files. You will need to make at least one change to Server.cs though, to add your code to run on startup. Use that very first call to subscribe to various events. For detailed information about the startup procedure, see [[API: Startup]].
<syntaxhighlight lang="csharp">
// Add this to beginning of Server.InitServer
ClassWithMyMods.Init();
 
// Meanwhile, in ClassWithMyMods:
// Use this to hook up events. Dont do anything else quite yet.
public static void Init(){
    Server.Initialized += MyServerInitializedHandler;
    Server.Started += MyServerStartedHandler;
    // etc events
}
 
// This is where you can go ahead and register custom commands, brushes, etc
// Server.Initialized is invoked after everything else in Server.InitServer() is done
static void MyServerInitializedHandler( object sender, EventArgs e ){
    CommandManager.RegisterCustomCommand( CdMyCommandStuff );
}
 
// This is where you can start timers, open up ports, manipulate world list, etc
// Server.Started is invoked just after server fully finished its startup routine
static void MyServerStartedHandler( object sender, EventArgs e ){
    Scheduler.NewTask( MyTask ).RunForever( myTaskInterval );
}
</syntaxhighlight>


==Calling custom code at regular intervals==
==Calling custom code at regular intervals==
Line 15: Line 43:
}</syntaxhighlight>
}</syntaxhighlight>
Besides '''RunForever(TimeSpan)''', SchedulerTask has many '''Run*''' methods for running a task once, several times, or forever - with different delays and at different intervals.
Besides '''RunForever(TimeSpan)''', SchedulerTask has many '''Run*''' methods for running a task once, several times, or forever - with different delays and at different intervals.
==Reacting to server shutdown==
There are two events associated with shutdown: '''Server.ShutdownBegan''' and '''Server.ShutdownEnded'''. Both supply a '''ShutdownEventArgs''' object that provides information about the shutdown parameters. For more information about how server shutdown works, see [[API: Shutdown]].
<syntaxhighlight lang="csharp">
// Subscribing to the event
Server.ShutdownBegan += OnShutdownBegan;
// Event handler
void OnShutdownBegan( object sender, ShutdownEventArgs e ){
    Console.Write( "The end is near!" );
    if( e.Restart ){
        Console.Write( "But we will be back shortly." );
    }
}
</syntaxhighlight>




Line 41: Line 53:


// Event handler
// Event handler
void OnPlayerReady( object sender, PlayerEventArgs e ){
void OnPlayerReady( object sender, PlayerEventArgs e ) {
     // if connecting player is of highest rank (presumably owner)
     // if connecting player is of highest rank (presumably owner)
     if( e.Player.Rank == RankManager.HighestRank ){
     if( e.Player.Info.Rank == RankManager.HighestRank ) {
         // Get a list of players who can see them join
         // Get a list of players who can see them join
         // (to avoid accidentally revealing hidden owners)
         // (to avoid accidentally revealing hidden owners)
Line 51: Line 63:
                               e.Player.Name );
                               e.Player.Name );


    // or, if player's name contains the word "grief"
        // or, if player's name contains the word "grief"
     }else if( e.Player.Name.Contains( "grief", StringComparison.OrdinalIgnoreCase ) ){
     } else if( e.Player.Name.Contains( "grief", StringComparison.OrdinalIgnoreCase ) ) {
         // Spam everyone some more!
         // Spam everyone some more!
         Server.Message( "&YLOOK OUT! GRIEFERS ARE COMING!" );
         Server.Message( "&YLOOK OUT! GRIEFERS ARE COMING!" );
Line 58: Line 70:
}
}
</syntaxhighlight>
</syntaxhighlight>


==Adding a new command==
==Adding a new command==
Line 74: Line 87:


// Command handler - must implement CommandHandler delegate
// Command handler - must implement CommandHandler delegate
void BleepHandler( Player player, Command cmd ){
void BleepHandler( Player player, CommandReader cmd ) {
     int numberOfBleeps = 10;
     int numberOfBleeps = 10;


     // if a param is given
     // if a param is given
     if( cmd.HasNext )
     if( cmd.HasNext ) {
         // try to parse it as a number, and save to numberOfBleeps
         // try to parse it as a number, and save to numberOfBleeps
         if( !cmd.NextInt( out numberOfBleeps ) ){
         if( !cmd.NextInt( out numberOfBleeps ) ) {
             // if that fails (not a number), print usage
             // if that fails (not a number), print usage
             CdBleep.PrintUsage( player );
             CdBleep.PrintUsage( player );
Line 88: Line 101:


     // check the range
     // check the range
     if( numberOfBleeps<1 || numberOfBleeps>32 ){
     if( numberOfBleeps < 1 || numberOfBleeps > 32 ) {
         player.Message( "Specify between 1 and 32 bleeps." );
         player.Message( "Specify between 1 and 32 bleeps." );
         return;
         return;
     }
     }


     for( int i=0; i<numberOfBleeps; i++ ){
     for( int i = 0; i < numberOfBleeps; i++ ) {
         Chat.SendGlobal( player, "bleep" );
         Chat.SendGlobal( player, "bleep" );
     }
     }
Line 101: Line 114:
CommandManager.RegisterCustomCommand( CdBleep );
CommandManager.RegisterCustomCommand( CdBleep );
</syntaxhighlight>
</syntaxhighlight>
==Reacting to server shutdown==
There are two events associated with shutdown: '''Server.ShutdownBegan''' and '''Server.ShutdownEnded'''. Both supply a '''ShutdownEventArgs''' object that provides information about the shutdown parameters. For more information about how server shutdown works, see [[API: Shutdown]].
<syntaxhighlight lang="csharp">
// Subscribing to the event
Server.ShutdownBegan += OnShutdownBegan;
// Event handler
void OnShutdownBegan( object sender, ShutdownEventArgs e ){
    Console.Write( "The end is near!" );
    if( e.Restart ){
        Console.Write( "But we will be back shortly." );
    }
}
</syntaxhighlight>
==Starting selections==
If you need the player to mark one or more coordinates for you (for example for a draw command), you'll be using the Selection API. Call player.StartSelection(...) to initiate a selection, and return control to fCraft. If/when player marks the required number of blocks, your callback will be called. If player cancels, disconnects, or starts another selection, then your callback will not be invoked.
Also note that while a player is in selection mode <code>Player.PlacingBlock</code> and <code>Player.PlacedBlock</code> events are not fired for clicks. <code>Player.Clicking</code> and <code>Player.Clicked</code> events are still fired.
<syntaxhighlight lang="csharp">
// This is where you start/set up the selection.
// You can call SelectionStart() from anywhere, really. Doesn't need to be a command handler.
static void ClickityHandler(Player player, CommandReader cmd) {
    // Random number to demonstrate argument passing. You don't need it.
    int randomNumber = new Random().Next();
   
    // Let player know that a selection started
    player.Message("Click anything! Random number is {0}", randomNumber);
    // Start it!
    player.SelectionStart(1, // number of blocks to click (must be at least 1)
                          ClickitySelectionCallback, // our callback function
                          randomNumber,              // an argument to pass to the callback (can be anything or null)
                          new Permission[0]);        // you could omit this completely
   
}
// The callback function. fCraft will call it automatically when the last block is marked.
static void ClickitySelectionCallback(Player player, Vector3I[] marks, object tag) {
    // Retrieve the parameter
    int randomNumber = (int)tag;
    // Marked coordinates are in an array, in same order that they were clicked/marked.
    // You can assume that it has at least as many marks as you asked for (in this case, at least 1)
    Vector3I coord = marks[0];
    // Let player know that it finished
    player.Message("You clicked at coordinates {0}, and random number was {1}", coord, randomNumber);
}
</syntaxhighlight>
Related properties: <code>Player.DisableClickToMark</code>, <code>Player.IsMakingSelection</code>, <code>Player.SelectionMarkCount</code>, <code>Player.SelectionMarksExpected</code>, <code>Player.IsRepeatingSelection</code>
Related methods: <code>Player.SelectionAddMark(...)</code>, <code>Player.SelectionExecute()</code>, <code>Player.SelectionResetMarks()</code>, <code>Player.SelectionCancel()</code>
==Adding custom permissions==
To add a new permission:
* Add a new constant to <code>Permissions</code> enum (<i>fCraft/Player/Permissions.cs</i>)
* Give the new permission to some default ranks (at least to the highest rank), in <code>Config.DefineDefaultRanks()</code> (<i>fCraft/System/Config.cs</i>)
* Add a tooltip explaining the permission in ConfigGUI's <code>MainForm.FillToolTipsRanks()</code> (<i>ConfigGUI/MainForm.ToolTip.cs</i>)
To add a per-rank limit to the new permission:
* For every rank that has this permission, add a default limit to <code>Config.DefineDefaultRanks()</code> (<i>fCraft/System/Config.cs</i>)
* Add a way to set the limit in ConfigGUI's <code>MainForm.FillPermissionLimitBoxes()</code> (<i>ConfigGUI/MainForm.cs</i>)
* Add a tooltip explaining the permission limit in <code>MainForm.FillToolTipsRanks()</code> (<i>ConfigGUI/MainForm.ToolTip.cs</i>)
[[Category:Dev]]
[[Category:Dev]]

Latest revision as of 13:26, 9 August 2018

This page demonstrates examples of modifying and extending fCraft via source code modification.

Hooking up custom code

This method of modding fCraft's code directly will be obsoleted if/when the plugin system is done

Until the proper Plugin API is finished, I recommend minimizing edits to fCraft's existing files, and trying to contain all custom code in your own namespace/files. You will need to make at least one change to Server.cs though, to add your code to run on startup. Use that very first call to subscribe to various events. For detailed information about the startup procedure, see API: Startup.

// Add this to beginning of Server.InitServer
ClassWithMyMods.Init();

// Meanwhile, in ClassWithMyMods:
// Use this to hook up events. Dont do anything else quite yet.
public static void Init(){
    Server.Initialized += MyServerInitializedHandler;
    Server.Started += MyServerStartedHandler;
    // etc events
}

// This is where you can go ahead and register custom commands, brushes, etc
// Server.Initialized is invoked after everything else in Server.InitServer() is done
static void MyServerInitializedHandler( object sender, EventArgs e ){
    CommandManager.RegisterCustomCommand( CdMyCommandStuff );
}

// This is where you can start timers, open up ports, manipulate world list, etc
// Server.Started is invoked just after server fully finished its startup routine
static void MyServerStartedHandler( object sender, EventArgs e ){
    Scheduler.NewTask( MyTask ).RunForever( myTaskInterval );
}

Calling custom code at regular intervals

The following example will call BleepTask method every 10 seconds.

// Interval at which your callback is called
TimeSpan bleepInterval = TimeSpan.FromSeconds( 10 );

// Adding your callback
Scheduler.NewTask( BleepTask ).RunForever( bleepInterval );

// Your callback. Implements SchedulerCallback delegate.
void BleepTask( SchedulerTask task ){
    Chat.Say( Player.Console, "bleep" );
}

Besides RunForever(TimeSpan), SchedulerTask has many Run* methods for running a task once, several times, or forever - with different delays and at different intervals.


Checking incoming players

There are many events fired while the player is connecting. For a full explanation, see API: Login. For example Player.Connected event is fired after fCraft looks player up in the database/verifies name/checks bans, but just before a handshake reply is sent to the player. This event allows addition more checks before player is allowed into the server. Here we're using Player.Ready event, which is fired when a player is fully connected, and just after they join the main map. This is where all announcement code should go.

// Subscribing to the event
Player.Ready += OnPlayerReady;

// Event handler
void OnPlayerReady( object sender, PlayerEventArgs e ) {
    // if connecting player is of highest rank (presumably owner)
    if( e.Player.Info.Rank == RankManager.HighestRank ) {
        // Get a list of players who can see them join
        // (to avoid accidentally revealing hidden owners)
        var playersToMsg = Server.Players.CanSee( e.Player );
        // Spam them (use &Y colorcode, which maps to SayColor)
        playersToMsg.Message( "&YOMG THE OWNER IS HERE! EVERYONE SAY \"HEY {0}\"",
                              e.Player.Name );

        // or, if player's name contains the word "grief"
    } else if( e.Player.Name.Contains( "grief", StringComparison.OrdinalIgnoreCase ) ) {
        // Spam everyone some more!
        Server.Message( "&YLOOK OUT! GRIEFERS ARE COMING!" );
    }
}


Adding a new command

New commands can be added by calling CommandManager.RegisterCustomCommand. You need to provide a CommandDescriptor object. If there is any conflict between your command and existing ones, or if some aspect of your command's descriptor is unacceptible, a CommandRegistrationException will be thrown.

// Command descriptor
CommandDescriptor CdBleep = new CommandDescriptor {
    Name = "bleep",
    Aliases = new[] { "bloop" },
    Category = CommandCategory.Chat,
    Permissions = new[] { Permission.Chat },
    Help = "Prints a number of bleeps in chat.",
    Usage = "/bleep [Times]",
    Handler = BleepHandler
};

// Command handler - must implement CommandHandler delegate
void BleepHandler( Player player, CommandReader cmd ) {
    int numberOfBleeps = 10;

    // if a param is given
    if( cmd.HasNext ) {
        // try to parse it as a number, and save to numberOfBleeps
        if( !cmd.NextInt( out numberOfBleeps ) ) {
            // if that fails (not a number), print usage
            CdBleep.PrintUsage( player );
            return;
        }
    }

    // check the range
    if( numberOfBleeps < 1 || numberOfBleeps > 32 ) {
        player.Message( "Specify between 1 and 32 bleeps." );
        return;
    }

    for( int i = 0; i < numberOfBleeps; i++ ) {
        Chat.SendGlobal( player, "bleep" );
    }
}

// Registering your command with the server
CommandManager.RegisterCustomCommand( CdBleep );

Reacting to server shutdown

There are two events associated with shutdown: Server.ShutdownBegan and Server.ShutdownEnded. Both supply a ShutdownEventArgs object that provides information about the shutdown parameters. For more information about how server shutdown works, see API: Shutdown.

// Subscribing to the event
Server.ShutdownBegan += OnShutdownBegan;

// Event handler
void OnShutdownBegan( object sender, ShutdownEventArgs e ){
    Console.Write( "The end is near!" );
    if( e.Restart ){
        Console.Write( "But we will be back shortly." );
    }
}


Starting selections

If you need the player to mark one or more coordinates for you (for example for a draw command), you'll be using the Selection API. Call player.StartSelection(...) to initiate a selection, and return control to fCraft. If/when player marks the required number of blocks, your callback will be called. If player cancels, disconnects, or starts another selection, then your callback will not be invoked.

Also note that while a player is in selection mode Player.PlacingBlock and Player.PlacedBlock events are not fired for clicks. Player.Clicking and Player.Clicked events are still fired.

// This is where you start/set up the selection.
// You can call SelectionStart() from anywhere, really. Doesn't need to be a command handler.
static void ClickityHandler(Player player, CommandReader cmd) {
    // Random number to demonstrate argument passing. You don't need it.
    int randomNumber = new Random().Next();
    
    // Let player know that a selection started
    player.Message("Click anything! Random number is {0}", randomNumber);

    // Start it!
    player.SelectionStart(1, // number of blocks to click (must be at least 1)
                          ClickitySelectionCallback, // our callback function
                          randomNumber,              // an argument to pass to the callback (can be anything or null)
                          new Permission[0]);        // you could omit this completely
    
}

// The callback function. fCraft will call it automatically when the last block is marked.
static void ClickitySelectionCallback(Player player, Vector3I[] marks, object tag) {
    // Retrieve the parameter
    int randomNumber = (int)tag;

    // Marked coordinates are in an array, in same order that they were clicked/marked.
    // You can assume that it has at least as many marks as you asked for (in this case, at least 1)
    Vector3I coord = marks[0];

    // Let player know that it finished
    player.Message("You clicked at coordinates {0}, and random number was {1}", coord, randomNumber);
}

Related properties: Player.DisableClickToMark, Player.IsMakingSelection, Player.SelectionMarkCount, Player.SelectionMarksExpected, Player.IsRepeatingSelection

Related methods: Player.SelectionAddMark(...), Player.SelectionExecute(), Player.SelectionResetMarks(), Player.SelectionCancel()


Adding custom permissions

To add a new permission:

  • Add a new constant to Permissions enum (fCraft/Player/Permissions.cs)
  • Give the new permission to some default ranks (at least to the highest rank), in Config.DefineDefaultRanks() (fCraft/System/Config.cs)
  • Add a tooltip explaining the permission in ConfigGUI's MainForm.FillToolTipsRanks() (ConfigGUI/MainForm.ToolTip.cs)

To add a per-rank limit to the new permission:

  • For every rank that has this permission, add a default limit to Config.DefineDefaultRanks() (fCraft/System/Config.cs)
  • Add a way to set the limit in ConfigGUI's MainForm.FillPermissionLimitBoxes() (ConfigGUI/MainForm.cs)
  • Add a tooltip explaining the permission limit in MainForm.FillToolTipsRanks() (ConfigGUI/MainForm.ToolTip.cs)