Modding:Events: Difference between revisions

1,509 bytes added ,  22:38, 10 February 2023
m
Reverted edits by Glass zebra (talk) to last revision by Egocarib
m (Restore LiquidVolume variable, alter event header)
m (Reverted edits by Glass zebra (talk) to last revision by Egocarib)
Tag: Rollback
 
(7 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[Category:Modding]]{{Modding Info}}
{{Modding Info}}{{Modding Topic Prerequisites | Modding:C Sharp Scripting}}[[Category:Script Modding]]


Events are the basic building block that most of [[{{gamename}}]]'s mechanics are built upon.<br/>
Events are the basic building block that most of [[{{gamename}}]]'s mechanics are built upon.<br/>
Line 7: Line 7:
=Standard Events=
=Standard Events=
Events are typically listened for in generic Parts or Effects, which is what implements more advanced functionality of an object.<br/>
Events are typically listened for in generic Parts or Effects, which is what implements more advanced functionality of an object.<br/>
[[Mutations]] ([[Modding:Creating_New_Mutations|Creation]]) and [[Skills_and_Powers|Skills]] are both Parts. Effects such as sleep, prone or poison are handled separately from Parts as they are usually temporary.
[[Mutations]] ([[Modding:Creating_New_Mutations|Creation]]) and [[Skills]] are both Parts. Effects such as sleep, prone or poison are handled separately from Parts as they are usually temporary.
<!-- Listen? Register? Subscribe? Ah whatever. -Arm -->
<!-- Listen? Register? Subscribe? Ah whatever. -Arm -->
===Listening===
===Listening===
To listen for an event you typically override the '''Register''' method of the base '''IPart''' or '''Effect''' class.<br/>
To listen for an event you typically override the '''Register''' method of the base '''IPart''' or '''Effect''' class.<br/>
This is executed '''once''' when the IPart/Effect is added to the object and subsequently [[Modding:Serialization_(Saving/Loading)|serialized]].<br/>
This is executed '''once''' when the IPart/Effect is added to the object and subsequently [[Modding:Serialization_(Saving/Loading)|serialized]].<br/>
Overriding the '''AllowStaticRegistration''' method to return true alters this behaviour: registrations are no longer serialized and '''Register''' is re-executed every '''save load'''.<br/>
It is however possible to register for an event ''anywhere'' as long as you have an instance of both an IPart/Effect and a GameObject.
It is however possible to register for an event ''anywhere'' as long as you have an instance of both an IPart/Effect and a GameObject.
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
public override void Register(GameObject obj) {
public override void Register(GameObject obj) {
    // Listen for when the GameObject in obj is awarded XP.
// Listen for when the GameObject in obj is awarded XP.
    obj.RegisterPartEvent(this, "AwardXP");
obj.RegisterPartEvent(this, "AwardXP");


    // The otherwise identical method call used for when [this] is an Effect.
// The otherwise identical method call used for when [this] is an Effect.
    obj.RegisterEffectEvent(this, "AwardXP");
obj.RegisterEffectEvent(this, "AwardXP");


    // Call the base Register method that we overrode.
// Call the base Register method that we overrode.
    base.Register(obj);
base.Register(obj);
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 30: Line 31:
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
public void AwardPlayerXP() {
public void AwardPlayerXP() {
    // Get the player GameObject.
// Get the player GameObject.
    GameObject player = XRLCore.Core.Game.Player.Body;
GameObject player = XRLCore.Core.Game.Player.Body;


    // Create a new "AwardXP" Event, specifying an "Amount" parameter with the integer value 50.
// Create a new "AwardXP" Event, specifying an "Amount" parameter with the integer value 50.
    // You can keep declaring staggered parameters or leave them out entirely.
// You can keep declaring staggered parameters or leave them out entirely.
    // Event.New(ID, [PrmName1, PrmVal1, PrmName2, PrmVal2, PrmName3, ...])
// Event.New(ID, [PrmName1, PrmVal1, PrmName2, PrmVal2, PrmName3, ...])
    Event awardXP = Event.New("AwardXP", "Amount", 50);
Event awardXP = Event.New("AwardXP", "Amount", 50);


    // You can also set the parameters one by one instead of passing them to the constructor.
// You can also set the parameters one by one instead of passing them to the constructor.
    awardXP.SetParameter("Amount", 50);
awardXP.SetParameter("Amount", 50);


    // Fire the event on the player.
// Fire the event on the player.
    player.FireEvent(awardXP);
player.FireEvent(awardXP);
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 50: Line 51:
public override bool FireEvent(Event E)
public override bool FireEvent(Event E)
{
{
    // If the ID of our event is "AwardXP"...
// If the ID of our event is "AwardXP"...
    if (E.ID == "AwardXP") {
if (E.ID == "AwardXP") {
        // Get the parameter we specified either in the constructor or using SetParameter.
// Get the parameter we specified either in the constructor or using SetParameter.
        int amount = E.GetIntParameter("Amount");
int amount = E.GetIntParameter("Amount");


        // Add the amount to this GameObject's experience.
// Add the amount to this GameObject's experience.
        this.ParentObject.Statistics["XP"].BaseValue += amount;
this.ParentObject.Statistics["XP"].BaseValue += amount;
    }
}


    // Return the result of the base FireEvent that we overrode, which returns a literal true.
// Return the result of the base FireEvent that we overrode, which returns a literal true.
    // Should you instead return false further event processing will stop, meaning no Parts and Effects after get the chance to handle the event.
// Should you instead return false further event processing will stop, meaning no Parts and Effects after get the chance to handle the event.
    return base.FireEvent(E);
return base.FireEvent(E);
}
}
</syntaxhighlight>
</syntaxhighlight>


<!-- Minimal events? Minified? Minimum? Unsure.  
<!-- Minimal events? Minified? Minimum? Unsure.  
    Rename if someone can coax one out of the devs. -Arm -->
Rename if someone can coax one out of the devs. -Arm -->
=Minimal Events=
=Minimal Events=
Minimal events are a new event system introduced with the {{favilink|Tomb of the Eaters}} update (V200).<br/>
Minimal events are a new event system introduced with the {{favilink|Tomb of the Eaters}} update (V200).<br/>
Line 78: Line 79:
// Listens for one event always.
// Listens for one event always.
public override bool WantEvent(int ID, int cascade) {
public override bool WantEvent(int ID, int cascade) {
    // Check if the ID parameter matches one of the events we want, in this case ZoneActivatedEvent.
// Check if the ID parameter matches one of the events we want, in this case ZoneActivatedEvent.
    // The base WantEvent of IPart/Effect will always return false.
// The base WantEvent of IPart/Effect will always return false.
    return base.WantEvent(ID, cascade) || ID == ZoneActivatedEvent.ID;
return base.WantEvent(ID, cascade) || ID == ZoneActivatedEvent.ID;
}
}


Line 89: Line 90:
// Listens for an event dynamically.
// Listens for an event dynamically.
public override bool WantEvent(int ID, int cascade) {
public override bool WantEvent(int ID, int cascade) {
    if(ID == ZoneActivatedEvent.ID)
if(ID == ZoneActivatedEvent.ID)
        return true;
return true;
       
    if(wantEndTurn && ID == EndTurnEvent.ID)
if(wantEndTurn && ID == EndTurnEvent.ID)
        return true;
return true;


    return base.WantEvent(ID, cascade);
return base.WantEvent(ID, cascade);
}
}
</syntaxhighlight>
</syntaxhighlight>
===Firing===
===Firing===
<!-- TODO: Expand this section to include the common static MinEvent.Send() method, GiveDramsEvent doesn't have it so either a second method or switch events for the section -->
To fire a min event you call the '''HandleEvent''' method on the relevant GameObject and pass it a specific MinEvent.<br/>
To fire a min event you call the '''HandleEvent''' method on the relevant GameObject and pass it a specific MinEvent.<br/>
Setting parameters on a MinEvent is a lot more intuitive compared to the old system as they are now properties of their own type.
Setting parameters on a MinEvent is a lot more intuitive compared to the old system as they are now properties of their own type.
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
public void GivePlayerDrams() {
public void GivePlayerDrams() {
    // Get the player GameObject.
// Get the player GameObject.
    GameObject player = XRLCore.Core.Game.Player.Body;
GameObject player = XRLCore.Core.Game.Player.Body;


    // Retrieve a GiveDramsEvent from the pool, this is the preferred way to get an event instance.
// Retrieve a GiveDramsEvent from the pool, this is the preferred way to get an event instance.
    GiveDramsEvent E = GiveDramsEvent.FromPool();
GiveDramsEvent E = GiveDramsEvent.FromPool();
    // Some events have overloaded FromPool to set properties of the event on retrieval.
// Some events have overloaded FromPool to set properties of the event on retrieval.
    E = GiveDramsEvent.FromPool(Actor: player, Drams: 50);
E = GiveDramsEvent.FromPool(Actor: player, Drams: 50);
    // You can still set properties directly.
// You can still set properties directly.
    E.Liquid = "water";
E.Liquid = "water";
    // Yet other events do not have an accessible FromPool method, in which case you'll have to instantiate your own.
// Yet other events do not have an accessible FromPool method, in which case you'll have to instantiate your own.
    E = new GiveDramsEvent() {
E = new GiveDramsEvent() {
        Actor = player,
Actor = player,
        Drams = 50
Drams = 50
    };
};


    // Fire the event on the player.
// Fire the event on the player.
    player.HandleEvent(E);
player.HandleEvent(E);
}
}
</syntaxhighlight>
</syntaxhighlight>
===Handling===
===Handling===
The handling of min events is the most drastic change from the old system, as you can overload the base '''HandleEvent''' method instead of overriding it.<br/>
The handling of min events is the most drastic change from the old system, as you can now override one separate method for each type of MinEvent which is much simpler to organize.<br/>
Your overloaded method is then retrieved through [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection reflection] based on the parameter type and cached in a dictionary.<br/>
What all that jargon means is that you can write separate methods for each event type which is much simpler to organize.
<syntaxhighlight lang="c#">
<syntaxhighlight lang="c#">
// A volume/container of liquid, like a puddle or flagon.
// A volume/container of liquid, like a puddle or flagon.
Line 131: Line 131:


// This method handles the GiveDramsEvent.
// This method handles the GiveDramsEvent.
public bool HandleEvent(GiveDramsEvent E) {
public override bool HandleEvent(GiveDramsEvent E) {
    liquid.GiveDrams(E.Liquid, ref E.Drams, E.Auto);
liquid.GiveDrams(E.Liquid, ref E.Drams, E.Auto);


    // Just like old events, returning false prevents further event processing.
// Just like old events, returning false prevents further event processing.
    // Here we have no more liquid left to give so there's no reason to continue.
// Here we have no more liquid left to give so there's no reason to continue.
    if (E.Drams <= 0)
if (E.Drams <= 0)
        return false;
return false;


    return true;
return true;
}
}


// This method handles the FrozeEvent.
// This method handles the FrozeEvent.
public bool HandleEvent(FrozeEvent E) {
public override bool HandleEvent(FrozeEvent E) {
    E.Object.DisplayName = "&CFrozen Object";
E.Object.DisplayName = "&CFrozen Object";
    return true;
return true;
}
}


// It's still possible to override HandleEvent and compare the ID or type like old events.
// It's still possible to override the base HandleEvent and compare the ID or type like old events.
// This is more useful for when you're cascading events carte blanche to sub-objects, see the Cascading section below.
// This is more useful for when you're cascading events carte blanche to sub-objects, see the Cascading section below.
public override bool HandleEvent(MinEvent E) {
public override bool HandleEvent(MinEvent E) {
    if (E.ID == GiveDramsEvent.ID) {
if (!base.HandleEvent(E)) {
        return HandleEvent(E as GiveDramsEvent);
return false;
    } else if (E is FrozeEvent) {
}
        return HandleEvent(E as FrozeEvent);
    }
if (E.ID == GiveDramsEvent.ID) {
return HandleEvent(E as GiveDramsEvent);
} else if (E is FrozeEvent) {
return HandleEvent(E as FrozeEvent);
}


    return true;
return true;
}
</syntaxhighlight>
===Custom===
Prior to patch 200.43 it was possible to simply overload the handling methods, where it was then retrieved & invoked through [https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection reflection] based on the parameter type and cached in a dictionary.<br/>
If you are creating your own custom MinEvent, there's nothing for you to override so you will still have to do this. To mark your custom MinEvent for invocation, override the '''WantInvokeDispatch''' method to return true.
<syntaxhighlight lang="c#>
public class MyCustomMinEvent : MinEvent
{
public new static readonly int ID = MinEvent.AllocateID();
 
public MyCustomMinEvent() {
base.ID = MyCustomMinEvent.ID;
}
 
// This is necessary for custom events that need the HandleEvent to be reflected.
public override bool WantInvokeDispatch() {
return true;
}
 
// A static helper method to fire our event on a GameObject.
// If it's a high-frequency event this should implement event pooling, rather than create a new MyCustomMinEvent each time.
public static void Send(GameObject Object) {
if (Object.WantEvent(MyCustomMinEvent.ID, MinEvent.CascadeLevel)) {
Object.HandleEvent(new MyCustomMinEvent());
}
// If you want to use an old event as a fallback, this is a good place to do so.
if (obj.HasRegisteredEvent("MyCustomEvent")) {
obj.FireEvent(Event.New("MyCustomEvent"));
}
}
}
 
// The reflected overload method that will handle our event in a part or effect.
public bool HandleEvent(MyCustomMinEvent E) {
return true;
}
}
</syntaxhighlight>
</syntaxhighlight>
Line 179: Line 218:
{
{
// If the cascade variable has the inventory bit...
// If the cascade variable has the inventory bit...
    if (MinEvent.CascadeTo(cascade, MinEvent.CASCADE_INVENTORY)) {
if (MinEvent.CascadeTo(cascade, MinEvent.CASCADE_INVENTORY)) {
        // Check if any item in our inventory wants this event.
// Check if any item in our inventory wants this event.
foreach(GameObject item in Inventory) {
foreach(GameObject item in Inventory) {
if(item.WantEvent(ID, cascade))
if(item.WantEvent(ID, cascade))
return true;
return true;
}
}
    }
}
   
    return base.WantEvent(ID, cascade);
return base.WantEvent(ID, cascade);
}
}