Not logged inOpenClonk Forum
Up Topic General / Help and Questions / [QUESTION] Potion brewing
- - By Sayned [kz] Date 2013-07-14 17:03
It's me again.

So I have made some maps with spells, and have got another idea - Alchemy. I have made around ten different potions and realised that I have no idea how to make a thing to brew this potions. For one map I need to make an item which will allow clonks brew potions when he is scrolling on the map and on another I want to make a big Pot which will allow to brew potions.

I have got a lot of different ideas how to make the item. Example: Code finds an item and after some time it gives a potion and removes the object. But if I will get around 50 potions, looking at this giant wall of code will be impossible. Nevertheless, I tried to use this system. I have failed and tried again. Now I have no idea what to do, so I want to ask you, what is the best way to make such code, so it won't be so giant and complicated?

Here are details of what I'm going to do:
It's an item called Alchemist's kit. It allows clonks brew potions anywhere if they have ingredients. After clonk activates this item and starts brewing, he can put into backpack and after 50(frames?) he will get potion he has choosen(yeah, system above doesn't let clonk with more then 1 ingredient to choose what potion is he going to brew. If there is a way to make clonks be able to choose potion they will be brewing it would be amazing).

Sorry if my english is bad, It's not my native language and I'm sleepy right now.
Parent - - By Zapper [de] Date 2013-07-14 17:33
You could have an item, that opens a menu on ControlUse() with CreateMenu and then adds one menu entry with AddMenuEntry to the menu for every possible potion. Wenn the player chooses a potion from the menu, another function is called (the one that you set in AddMenuEntry) and then you can check whether he has all the ingredients.
When he has all the ingredients, you can start the brewing, remove the ingredients and after a short time (for example with ScheduleCall or AddEffect or even AddTimer) you can create a potion.

You can find out what an object is made of with GetComponent and you could list all potions by adding a callback "func IsPotion() { return true; }" to your potions and then use GetDefinition to list all the objects with that callback, for example:

func ControlUse()
{
CreateMenu(...);
var i = 0, obj;
while (obj = GetDefinition(i++))
{
    if (!obj->~IsPotion()) continue;
    AddMenuEntry("MakePotion", ..., obj);
}
}

func MakePotion(ID)
{
    // check if OK with GetComponents
    ...
    // remove components
    ...
    // start timer to create potion
   var effect = AddEffect("MakePOtion"....);
    effect.potion = ID;
}

func FxMakePotionTimer(target, effect, time)
{
    // effects...
    ...
    if (time > 36*3) // three seconds
    {
        CreateObject(effect.potion);
       return -1;
    }
}
Parent - - By Sayned [kz] Date 2013-07-14 18:34
Seems like it's what I needed.

Does AddMenuEntry exist? I haven't found it in documentary, but found AddMenuItem. I think they're same.

Right now this code dissappoints me, mostly I don't understand how to use GetComponent

Also I haven't found anything checking isn't clonk is brewing anything else now. Does something check it? Or I haven't look carefully?
Parent - - By Zapper [de] Date 2013-07-14 19:00

>Does AddMenuEntry exist? I haven't found it in documentary, but found AddMenuItem. I think they're same.


Yes, I meant AddMenuItem

>mostly I don't understand how to use GetComponent


You can figure out either what components are needed or how much of a component:

var i, ID;
while (ID = GetComponent(nil, i++, nil, potion_ID))
{
    var amount_needed = GetComponent(ID, nil, nil, potion_ID);
    var amount_available = clonk->ContentsCount(potion_ID);
    if (amount_available < amount_needed)
    {
        Message("You need %d %s!", amount_needed, potion_ID->GetName());
        Sound("Error");
        return;
    }
    // everything is ok!
}


> Also I haven't found anything checking isn't clonk is brewing anything else now. Does something check it? Or I haven't look carefully?


If you use an effect to do the brewing timer, you could use GetEffect() to check if such an effect with the same name is already there
Parent - - By Sayned [kz] Date 2013-07-15 05:02
Ok, I tried to use CreateMenu and nothing happened. First time I tried to use it like on example in documentary, second time I have added random prorperties for menu. In both ways clonk just threw the item away.

CreateMenu(GetID()); - first time
CreateMenu(GetID(),nil,1,"Nothing to craft",0,0,false); - second time

Also game can't find pClonk(clonk) identifier in MakePotion(ID)

ERROR: unknown identifier: pClonk (in MakePotion, MagicTimes.ocd\Potions.ocd\AlchemistKit.ocd\Script.c:26:40)
C4AulScriptEngine linked - 44005 lines, 0 warnings, 1 error
Parent - - By Zapper [de] Date 2013-07-15 16:43
You always have to declare variables (like "pClonk") before you use them. In ControlUse, pClonk is one of your parameters [ ControlUse(object pClonk) ]. In your MakePotion(ID) that parameter does not exist automatically.
You could either add the parameter yourself and pass pClonk from ControlUse to MakePotion or, and that would be the quicker way, you could just use the object which has your item in its inventory:
In MakePotion:
var pClonk = Contained();
if (!pClonk) return; // make sure nothing goes wrong!


The other thing:
Whether you throw the item away only depends on the return value of ControlUse:
Throw away:

func ControlUse() { ... return; }

or

func ControlUse() { ... }

Don't throw away:

func ControlUse() { ... return true; }


CreateMenu:
You have to open the menu for the Clonk (I think that is the problem):
pClonk->CreateMenu(GetID(), nil, nil, "Nothing to craft", 0, 0, false);
Parent - - By Sayned [kz] Date 2013-07-16 05:47
Okay. Now it appears like everything is fine. But menu is empty always.
I have added for test only two menu items: Heal Potion and Hurt Potion. I have got all ingredients clonk will need to have if he wants to craft a potion. Added func IsPotion() { return true; } and when I open menu it shows "Nothing to craft" Here is the code:

func ControlUse(object pClonk)
{
  pClonk->CreateMenu(GetID(), nil, nil, "Nothing to craft", 0, 0, false);
  var i = 0, obj;
  while (obj = GetDefinition(i++))
  {
    if (!obj->~IsPotion()) continue;
    AddMenuItem("Brew a healing potion", "MakePotion", HealPotion);
    AddMenuItem("Brew a hurt potion", "MakePotion", HurtPotion);
  }
  return true;
}

func MakePotion(ID)
{
  var pClonk = Contained();
  if (!pClonk) return;
  // check if OK with GetComponents
  var i, ID, potion_ID;
  while (ID = GetComponent(nil, i++, nil, potion_ID))
  {
    var amount_needed = GetComponent(ID, nil, nil, potion_ID);
    var amount_available = pClonk->ContentsCount(potion_ID);
    if (amount_available < amount_needed)
    {
      Message("You need %d %s!", amount_needed, potion_ID->GetName());
      Sound("Error");
      return;
    }
  // everything is ok!
  }
  // remove components
  amount_needed->RemoveObject();
  // start timer to create potion
  var effect = AddEffect("MakePotion",this,100,4,this, this->GetID());
  effect.potion = ID;
  return;
}

func FxMakePotionTimer(target, effect, time)
{
  // effects...
  target->CreateParticle("MagicFlash",0,0,0,10,100, RGBa(255,155,0,200));
  if (time > 36*3) // three seconds
  {
  CreateObject(effect.potion);
  return -1;
  }
}


Heal Potion needs a Sproutberry, Hurt Potion needs a Mushroom.

Probably I have used AddMenuEntry wrong way.

I have tried to use AddMenuItem("Brew a hurt potion", "MakePotion", HurtPotion); and then I tried to use AddMenuItem("Brew a hurt potion", MakePotion(), HurtPotion); and neither worked. First  way showed me an empty menu, second one made clonk throw this item away.
Parent - - By Zapper [de] Date 2013-07-16 07:14 Edited 2013-07-16 08:20
pClonk->AddMenuItem!

And because of the GetDefinition-loop, you don't have to hard-code the two potions. You could do something like:
pClonk->AddMenuItem(Format("Brew a %s", obj->GetName()), "MakePotion", obj);


PS:
  amount_needed->RemoveObject(); will not work, amount_needed is only a number (5->RemoveObject()?)
You need another loop, for example like the GetComponent loop above. Or you remember the components in the upper loop in an array, for example

PPS: With array I mean something like that: (arrays are like lists btw)
func MakePotion(ID)
{
  var pClonk = Contained();
  if (!pClonk) return;
  // remember components to be able to remove them later
  var components = [];

  // check if OK with GetComponents
  var i, ID, potion_ID;
  while (ID = GetComponent(nil, i++, nil, potion_ID))
  {
    var amount_needed = GetComponent(ID, nil, nil, potion_ID);
    var amount_available = pClonk->ContentsCount(potion_ID);
    if (amount_available < amount_needed)
    {
      Message("You need %d %s!", amount_needed, potion_ID->GetName());
      Sound("Error");
      return;
    }
    // add ID X times to the list
    while (amount_needed--)
      PushBack(components, ID); // PushBack adds something to the end of the list

  // everything is ok!
  }
  // remove components
  for (var comp in components)
    pClonk->FindContents(comp)->RemoveObject();

  // start timer to create potion
  var effect = AddEffect("MakePotion",this,100,4,this, this->GetID());
  effect.potion = ID;
  return;
}
Parent - - By Sayned [kz] Date 2013-07-16 10:17 Edited 2013-07-16 14:51
It appears to be working but there is another problem and I have no idea how to solve it.

ERROR: unknown identifier: MakePotion (MenuCommand in MagicTimes.ocf\Arena.ocf\MageContest.ocs\Script.c:0:0)

I looked at documentary, and in documentation was shown excactly same way to use it.

How it looks in menu:
But still it doesn't work and shows an error in logs.

Also I haven't found GetComponents in documentation, does it exist?
Parent - By Pyrit Date 2013-07-16 18:38 Edited 2013-07-16 18:41
I think your clonk creates a menu by

pClonk->CreateMenu(GetID(), nil, nil, "Nothing to craft", 0, 0, false);

now MakePotion() is called in the clonk because the second parameter is nil. See in the doku:

>command_object:


>Object to receive the menu command (see AddMenuItem). Can be nil in local calls.

You could try

var me = this;
pClonk->CreateMenu(GetID(), me, nil, "Nothing to craft", 0, 0, false);
Parent - - By Zapper [de] Date 2013-07-16 18:47
GetComponent (without -s!)

What Pyrit says it probably your problem:
Your menu does not know where it finds "MakePotion" and you have to tell it by using the command_object parameter of CreateMenu:
[same as Pyrit, just without "me"-variable: ]pClonk->CreateMenu(GetID(), this, nil, "Nothing to craft", 0, 0, false);
Parent - - By Pyrit Date 2013-07-16 19:05
Mmmmh I wasn't sure if the this is called by pClonk then =?
Parent - By Zapper [de] Date 2013-07-16 19:28
It isn't, this will be the item then :)
Parent - - By Sayned [kz] Date 2013-07-17 06:18
It was my problem, but now I'm totally dissapointed with another thing. I tried to figure out how "MakePotion" works and now I have no idea what to do if I want to fix it.

What is happening: When I choose a potion to brew, clonk starts brewing, but then nothing happens.
Log:

WARNING: call to MakePotion gives 2 parameters, but only 1 are used (MenuCommand in MagicTimes.ocd\Potions.ocd\AlchemistKit.ocd\Script.c:0:0)

Code:
func ControlUse(object pClonk)
{
  pClonk->CreateMenu(GetID(), this, 1, "Nothing to craft", 0, 0, false);
  var i = 0, obj;
  while (obj = GetDefinition(i++))
  {
    if (!obj->~IsPotion()) continue;
    pClonk->AddMenuItem(Format("Brew a %s", obj->GetName()), "MakePotion", obj);
  }
  return true;
}

func MakePotion(ID)
{
  var pClonk = Contained();
  if (!pClonk) return;
  // remember components to be able to remove them later
  var components = [];
  // check if OK with GetComponents
  var i, ID, potion_ID;
  while (ID = GetComponent(nil, i++, nil, potion_ID))
  {
    var amount_needed = GetComponent(ID, nil, nil, potion_ID);
    var amount_available = pClonk->ContentsCount(potion_ID);
    if (amount_available < amount_needed)
    {
      Message("You need %d %s!", amount_needed, potion_ID->GetName());
      Sound("Error");
      return;
    }
    // add ID X times to the list
    while (amount_needed--)
    PushBack(components, ID); // PushBack adds something to the end of the list
    // everything is ok!
  }
  // remove components
  for (var comp in components)
  pClonk->FindContents(comp)->RemoveObject();
  // start timer to create potion
  var effect = AddEffect("BrewPotion",pClonk,100,4, nil, GetID());
  effect.potion = ID;
  return;
}

func FxBrewPotionTimer(target, effect, time)
{
  // effects...
  target->CreateParticle("MagicFlash",0,0,RandomX(-10,10),RandomX(-10,10),100, RGBa(255,155,0,200));
  if (time > 36*3) // three seconds
  {
  CreateObject(effect.potion);
  return -1;
  }
}

Also, effect will spawn object not in inventory, right?

Or writing like this is not necessary?
  {
  target->CreateContents(effect.potion);
  return -1;
  }

I'm sorry for asking big counts of questions >.<
Parent - - By Zapper [de] Date 2013-07-17 10:09
If you want to silence the warning, you could add another parameter to MakePotion, even if you don't use it. Your menu always passes two parameters, even if you don't need them at the moment:
func MakePotion(ID, par)

I think I know why your potion is not created: it is created but at position 0|0 (because your effect is not "relative" to an object), so both:
CreateObject(effect.potion, target->GetX(), target->GetY()); and target->CreateObject(effect.potion) would help.

But you probably want to spawn it in the inventory, like you said:
target->CreateContents(effect.potion); looks good. Does it work?
Parent - - By Sayned [kz] Date 2013-07-17 11:40
Potion doesn't spawn, even if I write target->CreateContents(effect.potion);
If potion could spawn on [0;0] it could fall down, right? But it doesn't. Warning gone, but potion still doesn't spawn. Item doesn't take any components needed to brew a potion, and doesn't give me any potions.

Log doesn't say anything.

Item starts "BrewPotion" effect even if clonk has not got any components of the potion. Seems like MakePotion doesn't recieve information about potion I have choosen.
Parent - - By Zapper [de] Date 2013-07-17 11:57
I think I know where the problem is:
You have two different variables named "ID" (one from func MakePotion(ID) and one from var i, ID, potion_ID) in your function and one "potion_ID".
First "ID" is what you want: the ID of the potion from the menu.
Then you overwrite it with while (ID = GetComponent(nil, i++, nil, potion_ID)) (note that potion_ID is undefined!!)

You should always use unique names for your variables :)

For example, you could name the parameter "potion_ID" (func MakePotion(potion_ID)) and then throw out the re-definition of "potion_ID" in the line var i, ID, potion_ID; (so that it only is var i, ID;).
Then you have to do effect.potion = potion_ID;, too, of course
Parent - - By Sayned [kz] Date 2013-07-17 12:50 Edited 2013-07-17 14:09
Ok, I have changed some variables, removed potion_ID and it works, then I found this funny bug:



It wasn't very hard to fix this bug.

When I fixed it, I have got another bug: Clonk doesn't remove ingredients to brew potions, he doesn't remove them from inventory and doesn't need them if he want to brew a potion, like he is brewing them by using air *lol*
Here's the code
func MakePotion(ID, par)
{
  var pClonk = Contained();
  if (!pClonk) return;
  // remember components to be able to remove them later
  var components = [];
  // check if OK with GetComponents
  var i, pID, ID;
  while (pID = GetComponent(nil, i++, nil, ID))
  {
    var amount_needed = GetComponent(ID, nil, nil, pID);
    var amount_available = pClonk->ContentsCount(ID);
    if (amount_available < amount_needed)
    {
      Message("You need %d %s!", amount_needed, ID->GetName());
      Sound("Error");
      return;
    }
    // add ID X times to the list
    while (amount_needed--)
    PushBack(components, ID); // PushBack adds something to the end of the list
    // everything is ok!
  }
  // remove components
  for (var comp in components)
  pClonk->FindContents(comp)->RemoveObject();
  // start timer to create potion
  var effect = AddEffect("BrewPotion",pClonk,100,4, nil, GetID());
  effect.potion = ID;
  return;
}


Probably I have changed wrong variable to pID or ID.
Parent - - By Zapper [de] Date 2013-07-17 16:23 Edited 2013-07-17 16:37
It's usually good to use clear variable names that you can quickly understand ;)
For example "amount_needed" and "amount_available" instead of "n" and "a";


// check if OK with GetComponents
  var i, component_ID;
  while (component_ID = GetComponent(nil, i++, nil, potion_ID))
  {
    var amount_needed = GetComponent(component_ID, nil, nil, potion_ID);
    var amount_available = pClonk->ContentsCount(component_ID);
    if (amount_available < amount_needed)
    {
      Message("You need %d %s!", amount_needed, component_ID->GetName());
      Sound("Error");
      return;
    }
    // add ID X times to the list
    while (amount_needed--)
    PushBack(components, component_ID); // PushBack adds something to the end of the list
    // everything is ok!
  }


If you really want to use "pID" and "ID", you could replace all "component_ID" by pID and all "potion_ID" by ID in the code above. I hope I didn't get confused on the way and it should work now :)
Parent - - By Sayned [kz] Date 2013-07-17 18:51
Yes, finally it works!

Thank you so much! ;)
Parent - By Zapper [de] Date 2013-07-17 19:31
No problem!
Parent - - By Sayned [kz] Date 2013-08-10 09:20 Edited 2013-08-10 14:25
Alchemy works fine, but I have got another question when was making one of my maps.

I throught that this question is too small for new topic, so I write it here.

I started making another map and almost finished it. Then I realized that stars from OC may cause giant lags so there must be way to remove them. I have realized that all I need to do is to make a menu like it was in one mappack from CR and place here option, which will allow host remove stars from map. I have made menu, I have made two menu items: "Stars On/Off", "Ok". But they don't work.

Code looks like that:
protected func InitializePlayer(int plr)
{
  if(plr==0)
    {
      var menu = CreateObject(Rock, LandscapeWidth()/2, LandscapeHeight()/2+150);
      SetCursor(plr, menu);
      menu.Visibility = VIS_None;
      menu->CreateMenu(WaterGuardSpell, nil, nil, "Options", nil, nil, true);
      menu->AddMenuItem("Stars (may cause lags)", "PlaceStars", AirGuardSpell);
      menu->AddMenuItem("Ok", "Done", Icon_Ok);
    }
  //SetPlayerZoomByViewRange(plr, LandscapeWidth(), 0, PLRZOOM_LimitMax);
  SetPlayerZoomByViewRange(plr, 0, LandscapeHeight(), PLRZOOM_LimitMax);
  SetPlayerViewLock(plr, true);
  return JoinPlayer(plr);
}


When I open my scenario I can see menu with both items in, but when I try to click them, log say:
ERROR: unknown identifier: PlaceStars (MenuCommand in MagicTimes.ocf\Arena.ocf\Astral.ocs\Script.c:0:0)
ERROR: unknown identifier: Done (MenuCommand in MagicTimes.ocf\Arena.ocf\Astral.ocs\Script.c:0:0)


Also, very small question:
Is there any way to make team own a building? Or how to make first player in team become owner of specified building?
Parent - - By Caesar [de] Date 2013-08-10 21:58 Edited 2013-08-11 19:03
Only players can own buildings, not teams. If I wanted to make the first player owner of the building, I'd do it like:

building->SetOwner(GetPlayerByTeam(team, 0));

global func GetPlayerByTeam(int team, int index) {
  var i;
  for(i = 0; i < GetPlayerCount(); ++i) {
    var player = GetPlayerByIndex(i);
    if(GetPlayerTeam(player) == i)
      if(!index)
        return player;
      else
        --index;
  }
  return NO_OWNER; // [EDIT]
}
Parent - - By Sayned [kz] Date 2013-08-11 08:53 Edited 2013-08-11 09:36
Ok, thank you.
What about menu? I have looked at other examples of menu, and they look exactly like my, but with other functions.
Maybe I have to change something in functions?
Parent - - By Caesar [de] Date 2013-08-11 19:08
Hmm, Done and PlaceStars are global (i.e. not public) functions? Otherwise, you'll have to work with something ugly as Format("Object(%d)->PlaceStars()", ObjectNumber(call_contextobject)) for the command.
Parent - - By Sayned [kz] Date 2013-08-11 19:59
They're not public and not global :D

But after I have changed them to global, both functions started to work.

Thank you!
Parent - By Caesar [de] Date 2013-08-11 20:13
In hindsight, Done is probably not a good name for a global function.
Up Topic General / Help and Questions / [QUESTION] Potion brewing

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill