Script: Callbacks and Timers
This page assumes that you know the basics about scripting.
You probably reached a stage where you asked yourself "okay, I know how things work in theory - but how do I actually bring them alive?". You might want your object to interact with others or to do certain things on certain events.
Callbacks
Callbacks are functions in your scripts that are called automatically when something special happens and which you can use to react to certain events. A common example is an object that explodes when it collides with the landscape, you would use the callback func Hit(int x_velocity, int y_velocity) for that. That function is called whenever your object hits the landscape (but not other objects!). To use a callback, you can simply write a function with the name of the callback into your script like that:
func Hit(int x, int y) { Explode(100); return; }
Note that some callbacks also expect return values that define the reaction of your object. Take func RejectEntrance(object into_object) that you can use to prevent your object from being picked up, for example you might want your object to be non-collectable when it is on fire:
func RejectCollect() { if (OnFire()) return true; else return false; }
Note that I omitted the parameter object into_object. The callback is still called. If you don't need certain parameters, you can leave them out.
If you want a callback that is called repeatedly every few frames, you can use the AddTimer(string name, int interval)
function to add a custom callback:
/* script of a mine */ // called on object creation func Initialize() { AddTimer("Check", 30); } // our custom callback func Check() { // explode when a moving object is near if (ObjectCount(Find_Distance(20), Find_OCF(OCF_HitSpeed1)) > 0) Explode(30); }
inheritance and _inherited()
When you read through some of the original scripts, you will find something weird like return _inherited(...) at the end of some callbacks. The function _inherited() is used to call the same function of the parent object when your object #includes another one. So for example when you write an extension to the Clonk, you might want that the original Clonk-script still receives the function call:
// CatchBlow is called when an object takes a hit func CatchBlow(int level) { if (level > 10) Sound("Hurt*"); return _inherited(level, ...); }
The three dots ... stand for all the other parameters that you did not assign a name. So if you have a named parameter you still have to write it manually into the _inherited-call. In the above example I omitted the object by parameter of CatchBlow.
List of Callbacks
You can find a list of some of the callbacks here: C4Script Reference
Due to historical reasons there are (currently) not all existing callbacks listed. If you are looking for a certain callback that you are sure to exist, you can go into our IRC channel and ask ;)
Effects as timers
Sometimes you need to change behavior of objects at runtime, for example when the Clonk is affected by a poision, you might want to deal some damage every few seconds. For a purpose like that, Effects are ideal in C4Script! Effect is actually a bit a misleading name even though they can obviously be used for both graphical and behavioral effects.[br]
Effects are not much more than simple timers that can be added to an object at runtime. I will here only shortly describe the purpose of effects, if you need to know the syntax, check this page: C4Script Reference
How to create Effects?
If you want to write a new Effect, you have to choose a name for your effect first, for example MyEffect. Then you can think about when you want certain things to happen, I'll chose the start of the Effect and every second here - other examples would be when the object takes damage or at the end of the Effect (for example when the object dies).
Effect functions are always prefixed with a Fx and suffixed with the name of a certain callback, f.e. Start. My example Effect now looks like this:
func FxMyEffectStart(object target, proplist effect, bool temp) { if (temp) return; CastParticles("MagicSpark", 30, 10, target->GetX(), target->GetY(), 20, 30, RGB(50, 200, 20), RGB(50, 255, 50)); } func FxMyEffectTimer(object target, proplist effect, int time) { if (time > 36 * 10) return -1; target->DoEnergy(-1); return 1; }
You probably notices the weird line if (temp) return; at the beginning of my Fx*Start function. Effects can use a priority system and when an Effect with a higher priority is added, lower-priority-Effects are temporarily stopped and started again. This has its applications, but right now I don't want to do anything on those temporary calls. I only want to cast my particles when the Effect is really added initially.
Okay, where did I tell my Effect when it is started or how often the Fx*Timer function is called? Well, I didn't.
An Effect has to be started from a script with AddEffect, imagine the following line would be in the script of the object where I defined my effect and that object is called MyMine:
func Check() { var clonk = FindObject(Find_Distance(20), Find_ID(Clonk)); if (!clonk) return; AddEffect("MyEffect", clonk, 20, 36, nil, MyMine); } /* ...here follow our effect functions as defined above... */
When I used AddEffect I had to define the target of the effect (clonk), the priority (here an arbitrary 20) and the interval of the timer call (36) - and, most importantly where I defined my effect (MyMine).
The effect will run until either the target dies or the Fx*Timer function returns -1.
Some Hints
- You can define effects with the timer interval of 0. They will run forever until you manually remove them. Useful as data storages or statuses.
- You can even add effects that are not defined anywhere! They will be removed whenever their Fx*Timer function would be called the first time and obviously do not get any callbacks. That is an interesting trick if you quickly want to attach some internal status information to an object.