Portal-Zone Gothic-Zone Gothic II-Zone Gothic 3-Zone Gothic 4-Zone Modifikationen-Zone Download-Zone Foren-Zone RPG-Zone Almanach-Zone Spirit of Gothic

 

Ergebnis 1 bis 9 von 9
  1. Beiträge anzeigen #1 Zitieren
    Local Hero
    Registriert seit
    Feb 2017
    Beiträge
    270
     
    F a w k e s ist offline

    G1 Followers (inventory management)

    Hello folks,
    Today I would like to share scripts which improve players experience with Followers in Gothic 1.
    Some bits and pieces (torch detection in hand, torch use by NPC, improved G1 trading, where you don't have to take care of ore, new ZS states triggered on player, trade multiplier values and more) were already presented in different threads on this forum, I will repost them, as code for followers is closely tied with these.

    With this package you can:
    Instruct follower to wait for you / follow you.
    Manage inventory of your follower.

    Followers behavior will be more immersive:
    If player is sneaking, they will be sneaking as well.
    During night they will use torches. (if they have them in inventory) They will hide torches, if player is in a portal room or sneaking.

    Most interesting part is I believe inventory management of your follower. This is how it works:
    1. Followers have available dialog option '(inventory management)' - this opens standard Trading
    2. In this 'trade' both sell & buy multiplier value is set to 0. This way trading serves as an item exchange interface.
    3. When trade is closed (detected by _HOOK_TRADE_ONEXIT), ZS state 'ZS_InvManagement' is applied to hero. ZS state will wait for InfoManager_HasFinished ().
    4. ZS_InvManagement then uses NPC_SetAsPlayer function on follower. (same as if you would use 'o' in marvin)
    5. ZS_InvManagement opens inventory - player can then equip/unequip/drop/eat/drink items in Followers inventory.
    6. ZS_InvManagement + several other hooks are checking if player closed inventory.
    7. If inventory is closed, ZS_InvManagement script will use NPC_SetAsPlayer on 'true hero' and will start ZS_Talk on follower to reopen dialog options.

    [Video]

    Complete code:

    Variables and constants:

    Code:
    INSTANCE Follower (C_NPC);
    INSTANCE Guide (C_NPC);
    
    var int InvManagement_Dialog;
        const int cIM_Dialog_Default        = 0;
        const int cIM_Dialog_Trade        = 1;
        const int cIM_Dialog_Trade_Closing    = 2;
        const int cIM_Dialog_Inventory        = 3;
        const int cIM_Dialog_Inventory_Reopen    = 4;
        const int cIM_Dialog_Exit        = 5;
    Couple of required (and very useful) functions - borrowed from this forum:

    Code:
    //Author: Dalai Zoll
    //https://forum.worldofplayers.de/forum/threads/1090721-Testschleichen?p=17909902&viewfull=1#post17909902
    /*===================================================================
    NPC_GetWalkMode 
    Den Walkmode eines NPC zurückgeben.
    "RUN" - normales laufen
    "WALK" - gehen
    "SNEAK" - schleichen
    "" - schwimmen, durch Wasser waten
    ==================================================================*/
    
    const int NPC_INWATER = 3;
    
    FUNC INT NPC_GetWalkMode (VAR C_NPC slf)
    {
        const int oCAniCtrl_Human__GetWalkModestring = 6432560;    //00622730  
        var oCNpc oCslf; oCslf = Hlp_GetNPC (slf);
    
        CALL_RetValIszString();
        CALL__thiscall(oCslf.AniCtrl, oCAniCtrl_Human__GetWalkModestring);
    
        var string result;
        result = CALL_RetValAszstring ();
    
        if (Hlp_StrCmp (result, "RUN"))        {     return NPC_RUN;     } else
        if (Hlp_StrCmp (result, "WALK"))    {     return NPC_WALK;    } else
        if (Hlp_StrCmp (result, "SNEAK"))    {    return NPC_SNEAK;    } else
        if (Hlp_StrCmp (result, ""))        {    return NPC_INWATER;    };
        
        //MOD - just in case, return something.
        return -1;
    };
    
    //Author: Sektenspinner
    //https://forum.worldofplayers.de/forum/threads/942338-Raum-einem-NPC-einer-Gilde-zuweisen?p=15078301&viewfull=1#post15078301
    FUNC STRING Wld_GetPlayerPortalRoom ()
    {
        MEM_InitGlobalInst();
        var oCPortalRoomManager portalman;
        portalman = MEM_PtrToInst (MEM_Game.portalman);
        var oCPortalRoom playerRoom;
    
        if (portalMan.curPlayerPortal)
        {
            playerRoom = MEM_PtrToInst (portalMan.curPlayerPortal);
    
            return playerRoom.portalName;
        } else
        {
            return "";
        };
    };
    
    //Author: OrcWarriorPL
    //https://forum.worldofplayers.de/forum/threads/879891-Skriptpaket-Ikarus-2/page12?p=14836995&viewfull=1#post14836995
    FUNC VOID Trade_ChangeSellMultiplier(var int mul)
    {    var int ptr;
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.dlgTrade
        ptr = MEM_ReadInt (ptr + 260);    //dlgTrade.oCViewDialogItemContainer
        MEM_WriteInt (ptr + 268, mul);    //oCViewDialogItemContainer.Multiplier = mul
    };
    
    FUNC VOID Trade_ChangeBuyMultiplier(var int mul)
    {    var int ptr;
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.dlgTrade
        ptr = MEM_ReadInt (ptr + 252);    //dlgTrade.oCViewDialogStealContainer
        MEM_WriteInt (ptr + 268, mul);    //oCViewDialogStealContainer.Multiplier = mul    
    };
    Torch handling - allows you to recognize if NPC is holding torch. By calling NPC_TorchSwitchOn / NPC_TorchSwitchOff you can put in / remove from hand of NPC torch.

    Code:
    //Originally posted here (slightly rewritten):
    //https://forum.worldofplayers.de/forum/threads/1532847-Fackel-via-Tastendruck-in-die-Hand?p=26018719&viewfull=1#post26018719
    /***
        These functions will fill global variable item 'pointing' to specific item in inventory
        NPC_GetInventoryCategoryItem - invCategory only
        NPC_GetInventoryItem - all invCategory
    ***/
    FUNC INT NPC_GetInventoryCategoryItem (var C_NPC slf, var int invCategory, var int itm)
    {
        var int p;
        var int itmInstance;
        
        var int itmSlot; itmSlot = 0;
        
        //Loop
        p = MEM_StackPos.position;
    
        //Je nejaky item v prvom slote tejto kategorie?
        if (NPC_GetInvItemBySlot (slf, invCategory, itmSlot) > 0)
        {
            itmInstance = Hlp_GetInstanceID (item);
    
            if (itm == itmInstance)
            {
                return TRUE;
            } else
            {
                itmSlot = itmSlot + 1;
                MEM_StackPos.position = p;
            };
        };
        
        return FALSE;
    };
    
    FUNC INT NPC_GetInventoryItem (var int slfInstance, var int itm)
    {
        var C_NPC slf;
        slf = Hlp_GetNPC (slfInstance);
        
        if (NPC_GetInventoryCategoryItem (slf, INV_WEAPON, itm) == FALSE)
        {
            if (NPC_GetInventoryCategoryItem (slf, INV_ARMOR, itm) == FALSE)
            {
                if (NPC_GetInventoryCategoryItem (slf, INV_RUNE, itm) == FALSE)
                {
                    if (NPC_GetInventoryCategoryItem (slf, INV_MAGIC, itm) == FALSE)
                    {
                        if (NPC_GetInventoryCategoryItem (slf, INV_FOOD, itm) == FALSE)
                        {
                            if (NPC_GetInventoryCategoryItem (slf, INV_POTION, itm) == FALSE)
                            {
                                if (NPC_GetInventoryCategoryItem (slf, INV_DOC, itm) == FALSE)
                                {
                                    if (NPC_GetInventoryCategoryItem (slf, INV_MISC, itm) == FALSE)
                                    {
                                        return FALSE;
                                    };
                                };
                            };
                        };
                    };
                };
            };
        };
        
        return TRUE;
    };
    
    /***
        Returns pointer to item in slotName
    ***/
    FUNC INT NPC_GetSlotItem (var int slfInstance, var string slotName) 
    {
        var C_NPC slf;
        slf = Hlp_GetNPC (slfInstance);
        
        //0068F4F0  .text     Debug data           ?GetSlotItem@oCNpc@@QAEPAVoCItem@@ABVzSTRING@@@Z
        const int oCNPC__GetSlotItem = 6878448;
        
        CALL_zStringPtrParam (slotName);
        CALL__thiscall (_@ (slf), oCNPC__GetSlotItem);
        return CALL_RetValAsPtr ();
    };
    
    /***
        NPC will use item
    ***/
    FUNC VOID NPC_UseItem (var int slfInstance, var int itmInstance)
    {
        //00698810  .text     Debug data           ?UseItem@oCNpc@@QAEHPAVoCItem@@@Z
        const int oCNPC__UseItem_G1 = 6916112;
    
        //0x0073BC10 public: int __thiscall oCNpc::UseItem(class oCItem *)
        const int oCNPC__UseItem_G2 = 7584784;
        
        var C_NPC slf;
        slf = Hlp_GetNPC (slfInstance);
    
        CALL_PtrParam (MEM_InstToPtr (itmInstance));
        CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNPC__UseItem_G1, oCNPC__UseItem_G1));
    };
    
    /***
        If NPC has in hand ItLsTorchBurning / ItLsTorchBurned - it will put it back to inventory, otherwise it will  lit ItLsTorch / ItLsTorchBurned and put ItLsTorchBurning in hand
    ***/
    FUNC VOID NPC_TorchSwitchOnOff (var int slfInstance)
    {
        var int ptr;
        var C_NPC slf;
        
        slf = Hlp_GetNPC (slfInstance);
    
        //Get pointer to ZS_LEFTHAND
        ptr = NPC_GetSlotItem (slf, "ZS_LEFTHAND");
        
        //Is there anything in hand?
        if (ptr) {
            var C_Item itm;
            itm = _^ (ptr);
            
            //Is it ItLsTorchBurning / ItLsTorchBurned?
            if (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurning))
            || (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurned))
            {
                //Use itm - will put ItLsTorch back to inventory and remove from inventory
                NPC_UseItem (slf, itm);
                NPC_RemoveInvItem (slf,  ptr);
            };
        } else
        {
            //Careful! NPC_GetInventoryItem is not actually returning pointer. I am using ptr variable just to 'save' variables :)
            
            //Search for ItLsTorchBurned
            ptr = NPC_GetInventoryItem (slf, ItLsTorchBurned);
            
            //Search for ItLsTorch
            if (!ptr) {
                ptr = NPC_GetInventoryItem (slf, ItLsTorch); 
            };
            
            //Fill item with pointer to some ItLsTorch in inventory
            if (ptr) {
                //Use it - puts ItLsTorchBurning in hand and remove from inventory
                NPC_UseItem (slf,  item);
                NPC_RemoveInvItem (slf,  _@ (item));
            };
        };
    };
    
    /***
        If NPC has torch in inventory function will put torch in NPCs hand
    ***/
    FUNC VOID NPC_TorchSwitchOn (var int slfInstance)
    {
        var int ptr;
        var C_NPC slf;
        
        slf = Hlp_GetNPC (slfInstance);
    
        //Get pointer to ZS_LEFTHAND
        ptr = NPC_GetSlotItem (slf, "ZS_LEFTHAND");
        
        if (ptr) {
            var C_Item itm;
            itm = _^ (ptr);
            
            //Is it ItLsTorchBurned ?
            if (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurned)) {
                NPC_UseItem (slf, itm);
                NPC_RemoveInvItem (slf,  ptr);
                
                ptr = 0;
            };
        };
        
        //Is hand empty?
        if (!ptr)
        {
            //Careful! NPC_GetInventoryItem is not actually returning pointer. I am using ptr variable just to 'save' variables :)
            
            //Search for ItLsTorchBurned
            ptr = NPC_GetInventoryItem (slf, ItLsTorchBurned);
            
            //Search for ItLsTorch
            if (!ptr) {
                ptr = NPC_GetInventoryItem (slf, ItLsTorch);
            };
            
            if (ptr) {
                //item is either ItLsTorchBurned or ItLsTorch from NPC_GetInventoryItem
                //Use it - puts ItLsTorchBurning in hand and remove it from inventory
                NPC_UseItem (slf, item);
                NPC_RemoveInvItem (slf, _@ (item));
            };
        };
    };
    
    /***
        If NPC is holding torch in hand it will put it back to inventory
    ***/
    FUNC VOID NPC_TorchSwitchOff (var int slfInstance)
    {
        var int ptr;
        var C_NPC slf;
        
        slf = Hlp_GetNPC (slfInstance);
    
        //Get pointer to ZS_LEFTHAND
        ptr = NPC_GetSlotItem (slf, "ZS_LEFTHAND");
        
        //Is there anything in hand?
        if (ptr) {
            var C_Item itm;
            itm = _^ (ptr);
            
            //Is it ItLsTorchBurning ?
            if (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurning))
            || (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurned))
            {
                //Use ItLsTorchBurning - will put ItLsTorch back to inventory - remove ItLsTorchBurning from inventory
                NPC_UseItem (slf, itm);
                NPC_RemoveInvItem (slf,  ptr);
            };
        };
    };
    
    /***
        Returns true if NPC is holding torch
    ***/
    FUNC INT NPC_UsesTorch (var int slfInstance)
    {
        var int ptr;
        var C_NPC slf;
        
        slf = Hlp_GetNPC (slfInstance);
    
        //Get pointer to ZS_LEFTHAND
        ptr = NPC_GetSlotItem (slf, "ZS_LEFTHAND");
        
        //Is there anything in hand?
        if (ptr) {
            var C_Item itm;
            itm = _^ (ptr);
            
            //Is it ItLsTorchBurning / ItLsTorchBurned ?
            if (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurning))
            || (Hlp_GetInstanceID (itm) == Hlp_GetInstanceID (ItLsTorchBurned))
            {
                return TRUE;
            };
        };
        
        return FALSE;
    };


    [Hooks]


    Enabling more ZS_States for player:

    Code:
    //Originally posted here (slightly rewritten - 'inventory management' for followers integrated):
    //https://forum.worldofplayers.de/forum/threads/1533803-G1-AI_StartState-hardcoded-ZS-states-for-Player?p=26035799&viewfull=1#post26035799
    
    //Cannibalized from Ikarus 1.2.1 oCNPC class definition:
    class oCNpc_States {
            var int        state_vfptr;                                // 0x0470
            var string     state_name;                                 // 0x0474 zSTRING
            var int        state_npc;                                  // 0x0488 oCNpc*
    //      TNpcAIState curState {
                var int    state_curState_index;                       // 0x048C int
                var int    state_curState_loop;                        // 0x0490 int
                var int    state_curState_end;                         // 0x0494 int
                var int    state_curState_timeBehaviour;               // 0x0498 int
                var int    state_curState_restTime;                    // 0x049C zREAL
                var int    state_curState_phase;                       // 0x04A0 int
                var int    state_curState_valid;                       // 0x04A4 zBOOL
                var string state_curState_name;                        // 0x04A8 zSTRING
                var int    state_curState_stateTime;                   // 0x04BC zREAL
                var int    state_curState_prgIndex;                    // 0x04C0 int
                var int    state_curState_isRtnState;                  // 0x04C4 zBOOL
    //      }
    //      TNpcAIState nextState {
                var int    state_nextState_index;                      // 0x04C8 int
                var int    state_nextState_loop;                       // 0x04CC int
                var int    state_nextState_end;                        // 0x04D0 int
                var int    state_nextState_timeBehaviour;              // 0x04D4 int
                var int    state_nextState_restTime;                   // 0x04D8 zREAL
                var int    state_nextState_phase;                      // 0x04DC int
                var int    state_nextState_valid;                      // 0x04E0 zBOOL
                var string state_nextState_name;                       // 0x04E4 zSTRING
                var int    state_nextState_stateTime;                  // 0x04F8 zREAL
                var int    state_nextState_prgIndex;                   // 0x04FC int
                var int    state_nextState_isRtnState;                 // 0x0500 zBOOL
    };
    
    /*
        ZS_ASSESSMAGIC is allowed state for player, but it is not defined in G1 -> we can reserve it for ourselves
        When this function will be called from _HOOK_NPC_STATES_DOAISTATE for player - it will
            call npc.state_nextState_name (intended function)
            overwrite npc.state_nextState_loop with npc.state_nextState_name_LOOP
            overwrite npc.state_nextState_end with npc.state_nextState_name_END
    */        
    FUNC VOID ZS_ASSESSMAGIC ()
    {
        var oCNPC npc;
        npc = Hlp_GetNPC (self);
        
        //Disable further calls
        npc.state_nextState_index = -1;
        
        //Call npc.state_nextState_name (ZS_WHIRLWIND or others, if specified in _HOOK_NPC_STATES_DOAISTATE)
        MEM_CallByString (npc.state_nextState_name);
        
        //Overwrite npc.state_nextState_loop with npc.state_nextState_name_LOOP
        var string fNameLoop;
        fNameLoop = ConcatStrings (npc.state_nextState_name, "_LOOP");
        npc.state_nextState_loop = MEM_FindParserSymbol (fNameLoop);
        
        //Overwrite npc.state_nextState_end with npc.state_nextState_name_END
        var string fNameEnd;
        fNameEnd = ConcatStrings (npc.state_nextState_name, "_END");
        npc.state_nextState_end = MEM_FindParserSymbol (fNameEnd);
    };
    
    FUNC INT ZS_ASSESSMAGIC_Loop ()    {};
    FUNC VOID ZS_ASSESSMAGIC_End ()    {};
    
    //006C5C60  .text     Debug data           ?DoAIState@oCNpc_States@@QAEHXZ
    const int oCNpc_States__DoAIState_G1 = 7101536;
    
    //0x0076D1A0 public: int __thiscall oCNpc_States::DoAIState(void)
    const int oCNpc_States__DoAIState_G2 = 7786912;
    FUNC VOID _HOOK_NPC_STATES_DOAISTATE ()
    {
        var oCNpc_States state;
        state = _^ (ECX);
    
        var oCNPC npc;
        npc = _^ (state.state_npc);
    
        if (NPC_IsPlayer (npc))
        {
            if (state.state_nextState_index != 0)
            && (state.state_nextState_index != -1)
            {
                //Here you can specify what states are allowed for player
                if (Hlp_StrCmp (state.state_nextState_name, "ZS_WHIRLWIND"))
                || (Hlp_StrCmp (state.state_nextState_name, "ZS_INVMANAGEMENT"))
                {
                    //Overwrite state_nextState_index - this will point to function ZS_ASSESSMAGIC
                    //npc.state_nextState_name will still contain string "ZS_WHIRLWIND")
                    npc.state_nextState_index = MEM_FindParserSymbol ("ZS_ASSESSMAGIC");
                };
            };
        };
    };
    G1 Improved trading system:
    Code:
    //Originally posted here (slightly rewritten - 'inventory management' for followers integrated):
    //https://forum.worldofplayers.de/forum/threads/1505251-Skriptpaket-LeGo-4/page5?p=25782129&viewfull=1#post25782129
    
    //0072A870  .text     Debug data           ?OnAccept@oCViewDialogTrade@@IAIXXZ
    const int oCViewDialogTrade__OnAccept        = 7514224;
    
    //00729390  .text     Debug data           ?TransferAccept@oCViewDialogTrade@@IAIXXZ
    const int oCViewDialogTrade__TransferAccept    = 7508880;
    
    FUNC VOID _HOOK_TRADE_ONACCEPT ()
    {
        var int ptr;
        
        var oCNpcInventory npcInventory_Trader;
        var oCNpcInventory npcInventory_Trader_Offer;
        var oCNpcInventory npcInventory_Buyer_Offer;
        var oCNpcInventory npcInventory_Buyer;
    
        //If we are currently in inventory management for followers - exit
        if (InvManagement_Dialog == cIM_Dialog_Trade) {
            return;
        };
    
    //--- Get Item containers (oCItemContainer)
    
        //Traders Inventory
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
        if (ptr) {
            ptr = MEM_ReadInt (ptr + 248);    //oCViewDialogItemInventory
            if (ptr) {
                ptr = MEM_ReadInt (ptr + 256);    //oCItemContainer
                npcInventory_Trader = _^ (ptr);
            };
        };
    
        //Traders 'offer'
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
        if (ptr) {
            ptr = MEM_ReadInt (ptr + 252);    //oCViewDialogStealContainer
            if (ptr) {
                ptr = MEM_ReadInt (ptr + 256);    //oCItemContainer
                npcInventory_Trader_Offer = _^ (ptr);
            };
        };
    
        //Buyers 'offer'
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
        if (ptr) {
            ptr = MEM_ReadInt (ptr + 260);    //oCViewDialogItemContainer
            if (ptr) {
                ptr = MEM_ReadInt (ptr + 256);    //oCItemContainer
                npcInventory_Buyer_Offer = _^ (ptr);
            };
        };
    
        //Buyers inventory
        ptr = MEMINT_oCInformationManager_Address;
        ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
        if (ptr) {
            ptr = MEM_ReadInt (ptr + 264);    //oCViewDialogItemInventory
            if (ptr) {
                ptr = MEM_ReadInt (ptr + 256);    //oCItemContainer
                npcInventory_Buyer = _^ (ptr);
            };
        };
        
    //--- Get Offer Value + Ore amount for both Trader and Buyer
    
        var int Value_Traders_Offer;
        var int Value_Buyers_Offer;
    
        var int Ore_Trader;
        var int Ore_Buyer;
    
        var int Delta;
    
        //inventory2_oCItemContainer_textCategoryStatic contains Total Value of offers - we can exploit that
        Value_Traders_Offer = STR_ToInt (npcInventory_Trader_Offer.inventory2_oCItemContainer_textCategoryStatic);
        Value_Buyers_Offer = STR_ToInt (npcInventory_Buyer_Offer.inventory2_oCItemContainer_textCategoryStatic);
        
        var C_NPC Trader;
        Trader = _^ (npcInventory_Trader.inventory2_owner);
        Ore_Trader = NPC_HasItems (Trader, ItMiNugget);
        
        var C_NPC Buyer;
        Buyer = _^ (npcInventory_Buyer.inventory2_owner);
        Ore_Buyer = NPC_HasItems (Buyer, ItMiNugget);
        
        Delta = Value_Traders_Offer - Value_Buyers_Offer;
        
        var string msg;
        
        if (Delta == 0) {
            //Trade went smoothly :)
        } else
        //Buyer has to supply ore !
        if (Delta > 0) {
            if (Delta > Ore_Buyer) {
                PrintScreen ("You don't have enough ore.", -1, _YPOS_MESSAGE_LOGENTRY, "font_old_20_white.tga", _TIME_MESSAGE_LOGENTRY);
            } else
            {
                //Accept Transfer anyway!            
                ptr = MEMINT_oCInformationManager_Address;
                ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
    
                CALL__thiscall (ptr, oCViewDialogTrade__TransferAccept);
    
                //'Move' ore from Buyer to Trader
                NPC_RemoveInvItems (Buyer, ItMiNugget, Delta);
                CreateInvItems (Trader, ItMiNugget, Delta);
                
                msg = ConcatStrings (IntToString (Delta), "x ore given.");
                PrintScreen (msg, -1, _YPOS_MESSAGE_GIVEN, "font_old_10_white.tga", _TIME_MESSAGE_TAKEN);
            };
        } else
        //Trader has to supply ore !
        {
            //Abs (Delta)
            Delta = 0 - Delta;
            
            if (Delta > Ore_Trader) {
                PrintScreen ("Trader does not have enough ore.", -1, _YPOS_MESSAGE_LOGENTRY, "font_old_20_white.tga", _TIME_MESSAGE_LOGENTRY);
            } else
            {
                //Accept Transfer anyway!
                ptr = MEMINT_oCInformationManager_Address;
                ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
    
                CALL__thiscall (ptr, oCViewDialogTrade__TransferAccept);
    
                //'Move' ore from Trader to Buyer
                NPC_RemoveInvItems (Trader, ItMiNugget, Delta);
                CreateInvItems (Buyer, ItMiNugget, Delta);
                
                msg = ConcatStrings (IntToString (Delta), "x ore taken.");
                PrintScreen (msg, -1, _YPOS_MESSAGE_GIVEN, "font_old_10_white.tga", _TIME_MESSAGE_TAKEN);
            };
        };
    };
    Hooks detecting closed inventory.
    Only by using hooks we can properly detect if player closed trading & inventory.
    If inventory is opened, one would expect when it is closed, that function oCNPC__CloseInventory would be called. BUT, there are several cases, where inventory closes and function oCNPC__CloseInventory is not called.
    So player would f*k up our system by:
    Drawing a weapon (oCNpc__EV_DrawWeapon, oCNpc__EV_DrawWeapon1, oCNpc__EV_DrawWeapon2)
    By pressing 'M' button to show map (I hate this hotkey) (oCMapScreen__Show)
    By pressing 'S', 'B' button to show Status screen (oCStatusScreen__Show) - this can be handled without hook by checking MEM_Game.singleStep
    By pressing 'L', 'N' button to show Log screen (oCLogScreen__Show) - this can also be handled without hook by checking MEM_Game.singleStep

    Code:
    /***
        Function is called when trading is closed
    ***/
    //0072AAB0  .text     Debug data           ?OnExit@oCViewDialogTrade@@IAIXXZ
    const int oCViewDialogTrade__OnExit        = 7514800;
    FUNC VOID _HOOK_TRADE_ONEXIT ()
    {
        //If we are currently in inventory management for followers ...
        if (InvManagement_Dialog == cIM_Dialog_Trade) {
            InvManagement_Dialog = cIM_Dialog_Trade_Closing;
    
            //End dialog for Follower
            AI_StopProcessInfos (Follower);
            
            //Start ZS_InvManagement for hero (Guide) 
            AI_StartState (Guide, ZS_InvManagement, 1, "");
    
            //Restore default trade values
            Trade_ChangeSellMultiplier (divf (mkf (3), mkf (10)));    //30%
            Trade_ChangeBuyMultiplier (FLOATONE);            //100%
        };
    };
    
    /***
        Function overwrites container total-value.
        Without this total value of players sold items would still be calculated and would not disappear, when you would be trading.
        Player could generate infinite amount of $ by moving back and forward items between himself and follower.
        For some reason Gothic ignores fact, that we used FLOATNULL in Trade_ChangeSellMultiplier ! ;(
    ***/
    //00727900  .text     Debug data           ?UpdateValue@oCViewDialogItemContainer@@IAIXXZ
    const int oCViewDialogItemContainer__UpdateValue = 7502080;
    FUNC VOID _HOOK_TRADESELL_UPDATEVALUE ()
    {
        if (InvManagement_Dialog == cIM_Dialog_Trade)
        {
            var int ptr;
            
            //Buyers 'offer'
            ptr = MEMINT_oCInformationManager_Address;
            ptr = MEM_ReadInt (ptr + 24);    //oCInformationManager.ocViewDialogTrade
            if (ptr) {
                ptr = MEM_ReadInt (ptr + 260);    //oCViewDialogItemContainer
                if (ptr) {
                    //Overwrite container - total value - write 0
                    MEM_WriteInt (ptr + 264, 0);
                };
            };
        };
    };
    
    /***
        Function is called when player presses 'M' for to show Map
    ***/
    //00478490  .text     Debug data           ?Show@oCMapScreen@@QAEXXZ
    const int oCMapScreen__Show = 4686992;
    FUNC VOID _HOOK_MAPSCREEN_SHOW ()
    {
        if (InvManagement_Dialog != cIM_Dialog_Default) {
            InvManagement_Dialog = cIM_Dialog_Inventory_Reopen;
        };
    };
    
    /***
        Weapon drawing closes inventory, also - we don't want to drop torch, put it back to inventory
    ***/
    
    //006A8500  .text     Debug data           ?EV_DrawWeapon@oCNpc@@QAEHPAVoCMsgWeapon@@@Z
    //006A8B80  .text     Debug data           ?EV_DrawWeapon1@oCNpc@@QAEHPAVoCMsgWeapon@@@Z
    //006A8E20  .text     Debug data           ?EV_DrawWeapon2@oCNpc@@QAEHPAVoCMsgWeapon@@@Z
    const int oCNpc__EV_DrawWeapon            = 6980864;
    const int oCNpc__EV_DrawWeapon1            = 6982528;
    const int oCNpc__EV_DrawWeapon2            = 6983200;
    
    FUNC VOID _HOOK_NPC_DRAWWEAPON ()
    {
        var oCNPC npc;
        npc = _^ (ECX);
        
        //Just in case
        if (Hlp_IsValidNPC (npc)) {
            //Is this our Follower?
            if (Hlp_GetInstanceID (npc) == Hlp_GetInstanceID (Follower)) {
                //Exit
                if (InvManagement_Dialog != cIM_Dialog_Default) {
                    InvManagement_Dialog = cIM_Dialog_Exit;
                };
            };
            
            //Remove torch from hand and put it to inventory. Don't drop it.
            NPC_TorchSwitchOff (npc);
        };
    };
    
    /***
        Function is closed when inventory is closed (only when properly closed ;( )
    ***/
    FUNC VOID _HOOK_NPC_CLOSEINVENTORY ()
    {
        var oCNPC npc;
        npc = _^(ECX);
        
        if (InvManagement_Dialog != cIM_Dialog_Default) {
            InvManagement_Dialog = cIM_Dialog_Exit;
        };
    };
    
    //Improved trading
    HookEngine (oCViewDialogTrade__OnAccept, 6, "_HOOK_TRADE_ONACCEPT");
    
    //Hook overwriting total value of players items for selling
    HookEngine (oCViewDialogItemContainer__UpdateValue, 7, "_HOOK_TRADESELL_UPDATEVALUE");
    
    //Hook detecting closed trading
    HookEngine (oCViewDialogTrade__OnExit, 5, "_HOOK_TRADE_ONEXIT");
    
    //Hooks detecting closed inventory
    HookEngine (oCMapScreen__Show, 7, "_HOOK_MAPSCREEN_SHOW");
    
    HookEngine (oCNpc__EV_DrawWeapon, 6, "_HOOK_NPC_DRAWWEAPON");
    HookEngine (oCNpc__EV_DrawWeapon1, 5, "_HOOK_NPC_DRAWWEAPON");
    HookEngine (oCNpc__EV_DrawWeapon2, 6, "_HOOK_NPC_DRAWWEAPON");
    
    HookEngine (oCNPC__CloseInventory, 6, "_HOOK_NPC_CLOSEINVENTORY");
    Modified ZS_FollowPC () to improve 'AI':

    Code:
    FUNC VOID B_FollowPC_AssessSC ()
    {
        var int removeTorch; removeTorch = FALSE;
    
    //--- Immersive AI
    
        if (NPC_GetDistToNPC (self, hero) < HAI_DIST_ACTIONRANGE)
        {
            if (NPC_GetWalkMode (hero) == NPC_SNEAK)
            {
                if (NPC_GetWalkMode (self) != NPC_SNEAK)
                {
                    B_FullStop (self);
                    AI_SetWalkMode (self, NPC_SNEAK);
                };
                
                //If player is sneaking - remove torch
                removeTorch = TRUE;
            } else
            if (NPC_GetWalkMode (self) == NPC_SNEAK)
            {
                B_FullStop (self);
                AI_SetWalkMode (self, NPC_RUN);
            };
        };
    
        //If player is in portal room - remove torch
        if (!Hlp_StrCmp (Wld_GetPlayerPortalRoom (), ""))
        {
            removeTorch = TRUE;
        };
    
        //If it is night time - and there is no reason to remove torch - use it. Otherwise remove it.
        if (Wld_IsTime (21, 30, 05, 30))
        && (removeTorch == FALSE) {
            NPC_TorchSwitchOn (self);
        } else {
            NPC_TorchSwitchOff (self);
        };
        
    //--- ZS_Talk
        
        if (NPC_CheckInfo (self, 1))
        {
            if (!C_BodyStateContains (hero, BS_FALL))
            {
                if (!C_BodyStateContains (hero, BS_SWIM))
                {
                    if (!C_BodyStateContains (hero, BS_DIVE))
                    {
                        //Is player close enough?
                        if (NPC_GetDistToNpc (self, hero) <= 800)
                        {
                            NPC_PercDisable (self, PERC_ASSESSPLAYER);
                            
                            hero.aivar[AIV_IMPORTANT] = TRUE;
                            
                            B_FullStop (self);
                            B_FullStop (hero);
                            AI_StartState (self, ZS_Talk, 0, "");
                            
                            return;
                        };
                    };
                };
            };
        };
    };
    
    FUNC VOID ZS_FollowPC ()
    {
        NPC_PercEnable (self, PERC_ASSESSENEMY, B_AssessEnemy);
        Perc_SetRange (PERC_ASSESSPLAYER, 5000);
        NPC_PercEnable (self, PERC_ASSESSPLAYER, B_FollowPC_AssessSC);
    
        NPC_SetPercTime (self, 1);
        self.senses = SENSE_SEE | SENSE_HEAR | SENSE_SMELL;
    
        NPC_PercEnable (self, PERC_ASSESSDAMAGE, ZS_ReactToDamage);
        NPC_PercEnable (self, PERC_ASSESSCASTER, B_AssessCaster);
        NPC_PercEnable (self, PERC_ASSESSMAGIC, B_AssessMagic);
        NPC_PercEnable (self, PERC_ASSESSMURDER, ZS_AssessMurder);
        NPC_PercEnable (self, PERC_ASSESSDEFEAT, ZS_AssessDefeat);
        NPC_PercEnable (self, PERC_ASSESSFIGHTSOUND, B_AssessFightSound);
        NPC_PercEnable (self, PERC_ASSESSTALK, B_AssessTalk);
        NPC_PercEnable (self, PERC_MOVEMOB, B_MoveMob);
    };
    
    FUNC INT ZS_FollowPC_Loop ()
    {
        //Call this perception anyway ...
        B_FollowPC_AssessSC ();
        
        if (NPC_GetDistToNpc (self, hero) > HAI_DIST_FOLLOWPC)
        {
            if (!C_BodyStateContains (self, BS_SWIM))
            {
                if (NPC_GetWalkMode (self) != NPC_RUN)
                && (NPC_GetWalkMode (self) != NPC_SNEAK)
                {
                    AI_SetWalkMode (self, NPC_RUN);
                };
            };
    
            AI_GotoNpc (self, hero);
        } else
        {
            B_SmartTurnToNpc (self, hero);
        };
        
        AI_Wait (self, 1);
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_FollowPC_End ()
    {
        self.senses = hero.senses;
    };
    ZS_InvManagement:
    Code:
    FUNC VOID NPC_SetAsPlayer (var int slfInstance)
    {
        //0069EAE0  .text     Debug data           ?SetAsPlayer@oCNpc@@UAEXXZ
        const int oCNPC__SetAsPlayer = 6941408;
        
        var C_NPC slf;
        slf = Hlp_GetNPC (slfInstance);
        
        CALL__thiscall (MEM_InstToPtr (slf), oCNPC__SetAsPlayer);
    };
    
    FUNC VOID ZS_InvManagement () { };
    
    FUNC INT ZS_InvManagement_Loop ()
    {
        //LogScreen, StatusScreen - both close inventory, and in case of both MEM_Game.singleStep == TRUE
        if (MEM_Game.singleStep) {
            //Reopen inventory
            InvManagement_Dialog = cIM_Dialog_Inventory_Reopen;
        } else {
            //If trading was closed (accept / exit)
            if (InvManagement_Dialog == cIM_Dialog_Trade_Closing) {
                //Wait for InfoManager to disappear
                if (InfoManager_HasFinished ())
                {
                    //Re-apply AIV_INVINCIBLE (we don't want to be approached by any NPC)
                    Guide.aivar[AIV_INVINCIBLE] = TRUE;
                    Follower.aivar[AIV_INVINCIBLE] = TRUE;
                    
                    //Set as player and open inventory
                    NPC_SetAsPlayer (Follower);
                    NPC_OpenInventory (Follower);
                    InvManagement_Dialog = cIM_Dialog_Inventory;
                };
            };
    
            //If inventory was closed but _HOOK_NPC_CLOSEINVENTORY was not called (so inventory was closed incorrectly by weapon drawing or by MapScreen), reopen it
            if (InvManagement_Dialog == cIM_Dialog_Inventory_Reopen)
            {
                NPC_OpenInventory (Follower);
                InvManagement_Dialog = cIM_Dialog_Inventory;
            };
            
            //If player closed inventory
            if (InvManagement_Dialog == cIM_Dialog_Exit)
            {
                //Set as player our 'true' hero
                NPC_SetAsPlayer (Guide);
    
                //Restore self & other
                self = Hlp_GetNPC (Follower);
                other = Hlp_GetNPC (Guide);
                
                //Reset AIV_INVINCIBLE so ZS_Talk can start
                self.aivar[AIV_INVINCIBLE] = FALSE;
                other.aivar[AIV_INVINCIBLE] = FALSE;
    
                //Start ZS_Talk
                AI_StartState (self, ZS_Talk, 1, "");
                
                return LOOP_END;
            };
        };
    
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_InvManagement_End () { };
    Dialog instances - add it to all NPCs within ZS_Talk (condition will make sure it is visible only for Mud & Shrat):

    Code:
    //Inventory management
    INSTANCE DIA_Follower_InvManagement (C_Info)
    {
        nr        = 776;
        condition    = DIA_Follower_InvManagement_Condition;
        information    = DIA_Follower_InvManagement_Info;
        permanent    = TRUE;
        trade        = TRUE;
        description    = "(inventory management)";
    };
    
    FUNC INT DIA_Follower_InvManagement_Condition ()
    {
        //Is this follower?
        if (self.aivar [AIV_PARTYMEMBER])
        {
            if (self.ID == 574)    //Mud
            || (self.ID == 1356)    //Shrat
            {
                return TRUE;
            };
        };
        
        return FALSE;
    };
    
    FUNC VOID DIA_Follower_InvManagement_Info ()
    {
        //Set sell & buy multiplier to 0
        Trade_ChangeSellMultiplier (FLOATNULL);
        Trade_ChangeBuyMultiplier (FLOATNULL);
        
        //Remember who is Guide (hero) and who is our Follower
        Follower = Hlp_GetNPC (self);
        Guide = Hlp_GetNPC (other);
        
        //We are currently trading
        InvManagement_Dialog = cIM_Dialog_Trade;
    };
    
    //Wait here / follow me
    //Originally posted here:
    //https://forum.worldofplayers.de/forum/threads/1532032-G1-Monster-kehren-IMMER-zum-Spawnpunkt-zur%C3%BCck%21?p=26003997&viewfull=1#post26003997
    /*
    Routine for waiting followers
    */
    FUNC VOID Follower_WaitHere_Routine ()
    {
        TA (self, 0, 23, ZS_StandAround, self.WP);
    };
    
    /*
    Pointer adjusted for G1, G2A code originally posted by Lehona:
    https://forum.worldofplayers.de/forum/threads/1483066-Aktuellen-Routinennamen-eines-Npcs-herausbekommen?p=25220976&viewfull=1#post25220976
    */
    FUNC STRING NPC_GetRoutineName (var C_NPC slf)
    {
        var int npcPtr; npcPtr = _@ (slf);
        
        var int symbID; symbID = MEM_ReadInt (npcPtr + 536);
        var zCPar_symbol symb; symb = _^ (MEM_GetSymbolByIndex (symbID));
        return symb.name;
    };
    
    /*
    Function checks if NPC_GetRoutineName is in RTN_ rtnName _NPC.ID format
    */
    FUNC INT NPC_IsInRoutineName (var C_NPC slf, var string rtnName)
    {
        var string curRtnName;
        curRtnName = STR_Upper (rtnName);
        curRtnName = ConcatStrings ("RTN_", curRtnName);
        curRtnName = ConcatStrings (curRtnName, "_");
        curRtnName = ConcatStrings (curRtnName, IntToString (slf.ID));
        
        return Hlp_StrCmp (NPC_GetRoutineName (slf), curRtnName);
    };
    
    //--- Dialogs below are added via ZS_Talk to any NPC with AIV_PARTYMEMBER flag
    
    INSTANCE DIA_Follower_Wait_Here (C_Info)
    {
        nr        = 778;
        condition    = DIA_Follower_Wait_Here_Condition;
        information    = DIA_Follower_Wait_Here_Info;
        permanent    = TRUE;
        description    = "(wait here)";
    };
    
    FUNC INT DIA_Follower_Wait_Here_Condition ()
    {
        //Is this follower?
        if (self.aivar [AIV_PARTYMEMBER])
        {
            //Is it human?
            if (C_NPCIsHuman (self))
            {
                //Is NPC in FOLLOW routine ? (for example RTN_FOLLOW_1234)
                if (NPC_IsInRoutineName (self, "FOLLOW"))
                {
                    return TRUE;
                };
            };
        };
    
        return FALSE;
    };
    
    FUNC VOID DIA_Follower_Wait_Here_Info ()
    {
        //Get Nearest waypoint
        var string WP;    WP = NPC_GetNearestWP (self);
        self.WP = WP;
    
        //Overlay current routine
        TA_BeginOverlay (self);
        TA (self, 0, 23, ZS_StandAround, self.WP);
        TA_EndOverlay (self);
    
        //Change daily_routine for Save/Load
        self.daily_routine = Follower_WaitHere_Routine;
    };
    
    INSTANCE DIA_Follower_Wait_Here_Come (C_Info)
    {
        nr        = 778;
        condition    = DIA_Follower_Wait_Here_Come_Condition;
        information    = DIA_Follower_Wait_Here_Come_Info;
        permanent    = TRUE;
        description    = "(follow me)";
    };
    
    FUNC INT DIA_Follower_Wait_Here_Come_Condition ()
    {
        //Is this follower?
        if (self.aivar [AIV_PARTYMEMBER])
        {
            //Is it human?
            if (C_NPCIsHuman (self))
            {
                //Is NPC waiting in FOLLOWER_WAITHERE_ROUTINE routine ?
                if (Hlp_StrCmp (NPC_GetRoutineName (self), "FOLLOWER_WAITHERE_ROUTINE"))
                {
                    return TRUE;
                };
            };
        };
    
        return FALSE;
    };
    
    FUNC VOID DIA_Follower_Wait_Here_Come_Info ()
    {
        NPC_ExchangeRoutine (self, "FOLLOW");
    };
    
    /*
    B_Assign_Follower_Dialogs added in ZS_Talk to any NPC with AIV_PARTYMEMBER flag:
    
        FUNC VOID ZS_Talk ()
        {
            ...
    
            if (self.aivar [AIV_PARTYMEMBER])    {    B_Assign_Follower_Dialogs (self);    };
    
            ...
        };
    */
    
    FUNC VOID B_Assign_Follower_Dialogs (var C_Npc slf)
    {
        DIA_Follower_InvManagement.npc = Hlp_GetInstanceID (slf);
    
        DIA_Follower_Wait_Here.npc = Hlp_GetInstanceID (slf);
        DIA_Follower_Wait_Here_Come.npc = Hlp_GetInstanceID (slf);
    };
    And that's it. Hopefully I didn't forget any piece of code.

  2. Beiträge anzeigen #2 Zitieren
    Legende der Amazonen Avatar von Bisasam
    Registriert seit
    Dec 2006
    Ort
    Meine Faust in Sinis Gesicht
    Beiträge
    9.639
     
    Bisasam ist offline
    Thank you so much for sharing your code. That replaces my hideous code for giving my follower Weapons. I'll use it.


    "Das erinnert doch sehr erfreulich an das, was man sich als Gothicfan wünscht!"
    -Korallenkette

  3. Beiträge anzeigen #3 Zitieren
    Local Hero
    Registriert seit
    Feb 2013
    Beiträge
    236
     
    pawbuj ist offline
    Can the followers use a ladders ?

    Second thing would be nice if the followers will collect a items around (f. e. plants, weapons ) and pass to a hero.
    Geändert von pawbuj (08.07.2019 um 08:18 Uhr)

  4. Beiträge anzeigen #4 Zitieren
    Local Hero
    Registriert seit
    Feb 2017
    Beiträge
    270
     
    F a w k e s ist offline
    Zitat Zitat von pawbuj Beitrag anzeigen
    Can the followers use a ladders ?
    Ladders are quite challenging! I tried below to update AI with couple of new ZS states. This improves followers ability to use ladders slightly - it's not perfect, but better than nothing.
    [Video]
    First NPC will check within function B_FollowPC_AssessSC what is current player's animation.
    Based on player's animation NPC recognizes that player is climbing - and new ZS state will be applied:
    If it is one of 'T_LADDER_S [0-8] _2_S [1-9]' we know that player is climbing up the ladder - ZS_FollowerAssessLadderClimbUp will be applied.
    If it is one of 'T_LADDER_S [1-9] _2_S [0-8]' we know that player is climbing down the ladder - ZS_FollowerAssessLadderClimbDown will be applied.

    ZS_FollowerAssessLadderClimbUp
    NPC will again check player's current animation.
    If it starts with T_LADDER or S_LADDER then
    we know player is still climbing. If animation is one of 'T_LADDER_S [0-9] _2_STAND' then we know that player finished climbing the ladder - ZS_FollowerClimbLadderUp will be applied.
    else
    we know player stopped climbing - and we can ignore ladder - this will trigger AI_ContinueRoutine and put NPC back to ZS_FollowPC.

    ZS_FollowerClimbLadderUp
    We will use NPC_CreateVobList to detect nearby ladders, first in a range of 5m, then 10m, 15m, 20m and finally 30m.
    Functions oCMobInter_CanInteractWith (ptr, self) & oCMobInter_IsAvailable (ptr, self) will check if ladder is available, if yes
    NPC will climb up the ladder and AI_ContinueRoutine will reapply ZS_FollowPC.
    else
    NPC will after time out of 30 seconds reapply ZS_FollowPC.

    ZS_FollowerAssessLadderClimbDown
    NPC will again check player's current animation.
    If it starts with T_LADDER or S_LADDER then
    we know player is still climbing. If animation is one of 'T_LADDER_S [0-9] _2_STAND' - we know that player finished climbing the ladder - ZS_FollowerClimbLadderDown will be applied.
    else
    we know player stopped climbing - and jumped down - ZS_FollowerClimbLadderDown will be applied

    ZS_FollowerClimbLadderdDown
    We will use NPC_CreateVobList to detect nearby ladders, first in a range of 5m, then 10m, 15m, 20m and finally 30m.
    Functions oCMobInter_CanInteractWith (ptr, self) & oCMobInter_IsAvailable (ptr, self) will check if ladder is available, if yes
    NPC will climb down the ladder and AI_ContinueRoutine will reapply ZS_FollowPC.
    else
    NPC will after time out of 30 seconds reapply ZS_FollowPC.

    If you want to use this 'AI update' you will need:
    NPCVobList.d
    https://forum.worldofplayers.de/foru...1#post26052199

    G1 AI_GotoVob alternative:
    https://forum.worldofplayers.de/foru...1#post26055633

    Code:
    Code:
    //Author: OrcWarrior
    //https://github.com/orcwarrior/Czas_Zaplaty/blob/master/Content/AI/AI_Intern/Sprint_Func.d
    FUNC STRING oCAniCtrl__GetCurrentAniName (var int oCAniCtrl_Ptr)
    {
        var int hlp;
        hlp = MEM_ReadInt (oCAniCtrl_Ptr + 104);            //zCModel     oCAniCtrl_Human._zCAIPlayer_model
        if (hlp) 
        {
            hlp = MEM_ReadInt (hlp + 56);                //*ActiveAniLayer1
            if (hlp)
            {
                hlp = MEM_ReadInt (hlp);            //*oCAni                
                if (hlp)
                { 
                    return MEM_Readstring (hlp + 36);    // This will read active ani name(?)
                };    //aniname(zstring)
            };
        };
    
        return "ERROR";
    };
    
    FUNC STRING NPC_GetAniName (var int slfInstance)
    {
        var oCNPC slf;
        slf = Hlp_GetNPC (slfInstance);
        
        var string str_Ani;
        str_Ani = oCAniCtrl__GetCurrentAniName (slf.AniCtrl);
    
        return str_Ani;
    };
    Code:
    FUNC INT oCMobInter_IsAvailable (var int mobPtr, var int slfInstance)
    {
        //0067F570  .text     Debug data           ?IsAvailable@oCMobInter@@QAEHPAVoCNpc@@@Z
        const int oCMobInter__IsAvailable = 6813040;
        
        if (!mobPtr) { return FALSE; };
        
        var oCNPC slf;
        slf = Hlp_GetNPC (slfInstance);
    
        CALL_PtrParam (MEM_InstToPtr (slf));
        CALL__thiscall (mobPtr, oCMobInter__IsAvailable);
    
        return CALL_RetValAsInt ();
    };
    
    FUNC INT oCMobInter_CanInteractWith (var int mobPtr, var int slfInstance)
    {
        //0067F5F0  .text     Debug data           ?CanInteractWith@oCMobInter@@UAEHPAVoCNpc@@@Z
        const int oCMobInter__CanInteractWith = 6813168;
        
        if (!mobPtr) { return FALSE; };
        
        var oCNPC slf;
        slf = Hlp_GetNPC (slfInstance);
    
        CALL_PtrParam (MEM_InstToPtr (slf));
        CALL__thiscall (mobPtr, oCMobInter__CanInteractWith);
    
        return CALL_RetValAsInt ();
    };
    Update func B_FollowPC_AssessSC in your ZS_FollowPC.d file by adding:
    Code:
    FUNC VOID B_ClimbingLadderStop ()
    {
       B_FullStop (self);
       AI_ContinueRoutine (self);
    };
    
    
    /***
        NPC will detect ladder and climb it up
    ***/
    FUNC VOID ZS_FollowerClimbLadderUp ()
    {
        //Option to stop NPC
        NPC_PercEnable (self, PERC_ASSESSTALK, B_ClimbingLadderStop);
        NPC_PercEnable (self, PERC_ASSESSDAMAGE, B_ClimbingLadderStop);
    
    
        B_FullStop (self);
        NPC_SetStateTime (self, 0);
    };
    
    FUNC INT ZS_FollowerClimbLadderUp_Loop ()
    {
        var int ptr;
        NPC_ClearVobList (self);
        
        //Detect range - increase every 3 seconds if NPC didn't find ladder
        if (NPC_GetStateTime (self) < 3)    {    NPC_CreateVobList (self, 500);    } else
        if (NPC_GetStateTime (self) < 6)    {    NPC_CreateVobList (self, 1000);    } else
        if (NPC_GetStateTime (self) < 9)    {    NPC_CreateVobList (self, 1500);    } else
        if (NPC_GetStateTime (self) < 12)    {    NPC_CreateVobList (self, 2000);    } else
        {
            NPC_CreateVobList (self, 3000);
        };
        
        ptr = NPC_VobList_FindMobInter (self, "LADDER");
        
        if (ptr)
        {
            if (oCMobInter_CanInteractWith (ptr, self))
            {
                if (oCMobInter_IsAvailable (ptr, self))
                {
                    Print ("Ladder detected - climbing up");
    
                    var oCNPC slf;
                    slf = Hlp_GetNPC (self);
                    
                    AI_GotoVob (self, ptr);
                    
                    slf.interactMob = ptr;
                    
                    AI_UseMob (self, "LADDER", 1);
                    AI_UseMob (self, "LADDER", 2);
                    AI_UseMob (self, "LADDER", 3);
                    AI_UseMob (self, "LADDER", 4);
                    AI_UseMob (self, "LADDER", 5);
                    AI_UseMob (self, "LADDER", 6);
                    AI_UseMob (self, "LADDER", 7);
                    AI_UseMob (self, "LADDER", 8);
                    AI_UseMob (self, "LADDER", 9);
                    
                    AI_UseMob (self, "LADDER", -1);
    
                    AI_TurnToNPC (self, hero);
                    AI_GotoNpc (self, hero);
    
                    AI_ContinueRoutine (self);
                    return LOOP_END;
                };
            };
        };
        
        if (NPC_GetStateTime (self) > 30) {
            Print ("Climbing up - timed out");
            AI_ContinueRoutine (self);
            return LOOP_END;
        };
        
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_FollowerClimbLadderUp_End ()
    {
        AI_ContinueRoutine (self);
    };
    
    /***
        NPC will detect ladder and climb it down
    ***/
    
    FUNC VOID ZS_FollowerClimbLadderDown ()
    {
        //Option to stop NPC
        NPC_PercEnable (self, PERC_ASSESSTALK, B_ClimbingLadderStop);
        NPC_PercEnable (self, PERC_ASSESSDAMAGE, B_ClimbingLadderStop);
    
    
        B_FullStop (self);
        NPC_SetStateTime (self, 0);
    };
    
    FUNC INT ZS_FollowerClimbLadderDown_Loop ()
    {
        var int ptr;
        NPC_ClearVobList (self);
        
        //Detect range - increase every 3 seconds if NPC didn't find ladder
        if (NPC_GetStateTime (self) < 3)    {    NPC_CreateVobList (self, 500);    } else
        if (NPC_GetStateTime (self) < 6)    {    NPC_CreateVobList (self, 1000);    } else
        if (NPC_GetStateTime (self) < 9)    {    NPC_CreateVobList (self, 1500);    } else
        if (NPC_GetStateTime (self) < 12)    {    NPC_CreateVobList (self, 2000);    } else
        {
            NPC_CreateVobList (self, 3000);
        };
        
        ptr = NPC_VobList_FindMobInter (self, "LADDER");
        
        if (ptr)
        {
            if (oCMobInter_CanInteractWith (ptr, self))
            {
                if (oCMobInter_IsAvailable (ptr, self))
                {
                    Print ("Ladder detected - climbing down");
    
                    var oCNPC slf;
                    slf = Hlp_GetNPC (self);
                    
                    AI_GotoVob (self, ptr);
                    
                    slf.interactMob = ptr;
    
                    AI_UseMob (self, "LADDER", 0);
                    AI_UseMob (self, "LADDER", -1);
                    
                    AI_TurnToNPC (self, hero);
                    AI_GotoNpc (self, hero);
    
                    AI_ContinueRoutine (self);
                    return LOOP_END;
                };
            };
        };
        
        if (NPC_GetStateTime (self) > 30) {
            Print ("Climbing down - timed out");
            AI_ContinueRoutine (self);
            return LOOP_END;
        };
        
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_FollowerClimbLadderDown_End ()
    {
        AI_ContinueRoutine (self);
    };
    
    /***
        NPC will wait for hero to climb up ladder
    ***/
    
    FUNC VOID ZS_FollowerAssessLadderClimbUp ()
    {
        B_FullStop (self);
        Print ("Hero is climbing up");
    };
    
    FUNC INT ZS_FollowerAssessLadderClimbUp_Loop ()
    {
        var string aniName;
        aniName = NPC_GetAniName (hero);
        
        if (STR_StartsWith (aniName, "T_LADDER"))
        || (STR_StartsWith (aniName, "S_LADDER"))
        {
            if (Hlp_StrCmp (aniName, "T_LADDER_S0_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S1_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S2_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S3_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S4_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S5_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S6_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S7_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S8_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S9_2_STAND"))
            {
                Print ("Hero climbed up ...");
                AI_StartState (self, ZS_FollowerClimbLadderUp, 1, "");
                return LOOP_END;
            };
        } else {
            Print ("Hero stopped climbing - ignoring");
            AI_ContinueRoutine (self);
            return LOOP_END;
        };
        
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_FollowerAssessLadderClimbUp_End ()
    {
    };
    
    /***
        NPC will wait for hero to climb down ladder
    ***/
    
    FUNC VOID ZS_FollowerAssessLadderClimbDown ()
    {
        B_FullStop (self);
        Print ("Hero is climbing down");
    };
    
    FUNC INT ZS_FollowerAssessLadderClimbDown_Loop ()
    {
        var string aniName;
        aniName = NPC_GetAniName (hero);
        
        if (STR_StartsWith (aniName, "T_LADDER"))
        || (STR_StartsWith (aniName, "S_LADDER"))
        {
            if (Hlp_StrCmp (aniName, "T_LADDER_S0_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S1_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S2_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S3_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S4_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S5_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S6_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S7_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S8_2_STAND"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S9_2_STAND"))
            {
                Print ("Hero climbed down ...");
                AI_StartState (self, ZS_FollowerClimbLadderDown, 1, "");
                return LOOP_END;
            };
        } else {
            Print ("Hero stopped climbing - did he fall ????");
            AI_StartState (self, ZS_FollowerClimbLadderDown, 1, "");
            return LOOP_END;
        };
        
        return LOOP_CONTINUE;
    };
    
    FUNC VOID ZS_FollowerAssessLadderClimbDown_End ()
    {
    };
    
    //---
    
    FUNC VOID B_FollowPC_AssessSC ()
    {
        var int removeTorch; removeTorch = FALSE;
    
    //--- Ladder climbing update
    
        var string aniName;
        aniName = NPC_GetAniName (hero);
        
        if (STR_StartsWith (aniName, "T_LADDER")) {
            //Climbing Up animations
            if (Hlp_StrCmp (aniName, "T_LADDER_S0_2_S1"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S1_2_S2"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S2_2_S3"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S3_2_S4"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S4_2_S5"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S5_2_S6"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S6_2_S7"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S7_2_S8"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S8_2_S9"))
            {
                AI_StartState (self, ZS_FollowerAssessLadderClimbUp, 1, "");
                 return;
            };
            
            //Climbing Down animations
            if (Hlp_StrCmp (aniName, "T_LADDER_S1_2_S0"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S2_2_S1"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S3_2_S2"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S4_2_S3"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S5_2_S4"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S6_2_S5"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S7_2_S6"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S8_2_S7"))
            || (Hlp_StrCmp (aniName, "T_LADDER_S9_2_S8"))
            {
                AI_StartState (self, ZS_FollowerAssessLadderClimbDown, 1, "");
                return;
            };
        };
    
    //--- Immersive AI
        ...
    Geändert von F a w k e s (10.07.2019 um 13:16 Uhr) Grund: adding B_ClimbingLadderStop

  5. Beiträge anzeigen #5 Zitieren
    Local Hero Avatar von Mark56
    Registriert seit
    Sep 2010
    Beiträge
    254
     
    Mark56 ist offline
    What If I start climbing and then let go and go other way ? I think it would be wise to check during npc's vertical position, to determine which way to go.

  6. Beiträge anzeigen #6 Zitieren
    Serima Avatar von Fisk2033
    Registriert seit
    Dec 2010
    Ort
    Dresden
    Beiträge
    5.803
     
    Fisk2033 ist offline
    Really nice feature. I'll try it in G2.

  7. Beiträge anzeigen #7 Zitieren
    Local Hero
    Registriert seit
    Feb 2017
    Beiträge
    270
     
    F a w k e s ist offline
    Zitat Zitat von Mark56 Beitrag anzeigen
    What If I start climbing and then let go and go other way ? I think it would be wise to check during npc's vertical position, to determine which way to go.
    Hi Mark,
    NPC will then ignore ladder (this case is demonstrated right at the beginning at 8th second) and ZS_FollowPC is reinstated:
    (or maybe I misunderstood what you mean ! )
    https://youtu.be/-j9yaMSBhAE?t=8s

  8. Beiträge anzeigen #8 Zitieren
    Local Hero Avatar von Mark56
    Registriert seit
    Sep 2010
    Beiträge
    254
     
    Mark56 ist offline
    Nice- didn't notice debug output. Nice stuff.
    Next nice thing would be NPC to detect jumping, they do jump up, but maybe it would be nice if they "replicated" jump (with delay) - when you jump from ledge to ledge, npc would fall. => go to the position and rotation (insert temporary freepoint) of jump and play the animation.

  9. Beiträge anzeigen #9 Zitieren
    Local Hero
    Registriert seit
    Feb 2017
    Beiträge
    270
     
    F a w k e s ist offline
    Zitat Zitat von Mark56 Beitrag anzeigen
    Nice- didn't notice debug output. Nice stuff.
    Next nice thing would be NPC to detect jumping, they do jump up, but maybe it would be nice if they "replicated" jump (with delay) - when you jump from ledge to ledge, npc would fall. => go to the position and rotation (insert temporary freepoint) of jump and play the animation.
    Hi Mark,
    I like the idea - but when it comes to execution ... This would be next level of AI programming, maybe one day someone not as lazy as me can write such improvement

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
Impressum | Link Us | intern
World of Gothic © by World of Gothic Team
Gothic, Gothic 2 & Gothic 3 are © by Piranha Bytes & Egmont Interactive & JoWooD Productions AG, all rights reserved worldwide