Not logged inOpenClonk Forum
Up Topic Development / Scenario & Object Development / Interface for useable objects [update]
- - By Newton [de] Date 2010-01-22 02:02 Edited 2011-02-13 21:28
I updated the interface definition and adjusted them for the upcoming gamepad controls.

The following callbacks are called to buildings, vehicles and items. In detail:
If the clonk is contained, ContainedUse* callbacks are made (with the same parameters) to the object in which the clonk is contained. If the clonk pushes or is attached to something (e.g. rides), the callbacks are made to this object. Otherwise, the callbacks will be made to the selected object in the clonks inventory.
For all callbacks: If the callback returns true, it holds that the control has been handled. So e.g. on mouse controls the item is not thrown instead.

Standard Callbacks

In the standard mouse control, using the object happens with a click with the mouse. x and y are local coordinates of the cursor.
In the gamepad control, the same applies. x and y are the local coordinates of the virtual gamepad cursor (which is controlled via the analog stick or the D-Pad)

ControlUse(object clonk, int x, int y)
Use the object. It gets called when the player presses the mouse button down in mouse control. In gamepad control, it gets called when the player releases the use-button. Note that if ControlUse holds as unhandled (returns false or is not defined), neither ControlUseStop nor ControlUseHolding will not be called.

ControlUseStop(object clonk, int x, int y)
Same as above but gets called when the player releases the mouse button or use-button of the gamepad.

ControlUseHolding(object clonk, int x, int y)
Is called while the mouse button (or use button) is held down. But only if the object returns for HoldingEnabled() { return true; } and ControlUseStart (see below) returns true.

Additional Callbacks

ControlUseStart(object clonk, int x, int y)
Same as ControlUse but is always explicitly called at the start. Note that with gamepad control, the x and y parameter might be 0,0. ControlUse is only called when ControlUseStart is not handled (returns false or is not defined).

ControlUseCancel(object clonk, int x, int y)
This is called whenever the use of the currently used item is interrupted in any way before the mouse button is released (or the use-button of the gamepad). After the use of an object has been cancelled, no more ControlUse* callbacks are called.
ControlUseCancel is called automatically when:

  • the clonk enters or exits a building, grabs or lets go a vehicle, attaches to or detaches from an object

  • another crew member is selected

  • the used item has exited the clonk

  • the used item has been deselected

  • the selected item has changed places with another item in the inventory

  • the other mouse button (use button) is pressed down (while this mouse button was hold down)

  • when an object is thrown

  • CancelUse() is called



Vehicle and buildings Callbacks

Vehicles and buildings may have a secondary use-callback which is triggered via the right mouse button in mouse control (and use2-button in gamepad control). The callbacks look the same except that it's ControlUseAlt*/ContainedUseAlt*. Shouldn't be an invitation to make a vehicle more complicated to use than it must, actually.

Movement commandos are forwarded to the pushed vehicle, the entered building or the object the clonk is attached to (for buildings, it's Contained* again):

  • ControlLeft(object clonk) - called when the left movement key is pressed

  • ControlRight(object clonk) - etc...

  • ControlUp(object clonk)

  • ControlDown(object clonk)

  • ControlStop(object clonk, int control) - called whenever a movement key is released



Especially for objects to which the clonk is attached to, additionally:

  • ControlJump(object clonk) on jump. (The controls for up and for jump can differ for different control sets.)


Using items while on a mount (e.g. horse)
The ControlUse* controls are always issued first to the mount (object to which the clonk is attached to).
If the mount returns false on the calls ControlUseStart (and ControlUse if defined), the control is considered unhandled and will be forwarded to the clonk's inventory. If then the item in the clonk's inventory to which the call goes returns true, this item will regarded as the currently used item (instead of the mount) until the end of the usage and all ControlUse*-calls will be forwarded to that item. However, as said in the first sentence, the control will still always be issued to the mount first - even if the mount is not the used item. Note that what the mount returns on these calls has no effect on whether the used item gets the control or not in this case: It always gets the control, the mount can't block calls so that they don't reach the item.
What it can do is to cancel the usage at any time by calling CancelUse(). The mount will also receive a ControlUseCancel call if the usage of the item is cancelled.
Parent - - By Ringwaul [ca] Date 2010-01-22 03:18

>It gets called when the player presses the mouse button down in mouse control. In gamepad control, it gets called when the player releases the use-button.
>Same as ControlUse but is always explicitly called at the start.


I guess that means the Bow and Jar of Winds must switch ControlUse() to ControlUseStart()?
Reply
Parent - - By Newton [de] Date 2010-01-23 03:04
Did that (except Jar of Wind). I also renewed & simplified the musket to be gamepad-ready. The reloading will now cancel on all possible cases it should cancel, thanks to ControlUseCancel() / CancelUse() :-)
You now have to press down the use button to reload. If it has been reloaded, it cancels the use. If you press down another time, you aim and can shoot. I think this is how you intended the musket to be: not like the bow where you need to draw the bow everytime before you shoot but where you can load first and shoot later.
Parent - - By Ringwaul [ca] Date 2010-01-23 07:46
Yeah, I had that Step variable in there to stop auto-firing after reload. Kinda weird.  Also, I probably wouldn't have bothered learning all about effects if I had known it was intended for ControlUseHolding() to do the reload-countdown.

+1 thing learned about C4script for me. :)
Reply
Parent - By Newton [de] Date 2010-01-23 13:37
Actually I first remodelled your reload effect there (making a clear distinction beween object and effect) but later realized that its much easier if it is done over controlUseHolding()
Parent - - By Nachtschatten Date 2010-01-22 21:53

> ControlUseStart(object clonk, int x, int y)
> ControlUse(object clonk, int x, int y)
> ControlUseStop(object clonk, int x, int y)


I have to admit that the behaviour of these confuses me a lot. What I'm seeing is:

  • There is a "button press" event, ControlUseStart() handles that.

  • There is a "button release" event, ControlUseStop() handles that.


So far, so good, and I'm fine with that. However, then there is ControlUse(), which seems to be redundant. It acts like ControlUseStart() for mouse control, but like ControlUseStop() for game pad control. What is this good for?
Reply
Parent - - By Newton [de] Date 2010-01-23 02:56
Obviously, both gamepad users and keyboard-without-mouse users need to be able to aim to be able to take advantage of the new controls. This works with a virtual cursor (like a crosshair) which moves around the clonk in a circle: When pressing down the [Use]-Button, the cursor appears and is controllable via the analog stick (or D-pad on older controllers, WASD on keyboard-only-controls) while holding down. ControlUseStart is always called when the Button has been pressed down, ControlUseStop when released.
The big difference between gamepad and mouse is here, that the object already gets the right coordinates on the press down of the mouse button because the player clicked somewhere on the map. The virtual cursor of the gamepad control on the other hand starts either at offset 0,0 or at some previously saved offset. Best example is the throwing, which will also be different: In mouse control, the object is immediately thrown into the direction of the mouse cursor while in gamepad control, it is thrown on release of the [use] button. Why? Because the gamepad-player needs to steer the cursor into the right angle first.
So, for objects like bows etc. where you have an aim animation (and the player shoots on release anyway), the handling is the same. *Start and *Stop are used. For objects which can be used immediately, like perhaps the dynamite (in which direction should the dynamite be placed?), the handling is different for gamepad and mouse.
Parent - - By Nachtschatten Date 2010-01-23 11:19
Ah, that makes sense. Thanks for the explanation.
Reply
Parent - - By Newton [de] Date 2010-01-23 19:01 Edited 2010-01-23 19:05
I could also change the mouse behaviour to standard use = release. Then for all controls, it would be the same behaviour. Opinions?
Parent - - By Sven2 [de] Date 2010-01-23 19:05
No, that's not cool for mouse.
Parent - - By Newton [de] Date 2010-01-23 19:06
You know, you got to release the mouse button in any case.
Parent - By Sven2 [de] Date 2010-01-23 21:22
Yes, but this will add some delay.
Parent - By Newton [de] Date 2010-02-04 02:26
Updated the interface a bit. (See the crossed sentence and the bold sentence)
Parent - - By Newton [de] Date 2010-05-28 20:24
After some consideration, I finally implemented a proper interface for usage of items while attached to another object (riding etc.). See the last paragraph in my initial post for reference. What I changed is that now the mount has to decide on the start of the usage whether to allow the usage of the item or not and cannot block any ControlUse*-Call to the item afterwards. (But cancel it of course) The item is also considered as the used item now by the clonk, solving various problems (regarding cancelling the usage). In short: The scripter who scripts a mount does not have the possibility anymore to fuck up the usage of the item by f.e. just returning true on ControlUseStop and thus not allowing the used item to finish or cancel its usage. I eliminated a source of error.
Since all calls will be made to the mount first in any case, the scripter does still have all possibilities here. (Except for the fucked-up ones)

For all who made or plan to make useable items: Any item can be used while on a mount except if the mount disallows it or the item disallows it. So please keep that in mind when creating items and check that for the items you made before.
Amongst others, you can check if a clonk is on a mount by clonk->GetProcedure() == "ATTACH"
Parent - - By Zapper [de] Date 2010-05-28 21:46

>Amongst others, you can check if a clonk is on a mount by clonk->GetProcedure() == "ATTACH"


I would like to have callbacks for stuff like that. IsRiding() for example. Otherwise we would have to change every single script with the ATTACH-check when something changes.
(Same for IsWalking()/IsJumping()..)
Parent - - By Newton [de] Date 2010-05-28 23:13
I don't get your point: What should change about that? You want a function for every procedure?

Regarding the IsRiding(): Well, this is not really fitting. He is attached. That could be anything.
Parent - - By Zapper [de] Date 2010-05-29 12:32 Edited 2010-05-29 12:38

>You want a function for every procedure?


No. Tumble and Jump both use the procedure FLIGHT for example (flaw in current implementation of IsJumping btw, good that we have that function in one place..!). And if we introduce special cases someday (GetAction() == "Jump" vs WildcardMatch(GetAction(), "*Jump*")) I would rather do that in one function in the Clonk than in each check in every third object

PS: imagine we use a helper-action one day with the procedure ATTACH in which the Clonk is not ment to use items. That would mess everything up and we would have to change every script that checks for "ATTACH" instead of using a callback in the Clonk (even if you don't want to call it IsRiding)
Parent - - By Newton [de] Date 2010-05-30 01:03 Edited 2010-05-30 01:20
IsJumping and co makes sense if the state in which the object is in is bound to its action. This kind of thing was often used in the past like with IsRiding. I think the check was whether the action was Ride or RideThrow or some Wildcard match. But now, even for stuff like IsWalking, we use animations. There won't be different actions like Walk, WalkArmed, WalkArmedLow (like in Hazard) anymore. The procedure will always be WALK and actually in this example even the action will always be Walk. Any other animations are handled by the animations interface now.

For some reason it was always the way to do for items in Clonk  to check for the action of the clonk rather than for the procedure. As you pointed out, this led to many compatibility problems and was always a source of error esp. in sloppy code. Also, it led to an unintentional mutual dependence of the clonk code and the item code since the item code needed to know which actions were associated with walking, which with having a weapon in the hand and so on. The solution was to let the clonk define that and offer the items this kind of interface. But that didn't solve the problem completely by the way because this was defined for each clonk type individually. And exactly this led to the incompatibility between the clonk types: The item first had to check if the clonk who wants to use it is really a IsKnightClonk or IsWhateverClonk to be sure that it may use the interface of IsBowAiming() and similar stuff.
This was never done consistently and sometimes single items still checked for specific actions and others queried those Is*-stuff. What I mean to say is that those Is(Procedure)ing-functions were in the most cases just crutches.
However, to check for procedures if you mean to check for procedures (which is the case is most cases, I'd say) is clearly the better way to do it (Is the clonk climbing, walking, attached? Yes or no.). All the same what additional actions the clonk will get and how many, walk will always be walk, climb will always be climb. (Remember? The old clonk actually had two climb actions.) And this works for all clonk types because only the properties of actions are queried, not the names (which was always a stupid thing to do, in my opinion).

Regarding your example with tumble and jump, I'd say what you mean to check is not whether the action is named "Tumble or "Jump" (and thus give the action tumble a special meaning beyond what is defined in the actmap) but check whether the clonk's action has as a property ObjectDisabled=1 (and has the procedure FLIGHT, depends on the application of course). I think the whole checking-for-action-names (rather than checking for action properties) came from a time before it was possible to access the properties via script and when it became possible, people just continued to use it like this because it was done like that before.

In three sentences: I am very sceptical about reintroducing a dependence on action names rather than action properties in item and in clonk code because it adds special meaning to the actions outside of their own definitions. And this leads to mutual dependence of objects and to incompatibilities all over the place. If we don't rely on action names, we don't need Is(Procedure)ing-functions that pose as crutches to keep the added meaning of certain actions in the clonk's code.

>PS


Jupp, that would mess up the whole control code too. Anyway, give me an example for that "helper-action" and let me prove you wrong.
Parent - - By Isilkor Date 2010-05-30 01:42 Edited 2010-05-30 02:03
That riding or whatever is currently implemented via an ATTACH procedure is an implementation detail, and should not be part of the interaction interface. We may someday decide that ATTACH isn't the best way to go when creating mounts, and code the whole mounting in script, and what do you suggest we do then? Rebuild a vast library of third-party objects that depended on your promise that any mounted Clonk will have its procedure set to ATTACH?

[Edit]
Another related thing: A lot of the inertia in changing engine internals stems from the mostly non-existing encapsulation of core engine parts, which mean that a simple change cascades to a huge amount of indirectly related changes (because they depend on the exact internal representation of some values), which themselves cascade and so on.

> I'd say what you mean to check is not whether the action is named "Tumble or "Jump" (and thus give the action tumble a special meaning beyond what is defined in the actmap) but check whether the clonk's action has as a property ObjectDisabled=1 (and has the procedure FLIGHT, depends on the application of course)


No, he doesn't want to check for procedures and ObjectDisabled properties. He wants to check whether the Clonk jumps, regardless of how that may be implemented internally.
Reply
Parent - - By Newton [de] Date 2010-05-30 14:12 Edited 2010-05-30 14:18

>We may someday decide that ATTACH isn't the best way to go when creating mounts


You may be right. However, then the same consideration has to be made with pushing vehicles: Some day we might decide that vehicles should be pushed not via the procedure PUSH and then we need to replace all the checks where the PUSH procedure was checked. In the reply to Matthias' post, I suggested to add an action property for actions with which a mount can be controlled. The same would need to be done for push procedures, no? And seen globally, probably for some more procedures. (E.g. some day we might decide that procedure=DIG will not be digging anymore etc.)
And then, the question arises why it is even possible to check for a procedure if it is that downward incompatible. My reply to that: Well, it isn't downward incompatible - those things will not change just like that.

>No, he doesn't want to check for procedures and ObjectDisabled properties. He wants to check whether the Clonk jumps, regardless of how that may be implemented internally.


I don't deem properties of actions as interna. As much as I don't deem properties of objects as interna (name, id, components, physical values, category and so on...). Taking an item as an example which allows the clonk to jump another time in the air if he is jumping normally (not tumbling or anything), it only interests the script if the clonk is in the air (procedure==FLIGHT) and is in control (ObjectDisabled=0). When there is a call IsJumping() in the clonk and this one is used, the item will only work for clonk types that defined this callback even though the item just meant to check if the clonk was jumping normally. (Which was 100% to read just from the properties of the action)
Parent - By Günther [de] Date 2010-05-31 19:16

> And then, the question arises why it is even possible to check for a procedure if it is that downward incompatible. My reply to that: Well, it isn't downward incompatible - those things will not change just like that.


Just because they haven't since Clonk 4 (some minor adjustments like for Autostopcontrol in CR don't really count) doesn't mean that they shouldn't change. Quite the contrary, that means that they are overdue for renovation. Making it harder by using GetProcedure all over the place will just mean that GetProcedure will have to start lying some day. Nowadays it might be feasible to just reimplement Procedures in script without a lot or even any engine changes - a couple of simple script functions every frame could be fast enough.
Reply
Parent - - By Matthias [de] Date 2010-05-30 02:03
In one sentence: Zapper is right.

>to check for procedures if you mean to check for procedures


Well, since we're obviously going with a typical object oriented approach here, you just shouldn't mean to check for procedures on the outside of the clonk.
It's clearly the better way to introduce a callback and name it "IsMounted" or whatever fits this case best.
Reply
Parent - - By Newton [de] Date 2010-05-30 13:57
I can't help but be taken aback by your reply. If you want to convince me that another way is the better way to implement it, you need to make a greater effort to lay down your position convincingly. Would you be so kind to at least disprove my reasoning instead of just saying that I am wrong?
I guess you want to deny the following statement, right?: "If we don't rely on action names, we don't need Is(Procedure)ing-functions [...]"
I was talking at least about several different things in my last post.

Regarding your reply: Only if you regard procedures of actions as interna of actions which shouldn't be visible to the outside. I don't. Procedures are properties which form the most important part of how the actions behave and are thus a more reliable and (backward compatible) source of information as e.g. the action name. Plus, procedures are valid with all objects, they don't require that certain functions are defined in the clonk's code.

But I already see where this discussion is heading, thats why I am taking a shortcut: Properties of actions vs. Is*-callbacks in script. Ultimatively, what is the better choice - have a IsMounted() function in the clonk control code or have a property "Mounted" in the action definition? It is only a tiny difference and I think it is debatable which of them is the better choice. I deem the property the better choice in order to capsulate the properties of an action into the action definition rather than put some of it into the script.

But still, I am not convinced yet that to check for the procedure in the case of being attached/mounted (rather than a Mounted property or a IsMounted() callback) is not enough. See also my reply to Isilkor's post.
Parent - - By Isilkor Date 2010-05-30 14:07 Edited 2010-05-30 14:15

> I deem the property the better choice in order to capsulate the properties of an action into the action definition rather than put some of it into the script.


That's fine, I just don't agree with the notion that Procedure=ATTACH means that the Clonk is mounted.
Reply
Parent - - By Newton [de] Date 2010-05-30 14:14

>> I deem the property the better choice in order to capsulate the properties of an action into the action definition rather than put some of it into the script.
>That's fine, I just don't agree with the notion that Procedure=ATTACH means that the Clonk is mounted.


For what other things might an ATTACH action be used on the clonk other than mounts?
Parent - By Isilkor Date 2010-05-30 14:26
I don't know. That doesn't mean that there will never be anything that might profit from using ATTACH besides from mounts.
Reply
Parent - - By Matthias [de] Date 2010-05-30 15:13
I'm sorry if I seem to be nitpicking, I meant to disprove your reasoning by stating that it's not corresponding to oop-patterns.

Someone who scripts content shouldn't have to care how (Some action property) and where (Action) the clonk defines being mounted, because that could change.

>Plus, procedures are valid with all objects, they don't require that certain functions are defined in the clonk's code.


No, but that's hardly an argument. If someone creates an object that can be mounted, but is not a clonk, chances are good that it being mounted isn't handled by procedures as well.
If someone creates a new clonk, he should simply inherit this code from the standard clonk.

>I deem the property the better choice in order to capsulate the properties of an action into the action definition rather than put some of it into the script.


In fact, I think it should be both. Why not add the script callback, which - for now - just checks if the current action is one with Mounted=TRUE?
Only introducing a "Mounted" property still requires you to access the clonks internal actions from an outside context. What if the "being mounted"-code moves from actions to script some day?
Of course, you can argue that it's unlikely to happen, but it wouldn't hurt to just take precautions here and add the proper layer of encapsulation.

I also think that this should be considered for similar checks that might be used that way right now.

>But still, I am not convinced yet that to check for the procedure in the case of being attached/mounted (rather than a Mounted property or a IsMounted() callback) is not enough. See also my reply to Isilkor's post.


Besides the reasons given above: You would have to guarantee that every action that uses ATTACH is a mounted-action. What if someone attaches their clonks to some torture apperatus?
Reply
Parent - By Newton [de] Date 2010-05-30 16:22

>In fact, I think it should be both. Why not add the script callback, which - for now - just checks if the current action is one with Mounted=TRUE?
>Only introducing a "Mounted" property still requires you to access the clonks internal actions from an outside context. What if the "being mounted"-code moves from actions to script some day?


Ok, well. This could be done.
However, why do you deem actions as internal? I think actually the only thing that should be internal of the action is the name, oop-wise (because only the same object "knows" what which the action is for). The action is practically a public object that displays (parts of) the object's current state.
But it is true that much of the stuff currently done by procedures could be implemented in script instead. We have (now) local variables and script callbacks, definition and action properties and when to use the first and when to use the second is not really defined yet (I mean by best practice). The whole future and application of actions/actmaps seems to be a bit unclear or at least not well-defined.

>I also think that this should be considered for similar checks that might be used that way right now.


Which are?

>What if someone attaches their clonks to some torture apperatus?


I've been waiting for someone to pose an example. Well, given that the clonk has an appropiate ATTACH-getting-tortured-action, the torture apperatus would just get the controls (Up, Down, Left, Right, ControlUse*). It's up to the apperatus to make something out of it or not. And actually, that the apparatus gets those controls is actually good because this way, the clonk can't use it's items and can't move.
Up Topic Development / Scenario & Object Development / Interface for useable objects [update]

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill