|
-
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.
-
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
-
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)
-
Zitat von pawbuj
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 thenwe 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. elsewe 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 yesNPC will climb up the ladder and AI_ContinueRoutine will reapply ZS_FollowPC. elseNPC 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 thenwe 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. elsewe 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 yesNPC will climb down the ladder and AI_ContinueRoutine will reapply ZS_FollowPC. elseNPC 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
-
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.
-
Really nice feature. I'll try it in G2.
-
Zitat von Mark56
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
-
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.
-
Berechtigungen
- Neue Themen erstellen: Nein
- Themen beantworten: Nein
- Anhänge hochladen: Nein
- Beiträge bearbeiten: Nein
|
|