Aller au contenu

Photo

area where most items can be used indefinitely [SOLVED]


  • Veuillez vous connecter pour répondre
5 réponses à ce sujet

#1
FanfanVilperdue

FanfanVilperdue
  • Members
  • 18 messages
Hello,

  Here's what I'm trying to do.

  1/ In a custom area (which is an arena), the player can access three "modes":
  • Hero: without companions.
  • Party: normal 4-member party (or less).
  • Extended party: the players controls only the hero, but all pool members are here also as allies.
  2/ In this area, the hero and the companions can use most items indefinitely
(ammunitions, poultices, etc., in fact everything except items with
permanent effects like tomes).

  #1 is done easily, #2 is a lot trickier, and #1 and #2 together give me the hell of a headache!

  For the "extended party" mode, here is a short version of the script (which works):
    UT_PartyStore();

    object [] aPool = GetPartyPoolList();
    int nSizePool = GetArraySize(aPool);
    int iPool;
    object oCurrent;                           

    for(iPool = 0; iPool < nSizePool; iPool++)
    {
        oCurrent = aPool[iPool];
        if(!IsPartyMember(oCurrent))
        {
            WR_SetObjectActive(oCurrent, TRUE);
            UT_LocalJump(oCurrent, [i]somewhere in the area[/i]);
            SetGroupId(oCurrent, GROUP_PC);
            SetImmortal(oCurrent, FALSE);
            SetFollowPartyLeader(oCurrent, TRUE);
        }
    }

  Under the EVENT_TYPE_ENTER area event, we change the event-handling script for all pool members, in order to manage items properly:
    object[] aPool = GetPartyPoolList();
    int nSizePool = GetArraySize(aPool);
    int iPool;
    object oCurrent;
    for (iPool = 0; iPool < nSizePool; iPool++)
    {
        oCurrent = aPool[iPool];
        if (!IsSummoned(oCurrent))
        {
            EnablevEvent(oCurrent, TRUE, EVENT_TYPE_INVENTORY_REMOVED);
            SetEventScript(oCurrent, R"custom_party_member.ncs");
        }
    }

  And here is a short version of the script "custom_party_member".
#include [i]blablabla...[/i]

const int COMMAND_TYPE_EXECUTE_EFFECT = 32;
const int ITEM_ABILITY_UNIQUE_POWER_SINGLE_USE = 200202;
const int ITEM_TALENT_POINT = 200258;
const int ITEM_SKILL_POINT = 200259;
const int ITEM_ATTRIBUTE_POINT = 200260;

void main()                                            
{                                 
    event ev = GetCurrentEvent();
    int nEventType = GetEventType(ev);
    int bEventHandled = FALSE;

    switch (nEventType)
    {
        case EVENT_TYPE_INVENTORY_REMOVED:
        {
            // The item being removed
            object oItem = GetEventObject(ev, 0);
            int nItemType = GetItemType(oItem);

            // We will check the current command 
            // because we want to do nothing  
            // when the item is manually destroyed
            // in the inventory GUI.

            command cCommand = GetCurrentCommand(OBJECT_SELF);
            int nCommandType = GetCommandType(cCommand);

            //***************
            // Ammunitions
            //***************
            // When the character is using ammunition, one 
            // can notice that oItem is always invalid
            // (and not an ammo type item). Thus, we cannot 
            // get the item via oItem. 
            if (
                (nItemType == ITEM_TYPE_INVALID) &&
                (
                    (nCommandType == COMMAND_TYPE_ATTACK) ||
                    (nCommandType == COMMAND_TYPE_USE_ABILITY)
                )
            )
            {
                object oAmmunition =
                    GetItemInEquipSlot(INVENTORY_SLOT_RANGEDAMMO,
                        OBJECT_SELF);
                if (IsObjectValid(oAmmunition))
                    SetItemStackSize(oAmmunition, 
                        GetItemStackSize(oAmmunition) + 1);
            }

            //***************
            // Other (not ammunitions) ==> poultices, poisons, etc.
            //***************
            // Normally, the current command should 
            // be COMMAND_TYPE_USE_ABILITY.
            // But one can notice that sometimes, it is 
            // COMMAND_TYPE_EXECUTE_EFFECT.
            else if (
                (nCommandType == COMMAND_TYPE_USE_ABILITY) ||
                (nCommandType == COMMAND_TYPE_EXECUTE_EFFECT)
            )
            {
                object oNewOwner = GetEventCreator(ev);
                int nAbility = GetItemAbilityId(oItem);
                if
                (
                    (nAbility != ITEM_ABILITY_UNIQUE_POWER_SINGLE_USE)
                    && (nAbility != ITEM_TALENT_POINT)
                    && (nAbility != ITEM_SKILL_POINT)
                    && (nAbility != ITEM_ATTRIBUTE_POINT)
                )
                {
                    MoveItem(OBJECT_SELF, OBJECT_SELF, oItem);
                }
            }

            break;
        }
    }

    if (!bEventHandled)
    {
        HandleEvent(ev, RESOURCE_SCRIPT_PLAYER_CORE);
    }
}

  And now, the questions!
  • For ammunitions, it works except if the character is using special ammo, like ice arrow, and had exactly one left. I understand why, but I can see no better way to do.
  • For quick items, it works except if it is the last item in the stack. In my real script, I have a workaround using a M2DA, which allows emulating a StringToResource for a certain list of items. But it will never work for items created by other modders or included in DLCs. Does someone see a better way to do it?
  • Last but not least, all this doesn't work at all for companions in "extended party" mode! According to my tests, they use the "custom_party_member" script indeed, but the EVENT_TYPE_INVENTORY_REMOVED is never fired!

Sorry for this long message and very big thanks in advance to anyone who has an clue about it!!!

Modifié par FanfanVilperdue, 27 avril 2010 - 02:11 .


#2
_L_o_B_o_

_L_o_B_o_
  • Members
  • 117 messages

FanfanVilperdue wrote...

[*]For ammunitions, it works except if the character is using special ammo, like ice arrow, and had exactly one left. I understand why, but I can see no better way to do.


I have not tested your code yet, but I guess that the problem with the ammunition is that EVENT_TYPE_INVENTORY_REMOVED is fired after the item has been removed from the creature's inventory, so SetItemStackSize() is not working because there is no item. Is that right? In this case, can't you add a new item to the inventory instead of changing its stack size?

Modifié par _L_o_B_o_, 25 avril 2010 - 08:15 .


#3
FanfanVilperdue

FanfanVilperdue
  • Members
  • 18 messages

_L_o_B_o_ wrote...
EVENT_TYPE_INVENTORY_REMOVED is fired after the item has been removed from the creature's inventory, so SetItemStackSize() is not working because there is no item.


Yes, that's exactly the point, and it would be a solution to add a new item (both for ammunitions and quick items). But it would use CreateItemOnObject(resource rItemFileName, object oTarget, etc.), so it would need a resource as argument. Thus, we'd need something like :
string sResRef = GetResRef(oItem);
resource rItem = [color="#ff0000"]StringToResource[/color](sResRef);
CreateItemOnObject(rItem, OBJECT_SELF, 1, "", TRUE, TRUE);

The first problem is that the famous "StringToResource" doesn't exist... The second problem is that, with EVENT_TYPE_INVENTORY_REMOVED for ammunitions, GetEventObject(ev, 0) doesn't give the type of ammunition but only an invalid item (I don't know why).

In fact, for quick items, my full code is the following.
            //***************
            // Other (not ammunitions) ==> poultices, poisons, etc.
            //***************
            else if (
                (nCommandType == COMMAND_TYPE_USE_ABILITY) ||
                (nCommandType == COMMAND_TYPE_EXECUTE_EFFECT)
            )
            {
                string sResRef = GetResRef(oItem);
                resource rItem =
                    GetM2DAResource(-1, sResRef, 1, "infinite_items");

                // Normal case: the item is in "infinite items" 2da.
                // It works.
                if (rItem != INVALID_RESOURCE)
                {
                    CreateItemOnObject(rItem, OBJECT_SELF, 
                        1, "", TRUE, TRUE);
                    PrintToLog("custom_party_member: "
                        + "quick item found in M2DA "
                        + "(CreateItemOnObject)");

                // Exception: the item is not in "infinite items" 2da.
                // It works except if it was the last item of this kind 
                // (which is a bug).
                } else
                {
                    int nAbility = GetItemAbilityId(oItem);
                    if
                    (
                        (nAbility != ITEM_ABILITY_UNIQUE_POWER_SINGLE_USE)
                        && (nAbility != ITEM_TALENT_POINT)
                        && (nAbility != ITEM_SKILL_POINT)
                        && (nAbility != ITEM_ATTRIBUTE_POINT)
                    )
                    {
                        MoveItem(OBJECT_SELF, OBJECT_SELF, oItem);
                        PrintToLog("custom_party_member: " 
                            + "quick item not found in M2DA " 
                            + "(attempting MoveItem).");
                    } else
                    {
                        PrintToLog("custom_party_member: "
                            + "quick item not found in M2DA "
                            + "(no action).");
                    }
                }
            }

And "infinite_items" M2DA looks like this:
ID  ; gen_im_qck_health_101     ; gen_im_qck_health_201     ; etc.
int ; string                    ; string                    ; etc.
1   ; gen_im_qck_health_101.uti ; gen_im_qck_health_201.uti ; etc.
It allows to emulate a StringToResource, but it's not a very clean solution... :(

Modifié par FanfanVilperdue, 25 avril 2010 - 10:17 .


#4
_L_o_B_o_

_L_o_B_o_
  • Members
  • 117 messages
This is more a workaround, but since usable items in your area are infinite, you could do the following. When the player enters the area, increment the current stack size of usable items in one unit. After the player leaves the area, decrement in one unit. So there will never be just one unit left.



I have not checked the code needed to do it, so maybe it can't be done...

#5
FanfanVilperdue

FanfanVilperdue
  • Members
  • 18 messages
  Thanks a lot for the idea! As you will see below, it's a bit far-fetched to get working, but it lead me on the way!

  In fact, there are some points of attention:
  • What happens if they get a new item? Not a problem for me, since it's not supposed to happen in this area (there are no bodybags, for instance).
  • What happens if a crafting talent is used?
  This can be managed, but there is also a problem: if the character has exactly the maximum number of this item (e.g. 99 health poultices), we cannot increment the stack size. There is a workaround for this : use an item local variable to register if we incremented the stack size or not...

  But but but... if we accept to use an item local variable, there is another way to do it: register the initial stack size in the local variable, and set the stack size to maximum.

void change_script_extended_party(resource rScript){[i]blablabla...[/i]}

/** @brief EXAMPLE: infinite_items_mode(TRUE);
*
* Switch the "infinite items" mode, where ammos and quick items
* (except tomes) can be used indefinitely. CAUTION, this script 
* doesn't check if the mode was already on or off. If called with 
* TRUE whereas the mode is already on, it can lead to unexpected 
* results.
*
* @param bModeOn - Whether the mode should be set on or off.
**/
void infinite_items_mode(int bModeOn)
{    
    // Change the script (done in the end if bModeOn == TRUE)
    if (!bModeOn)
        change_script_extended_party(RESOURCE_SCRIPT_PLAYER_CORE);
    
    object[] aPool = GetPartyPoolList();
    int nPoolSize = GetArraySize(aPool);
    int iCharacter;
    object oCharacter;
    object[] aInventory;
    int nInventorySize;
    int iItem;
    object oItem;
    int nItemType;
    int nAbility;

    for(iCharacter = 0; iCharacter < nPoolSize; iCharacter++)
    {
        oCharacter = aPool[iCharacter];
        if(IsHero(oCharacter))
        {
            aInventory = GetItemsInInventory(oCharacter,
                GET_ITEMS_OPTION_ALL, 0, "", FALSE);
        } else
        {
            aInventory = GetItemsInInventory(oCharacter,
                GET_ITEMS_OPTION_EQUIPPED, 0, "", FALSE);
        }
        nInventorySize = GetArraySize(aInventory);

        for(iItem = 0; iItem < nInventorySize; iItem++)
        {
            oItem = aInventory[iItem];
            nItemType = GetItemType(oItem);
            nAbility = GetItemAbilityId(oItem);
            if
            (
                (nItemType == ITEM_TYPE_AMMO) ||
                (
                    (nAbility != ITEM_ABILITY_UNIQUE_POWER_SINGLE_USE)
                    && (nAbility != ITEM_TALENT_POINT)
                    && (nAbility != ITEM_SKILL_POINT)
                    && (nAbility != ITEM_ATTRIBUTE_POINT)
                    && (nAbility != INVALID_ITEM_ABILITY)
                )
            )
            {   
                if (bModeOn)
                {   
                    // Store the current stack size
                    SetLocalInt(oItem, ITEM_COUNTER_1, 
                        GetItemStackSize(oItem));
                    // Set the stack size to maximum
                    SetItemStackSize(oItem, 
                        GetMaxItemStackSize(oItem));
                } else {
                    // Restore the stack size
                    SetItemStackSize(oItem, 
                        GetLocalInt(oItem, ITEM_COUNTER_1));
                }
            }
        }
    }

    // Change the script (done in the beginning if bModeOn == FALSE)
    if (bModeOn)
        change_script_extended_party(R"custom_party_member.ncs");
}

  In the area script, we use infinite_items_mode(TRUE) on event ENTER and infinite_items_mode(FALSE) on event EXIT. In the module script, we manage the GUIs (inventory, crafting):
case EVENT_TYPE_GAMEMODE_CHANGE:
{     
    // In this module, "infinite items" areas are marked 
    // with AREA_GAME_MODE_OVERRIDE.    
    int bGameModeOverride = 
        GetLocalInt(GetArea(GetHero()), AREA_GAME_MODE_OVERRIDE);
    if (bGameModeOverride)
    {   
        int nNewGameMode = GetEventInteger(ev, 0);
        int nOldGameMode = GetEventInteger(ev, 1);
        if ((nOldGameMode != GM_GUI) && (nNewGameMode == GM_GUI))
        {                    
            infinite_items_mode(FALSE);
        } 
        else if ((nOldGameMode == GM_GUI) && (nNewGameMode != GM_GUI))
        { 
            infinite_items_mode(TRUE);
        }
    }
    break;
}

  Consequently, the test on the current command can be removed in "custom_party_member".
case EVENT_TYPE_INVENTORY_REMOVED:
{   
    object oItem = GetEventObject(ev, 0);
    int nItemType = GetItemType(oItem);

    // Ammunitions
    if (nItemType == ITEM_TYPE_INVALID)
    {
        object oAmmunition =
            GetItemInEquipSlot(INVENTORY_SLOT_RANGEDAMMO, OBJECT_SELF);
        if (IsObjectValid(oAmmunition))
            SetItemStackSize(oAmmunition, 
                GetItemStackSize(oAmmunition) + 1);
    }

    // Quick items (poultices, etc.)
    else
    {
        int nAbility = GetItemAbilityId(oItem);
        if
        (
            (nAbility != ITEM_ABILITY_UNIQUE_POWER_SINGLE_USE)
            && (nAbility != ITEM_TALENT_POINT)
            && (nAbility != ITEM_SKILL_POINT)
            && (nAbility != ITEM_ATTRIBUTE_POINT)
        )
        {   
            object oNewOwner = GetEventCreator(ev);
            MoveItem(oNewOwner, OBJECT_SELF, oItem);
        } 
    }

    break;
}

  Almost works like this! The only problem is for "extended party" mode: since the event INVENTORY_REMOVED doesn't fire, there will be a bug if a character uses 99 arrows while in the area... Does anyone has a idea about it?

  Thank again for your help _L_o_B_o_!

Modifié par FanfanVilperdue, 27 avril 2010 - 10:26 .


#6
FanfanVilperdue

FanfanVilperdue
  • Members
  • 18 messages

FanfanVilperdue wrote...
The only problem is for "extended party" mode: since the event INVENTORY_REMOVED doesn't fire, there will be a bug if a character uses 99 arrows while in the area...


  In fact, there is also a workaround for this: in "custom_party_member" script, we can just add the following lines, whatever the event:
    object oAmmo =
        GetItemInEquipSlot(INVENTORY_SLOT_RANGEDAMMO, OBJECT_SELF);
    if (IsObjectValid(oAmmo))
        SetItemStackSize(oAmmo, GetMaxItemStackSize(oAmmo));

  My hands are dirty... :unsure: