Hello folks,
With Auronen we were working on weapon-stacking, well actually on weapon-splitting feature for G1. Weapon-stacking is something that you can easily achieve by adding ITEM_MULTI flag to your weapons:
Disadvantage with weapon-stacking is - when you equip weapon with ITEM_MULTI flag, all items in same inventory slot are 'equipped'. If you have more pieces of same weapon and you would like to sell extra pieces, you have to unequip, sell and then re-equip it. With this feature equipped weapon will always go into its own item slot. (same behaviour as in G2A) Demonstration here: [Video]
Going forward we will add more features into this repository. Auronen is working on a cool G2A feature, he will probably let us know more about it shortly
Please be aware this feature requires both Ikarus and Lego. Also it is experimental - there might be some flaws, if you face any issues - let us know
Geändert von F a w k e s (23.12.2021 um 08:20 Uhr)
Grund: Updating title
Hello people,
I wanted to have a relatively easy way to add new damage types and damage calculation functions to the game, and this is my first attempt.
I tried to polish it as much as possible, I took inspiration for the "API" from LeGo (amazing thing).
The whole feature uses hash tables from LeGo to store PermMem handles for items that have new damage types assigned. (a InstanceID and PermMem handle pair)
It is still work in progress I want to add functionality for spells as well + dynamic item instances for sharpening/enchantment type of effects.
It is going to be a part of the next version of AFSP.
What it does:
This script allows you to define your own damage types and define your own damage calculation functions for said damage types.
You can also overwrite damage calculation for built-in gothic damage (divided by fists/mosnters, melee and ranged).
Automatic on_equip and on_unequip function for new damage types.
What it doesn't do (yet):
doesn't print(log) nice debug messages on hit
doesn't handle spells
doesn't have dynamic instances
You also need some class definitions (github link) and it's G1 counterpart (github link)
Spoiler:(zum lesen bitte Text markieren)
Code:
/* AF_EnhancedDamage*
* Authors: Auronen & Fawkes
* Version: 0.01 first draft
*
* Description:
* This script allows you to define your own damage types and define
* your own damage calculation functions for said damage types.
* You can also overwrite damage calculation for built-in gothic damage.
* Automatic on_equip and on_unequip function for new damage types.
*
* How to use:
* 1. First define your damage types indices as such:
* User defined constants:
* AFED_Innos = 0;
* AFED_Beliar = 1;
* AFED_Adanos = 2;
* AFED_Demon = 3;
* AFED_Max = 4;
*
* ==== in AF_EnhancedDamage_InitWeapons() ====
*
* 2. If desired, register your custom damage calculation functions
*
* AFED_SetFunc(AFED_Innos, calcInnosDmg);
* 3. Define your custom damage and armour values for items
* AFED_SetDamage_Item (ItMw_2H_Blessed_03, AFED_Innos , 35);
* AFED_SetArmour_Item (ITAR_PAL_H , AFED_Beliar, 45);
* AFED_SetArmour_Item (ITAR_PAL_H , AFED_Demon , 35);
* 4. Assign attack damage value and type for monsters in their respective instance definitions
* INSTANCE Molerat (Mst_Default_Molerat)
* {
* B_SetVisuals_Molerat();
* Npc_SetToFistMode(self);
* CreateInvItems (self, ItFoMuttonRaw, 1);
*
* // add damage and armour values here
* AFED_SetArmour_Npc (self, AFED_Holy, 9);
* AFED_SetDamage_Npc (self, AFED_Holy, 5);
* };
*
*
* "API":
* Damage calculation functions:
* There are two types of damage calculation functions you can define
* * Vanilla Gothic damage calculation functions
* - fists/monsters
* - melee
* - ranged
* * Damage calculation functions for damage types defined by you
* - comes with predefined damage calculation function with this formula
* (all have minimal damage of 5 points)
* fists/monsters -> damage - resistance
* melee -> weapon damage - resistance
* ranged -> weapon damage + arrow damage - resistance
*
* Registering new damage functions
* If you wish to register your custom damage calculation function (with the correct signature!) you can use this function
*
* Overwriting base game damage calculation functions:
*
* AFED_OverwriteVanillaDamageFucn (var int weaponType, var func fnc)
* weaponType - choose from these three constants
* AFED_Fists
* AFED_Melee
* AFED_Ranged
* fnc - function with the correct signature
* int calcVanilla(var C_ITEM weapon, var C_NPC attacker, var C_NPC victim)
*
*
* Registering your damage calculation functions for newly defined damage types:
*
* AFED_SetFunc (var int damageType, var func fnc)
* damageType - damage type defined by you (see "User defined constants")
*
* fnc - function with the correct signature
* int calcAFED (var C_ITEM weapon, var C_NPC attacker, var C_NPC victim, var int damageType, var c_item arrow)
*
* Assigning damage/armour values
* AFED_SetDamage_Item (var C_ITEM npc, var int damageType, var int value)
* AFED_SetArmour_Item (var C_ITEM item, var int damageType, var int value)
*
* AFED_SetDamage_Npc (var C_NPC npc, var int damageType, var int value)
* AFED_SetArmour_Npc (var C_NPC npc, var int damageType, var int value)
*
* Retrieving assigned values
* AFED_GetDamage_Item (var C_ITEM item, var int damageType)
* AFED_GetArmour_Item (var C_ITEM item, var int damageType)
*
* AFED_GetDamage_Npc (var C_NPC npc, var int damageType)
* AFED_GetArmour_Npc (var C_NPC npc, var int damageType)
*
*
*
*
* =======================================================================
* ======================== debug print functions ========================
* =======================================================================
*
* These are functions that print out damage/armour with zSpy as output,
* names should be self-explanatory :)
*
* AFED_PrintArmour (var c_npc instance)
* AFED_PrintDamage (var c_npc instance)
* AFED_PrintMelee (var c_npc instance)
* AFED_PrintRanged (var c_npc instance)
*
* =================================================================
* ======================== extra functions ========================
* =================================================================
*
* Functions that I made during the development of this "feature"
* int NPC_InsertAllItems(var c_npc npc_inst)
* Inserts all items defined in the symbol table into inventory of npc_inst
* return: number of items inserted
*
* int Hlp_IsEmptyInstance(var int symbolID)
* Checks if instance is empty instance -> created by MEM_NullToInst
* return: TRUE or FALSE
*
*/
/* NOTES:
* I cannot wrap my head around the symbol table and instances and PermMem to save
* newly created symbols of item instances :( - Auronen
*
*
*/
/* TODO:
* [x] Damage calculation functions for vanilla damage
* [] Handle spells and allow to add new damage types to spells
* [] Dynamic instances for sharpening/enchantment type of effects
*
*/
func int Hlp_IsEmptyInstance(var int symbolID)
{
if (symbolID == 0)
{
MEM_Info("Symbol not found!");
return -1;
};
return !(MEM_ReadInt(MEM_GetSymbolByIndex(symbolID) + zCParSymbol_content_offset) == MEM_ReadInt(MEM_GetSymbolByIndex(symbolID) + zCParSymbol_offset_offset ) == 0);
};
func int NPC_InsertAllItems(var c_npc npc_inst)
{
// rewritten from Degenerated's post https://forum.worldofplayers.de/forum/threads/1476153-Creating-every-item-via-scripts?p=25089817&viewfull=1#post25089817
// many thanks to mud-freak (@szapp) for pointing out my mistake with the data stack :)
var int baseSym; baseSym = MEM_GetSymbolIndex("C_ITEM");
var int found; found = 0;
while(1); var int i;
if (i == MEM_Parser.symtab_table_numInArray -1 ) { break; };
var zCPar_Symbol s;
s = _^(MEM_GetSymbolByIndex(i));
var int s_parent_index;
if (!s.parent)
{
s_parent_index = 0;
}
else
{
var zCPar_Symbol symb2;
symb2 = _^(s.parent);
s_parent_index = MEM_GetSymbolIndex( symb2.name );
};
if((s_parent_index == 0) || ((s.bitfield & zPAR_TYPE_INSTANCE) != zPAR_TYPE_INSTANCE))
{
i += 1;
continue;
};
var zCPar_Symbol p;
p = _^(MEM_GetSymbolByIndex(s_parent_index));
var int p_parent_index;
if (!p.parent)
{
p_parent_index = 0;
}
else
{
var zCPar_Symbol symb3;
symb3 = _^(p.parent);
p_parent_index = MEM_GetSymbolIndex( symb3.name );
};
var int pBase;
pBase = s_parent_index;
if(((p.bitfield & zPAR_TYPE_PROTOTYPE) == zPAR_TYPE_PROTOTYPE) && (p_parent_index != 0))
{
pBase = p_parent_index;
};
if(baseSym == pBase)
{
// Not item instances, but c_item variables in the scripts
/*
if ( s.content == s.offset && s.offset == 0 )
{
PVs("oCItem.name", s.name);
};
*/
// Creates one item in npc_inst inventory ---> for every item
// If s.offset uncommented --> only for items that don't have instance yet (they are not in someone's inventory or in the world)
if ( s.content != 0 /*&& s.offset == 0*/ )
{
found += 1;
CreateInvItem (npc_inst, i);
};
};
i += 1;
end;
MEM_Info( ConcatStrings("Number of items found and inserted: ", IntToString(found) ) );
return found;
};
//========================================
// [User defined] Damage types
//========================================
const int AFED_Holy = 0;
const int AFED_Pure = 1;
// !Do not change the name, just the value!
const int AFED_Max = 2;
//========================================
// Constants
//========================================
const int oSDamageDescriptor_attackerNpc = 8;
const int oSDamageDescriptor_itemWeapon = 20;
const int oSDamageDescriptor_spellID = 24;
/* if it is the same, we don't have to switch :)
const int oSDamageDescriptor_attackerNpc_G2 = 8;
const int oSDamageDescriptor_itemWeapon_G2 = 20;
const int oSDamageDescriptor_spellID_G2 = 24;
*/
const int AFED_CalcFuncArray = 0; // array - holds damage calculation functions
const int AFED_CalcFuncArrayVanilla = 0; // array - holds vanilla damage calculation functions
const int AFED_HT = 0; // hash table - holds PermMem handles for AFDamage objects
const int AFED_LST_Ammunition = 0; // list - hold instaceIDs of ammunition
const int AFED_Fists = 0;
const int AFED_Melee = 1;
const int AFED_Ranged = 2;
//========================================
// class, holds two array handles
//========================================
class AFDamage
{
var int damageHndl;
var int armourHndl;
};
instance AFDamage@(AFDamage);
/*
I don't think I need this (eve though it would probably save some load time)
The init function reconstructs the damage objects based on the AF_EnhancedDamage_InitWeapons() function
To "renew" registered damage to monsters that are already on the map, I should
insert and then delete every monster on the TOT waypoint, to run their instance "functions"
to insert their defined damage objects into the HT
//========================================
// Archiver function
//========================================
func void AFDamage_Archiver(var AFDamage this)
{
PM_SaveInt ("damageHndl" , this.damageHndl );
PM_SaveInt ("armourHndl" , this.armourHndl );
};
//========================================
// Unarchiver function
//========================================
func void AFDamage_Unarchiver(var AFDamage this)
{
if (PM_Exists("damageHndl" )) { this.damageHndl = PM_Load ("damageHndl" ); };
if (PM_Exists("armourHndl" )) { this.armourHndl = PM_Load ("armourHndl" ); };
};
*/
//========================================
// Creates array to hold handles of damage objects
//========================================
func int _AFED_CreateArray ()
{
var int array; array = new(zCArray@);
var zCArray arr; arr = get(array);
arr.array = MEM_Alloc(AFED_Max*4);
arr.numInArray = AFED_Max;
arr.numAlloc = AFED_Max;
return array;
};
//========================================
// Registers damage calculation function
//========================================
func void AFED_SetFunc (var int damageType, var func fnc)
{
if (damageType >= AFED_Max)
{
MEM_Warn("Trying to set a function for a non valid damage type.");
return;
};
var int fncPtr; fncPtr = MEM_GetFuncPtr(fnc);
MEM_ArrayWrite(getPtr(AFED_CalcFuncArray), damageType, fncPtr);
};
//========================================
// Overrides vanilla damage calculation function
//========================================
func void AFED_OverwriteVanillaDamageFucn (var int weaponType, var func fnc)
{
if (weaponType > 3 || weaponType < 0)
{
MEM_Warn("Vanilla functions overwrite out of bounds. Use 0 - fists/monsters, 1 - melee, 2 - ranged");
};
var int fncPtr;
fncPtr = MEM_GetFuncPtr(fnc);
MEM_ArrayWrite(getPtr(AFED_CalcFuncArrayVanilla), weaponType, fncPtr);
};
//========================================
// Inits the array for damage calculation functions
//========================================
func void AFED_InitDamCalcArr()
{
// create array
AFED_CalcFuncArray = _AFED_CreateArray();
// fill array with default damage calc functions
var int fncPtr; fncPtr = MEM_GetFuncPtr(calcDefault);
repeat(i, AFED_Max); var int i;
MEM_ArrayWrite(getPtr(AFED_CalcFuncArray), i, fncPtr);
end;
};
//========================================
// Inits the array for vanilla damage calculation functions
//========================================
func void AFED_InitVanillaDamCalcArr()
{
var int array; array = new(zCArray@);
var zCArray arr; arr = get(array);
arr.array = MEM_Alloc(3*4);
arr.numInArray = 3;
arr.numAlloc = 3;
AFED_CalcFuncArrayVanilla = array;
};
//========================================
// Calculates damage using
// the registered functions
//========================================
func int AFED_CalculateDamage(var c_item weaponSymb, var c_npc attackerSymb, var c_npc victimSymb, var c_item arrowSymb)
{
var int dmg; dmg = 0;
var int funcPtr;
repeat(i, AFED_Max); var int i;
funcPtr = MEM_ArrayRead(getPtr(AFED_CalcFuncArray), i);
if (funcPtr)
{
MEM_PushInstParam(weaponSymb ); // oCItem - weapon doing damage
MEM_PushInstParam(attackerSymb ); // oCNpc - attacker NPC
MEM_PushInstParam(victimSymb ); // oCNpc - victim NPC
MEM_PushIntParam (i ); // int - damage type
MEM_PushInstParam(arrowSymb ); // oCItem - arrow
MEM_CallByPtr(funcPtr);
dmg += MEM_PopIntResult();
}
else
{
MEM_Info("Ptr not found!");
};
end;
return dmg;
};
//========================================
// Calculates damage using
// the registered functions
//========================================
func int AFED_CalculateVanillaDamage(var c_item weaponSymb, var c_npc attackerSymb, var c_npc victimSymb, var int weaponType)
{
var int dmg; dmg = -1;
var int funcPtr;
funcPtr = MEM_ArrayRead(getPtr(AFED_CalcFuncArrayVanilla), weaponType); // weaponType - fists/melee/ranged
if (funcPtr)
{
MEM_PushInstParam(weaponSymb ); // oCItem - weapon doing damage
MEM_PushInstParam(attackerSymb ); // oCNpc - attacker NPC
MEM_PushInstParam(victimSymb ); // oCNpc - victim NPC
MEM_CallByPtr(funcPtr);
dmg = MEM_PopIntResult();
};
return dmg;
};
//========================================
// Creates instance, return PermMem handle
//========================================
func int AFED_Create ()
{
var int damHndl; damHndl = new(AFDamage@);
var AFDamage objDam; objDam = get(damHndl);
objDam.damageHndl = _AFED_CreateArray();
objDam.armourHndl = _AFED_CreateArray();
return damHndl;
};
//========================================
// Intern - set value
//========================================
func void _AFED_SetValue (var int instID, var int damageType, var int value, var int damage)
{
// if itemID - oCItem or oCNpc - is not in hash table, create damage object
// for this specific instanceID
if (!_HT_Has(getPtr(AFED_HT), instID))
{
HT_Insert(AFED_HT, AFED_Create(), instID);
};
var AFDamage damObj; damObj = get(HT_Get(AFED_HT, instID));
if (!damage)
{
MEM_ArrayWrite(getPtr(damObj.damageHndl), damageType, value);
return;
}
else
{
MEM_ArrayWrite(getPtr(damObj.armourHndl), damageType, value);
return;
};
};
//========================================
// Sets damage types and their values
//========================================
func void AFED_SetDamage_Item (var C_ITEM item, var int damageType, var int value)
{
var int instID; instID = Hlp_GetInstanceID(item);
_AFED_SetValue (instID, damageType, value, 0);
};
func void AFED_SetDamage_Npc (var C_NPC npc, var int damageType, var int value)
{
var int instID; instID = Hlp_GetInstanceID(npc);
_AFED_SetValue (instID, damageType, value, 0);
};
//========================================
// Sets armour against damage type and its value
//========================================
func void AFED_SetArmour_Item (var C_ITEM item, var int damageType, var int value)
{
var int instID; instID = Hlp_GetInstanceID(item);
_AFED_SetValue (instID, damageType, value, 1);
};
func void AFED_SetArmour_Npc (var C_NPC npc, var int damageType, var int value)
{
var int instID; instID = Hlp_GetInstanceID(npc);
_AFED_SetValue (instID, damageType, value, 1);
};
//========================================
// Intern - get value
//========================================
func int _AFED_GetValue (var int symbIndex, var int damageType, var int damage)
{
// if the handle is assigned (the array already exists)
if (!_HT_Has(getPtr(AFED_HT), symbIndex))
{
// return 0, in order to continue the calculations
return 0;
};
var AFDamage damObj; damObj = get(HT_Get(AFED_HT, symbIndex));
if (!damage)
{
return MEM_ArrayRead(getPtr(damObj.damageHndl), damageType);
}
else
{
return MEM_ArrayRead(getPtr(damObj.armourHndl), damageType);
};
};
//========================================
// Gets a value of the damage type
//========================================
func int AFED_GetDamage_Item (var C_ITEM item, var int damageType)
{
var int instID; instID = Hlp_GetInstanceID(item);
return _AFED_GetValue (instID, damageType, 0);
};
func int AFED_GetDamage_Npc (var C_NPC npc, var int damageType)
{
var int instID; instID = Hlp_GetInstanceID(npc);
return _AFED_GetValue (instID, damageType, 0);
};
//========================================
// Gets armour against specified damage type
//========================================
func int AFED_GetArmour_Item (var C_ITEM item, var int damageType)
{
var int instID; instID = Hlp_GetInstanceID(item);
return _AFED_GetValue (instID, damageType, 1);
};
func int AFED_GetArmour_Npc (var C_NPC npc, var int damageType)
{
var int instID; instID = Hlp_GetInstanceID(npc);
return _AFED_GetValue (instID, damageType, 1);
};
//========================================
// [HOOK] onDamage_Event
//========================================
func void _hook_OnDmg_Event ()
{
MEM_Info("=====================================_hook_OnDmg_Event=====================================");
var int victimPtr; // oCNpc* - for reading resistance values/buffs/debuffs
var int attackerPtr; // oCNpc* - for reading perks/buffs/debuffs
var int weaponPtr; // oCItem* - for reading damage types + dynamic things (in the future - possibly with the help of DII?)
var int ddPtr; // oSDamageDescriptor*
var int dmg; // original damage
var c_npc attacker;
var c_npc victim;
var c_item weapon;
var c_item arrow;
if (MEMINT_SwitchG1G2 (TRUE, FALSE)) //G1 & G2A both have different parameters
{
victimPtr = EDI;
ddPtr = MEM_ReadInt (ESP + 548);
dmg = EAX;
}
else //G2A not verified!! It works :smirk:
{
victimPtr = EBP;
ddPtr = MEM_ReadInt (ESP + 644);
dmg = EDI;
};
MEM_Info(ConcatStrings("Vanilla damage: ", IntToString(dmg)));
weaponPtr = MEM_ReadInt(ddPtr + oSDamageDescriptor_itemWeapon);
// =============== Burn ticks ===============
if (!MEM_ReadInt(ddPtr + oSDamageDescriptor_attackerNpc))
{
// for burning damage ticks?? - they do nothing in G2A
return;
};
attacker = _^(MEM_ReadInt(ddPtr + oSDamageDescriptor_attackerNpc));
victim = _^(victimPtr);
// =============== Magic ===============
/*
* NOTE:
* Magic is not yet implemented, so we just skip it :)
*/
var int spellid; spellid = MEM_ReadInt(ddPtr + oSDamageDescriptor_spellID);
// PV("Spell ID", spellid);
MEM_Info(ConcatStrings("Spell ID : ", IntToString(spellid)));
if (spellid >= 1) // ID 0 spell is SPL_PalLight (G2) and Light spell (G1) - they don't have a target :)
{
// PVs("Spell name", MEM_ReadStringArray(_@s(TXT_SPELLS), spellid));
MEM_Info(ConcatStrings("Spell name : ", MEM_ReadStringArray(_@s(TXT_SPELLS), spellid)));
// PV ("Magic damage", dmg);
MEM_Info(ConcatStrings("Magic damage : ", IntToString(dmg)));
return;
};
// =============== No weapon ===============
// weapon pointer is zero if the attacker is attacking without a weapon - animals (=) fists
if (!weaponPtr && spellid <= 0)
{
MEM_Info("=============== No weapon ===============");
weapon = MEM_NullToInst();
arrow = MEM_NullToInst();
/* Fists and animals are based on strength
* so we just add extra damage on top of the "raw physical" damage
*/
if ( -1 != AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Fists)) // if damage function is defined
{
dmg = AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Fists);
};
MEM_Info(ConcatStrings("Base damage: ", IntToString(dmg)));
// gets added to the base damage (calculated from strength)
dmg += AFED_CalculateDamage(weapon, attacker, victim, arrow);
MEM_Info(ConcatStrings("Total damage: ", IntToString(dmg)));
if (MEMINT_SwitchG1G2 (TRUE, FALSE))
{
EAX = dmg;
return;
}
else
{
EDI = dmg;
return;
};
}
else
{
weapon = _^(weaponPtr);
};
// =============== Ranged weapon ===============
/*
* ItRw_Bolt
* ItRw_Arrow
* and so on
*/
if (List_Contains(AFED_LST_Ammunition, Hlp_GetInstanceID(weapon)))
{
MEM_Info("=======================Ranged weapon=======================");
arrow = _^(_@(weapon));
// getting bow/crossbow from attacker (even if he already put it away)
if (NPC_IsInFightMode(attacker, FMODE_FAR))
{
weapon = NPC_GetReadiedWeapon (attacker);
}
else if (NPC_HasEquippedRangedWeapon (attacker))
{
weapon = NPC_GetEquippedRangedWeapon (attacker);
};
if ( -1 != AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Ranged)) // if damage func is defined
{
dmg = AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Ranged);
};
MEM_Info(ConcatStrings("Base damage: ", IntToString(dmg)));
if (MEMINT_SwitchG1G2 (TRUE, FALSE))
{
EAX = dmg + AFED_CalculateDamage(weapon, attacker, victim, arrow);
MEM_Info(ConcatStrings("Total damage: ", IntToString(EAX)));
return;
}
else
{
EDI = dmg + AFED_CalculateDamage(weapon, attacker, victim, arrow);
MEM_Info(ConcatStrings("Total damage: ", IntToString(EDI)));
return;
};
};
// =============== Melee weapon ===============
arrow = MEM_NullToInst();
MEM_Info("=============== Melee weapon ===============");
if ( -1 != AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Melee)) // if damage func is defined
{
dmg = AFED_CalculateVanillaDamage(weapon, attacker, victim, AFED_Melee);
};
MEM_Info(ConcatStrings("Base damage: ", IntToString(dmg)));
if (MEMINT_SwitchG1G2 (TRUE, FALSE))
{
EAX = dmg + AFED_CalculateDamage(weapon, attacker, victim, arrow);
MEM_Info(ConcatStrings("Total damage: ", IntToString(EAX)));
return;
}
else
{
EDI = dmg + AFED_CalculateDamage(weapon, attacker, victim, arrow);
MEM_Info(ConcatStrings("Total damage: ", IntToString(EDI)));
return;
};
};
//========================================
// [TEMPLATE] Default damage calculation function
//========================================
func int calcDefault (var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb, var int damageType, var c_item arrowSymb)
{
var int dmg;
var int resistance;
if (Hlp_IsEmptyInstance(weaponSymb)) // if weapon is empty instance - calculate damage from attacker (fists and animals)
{
dmg = AFED_GetDamage_Npc(attackerSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else if (Hlp_IsEmptyInstance(arrowSymb)) // if arrow is empty -> it's melee weapon
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else // if weapon and arrow are valid instances -> it's ranged weapon
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
dmg += AFED_GetDamage_Item(arrowSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
};
if (dmg - resistance > 5)
{
return dmg - resistance;
}
else
{
return 5;
};
};
//========================================
// Finds all instances with ITEM_KAT_MUN
// flag and puts their instanceIDs into a list
//========================================
func void AFED_Ammo(var int npc)
{
var oCNpc npc_; npc_ = _^(npc);
var zCListSort list;
var oCItem item;
list = _^(npc_.inventory2_oCItemContainer_contents);
while (list.next != 0);
list = _^(list.next);
item = _^(list.data);
if (item.mainflag == ITEM_KAT_MUN)
{
if (!AFED_LST_Ammunition)
{
AFED_LST_Ammunition = List_Create(Hlp_GetInstanceID(item));
}
else
{
List_Add(AFED_LST_Ammunition, Hlp_GetInstanceID(item));
};
};
end;
};
//========================================
// [HOOK] On equip item add the resistances
// to the AFED_Damage object
//========================================
func void _hook_oCNpc_EquipItem ()
{
// 0068F940 .text Debug data ?EquipItem@oCNpc@@QAEXPAVoCItem@@@Z
// 0x007323C0 public: void __thiscall oCNpc::EquipItem(class oCItem *)
// MEM_Info("===== _hook_oCNpc_EquipItem =====");
var int itemPtr; itemPtr = MEM_ReadInt (ESP + 4);
var int instID_Item;
var int instID_Npc;
var C_NPC npc; npc = _^(ECX);
instID_Npc = Hlp_GetInstanceID(npc);
if (itemPtr)
{
var C_ITEM itm; itm = _^(itemPtr);
instID_Item = Hlp_GetInstanceID(itm);
// if npc is not yet registered in the hash table
if (!_HT_Has(getPtr(AFED_HT), instID_Npc))
{
HT_Insert(AFED_HT, AFED_Create(), instID_Npc);
};
repeat(i, AFED_Max); var int i;
AFED_SetArmour_Npc(npc, i, AFED_GetArmour_Npc(npc, i) + AFED_GetArmour_Item(itm, i) );
end;
};
};
//========================================
// [HOOK] On unequip item subtract the resistances
// to the AFED_Damage object
//========================================
func void _hook_oCNpc_UnequipItem ()
{
// 0068FBC0 .text Debug data ?UnequipItem@oCNpc@@QAEXPAVoCItem@@@Z
// 0x007326C0 public: void __thiscall oCNPC::UnequipItem(class oCItem *)
// MEM_Info("===== _hook_oCNpc_UnequipItem =====");
var int itemPtr; itemPtr = MEM_ReadInt (ESP + 4);
var int instID_Item;
var int instID_Npc;
var C_NPC npc; npc = _^(ECX);
instID_Npc = Hlp_GetInstanceID(npc);
if (itemPtr)
{
var C_ITEM itm; itm = _^(itemPtr);
instID_Item = Hlp_GetInstanceID(itm);
// if npc is not yet registered in the hash table
if (!_HT_Has(getPtr(AFED_HT), instID_Npc))
{
HT_Insert(AFED_HT, AFED_Create(), instID_Npc);
};
repeat(i, AFED_Max); var int i;
AFED_SetArmour_Npc(npc, i, AFED_GetArmour_Npc(npc, i) - AFED_GetArmour_Item(itm, i) );
end;
};
};
//========================================
// [User defined] Here you define:
// * damage calculation functions
// * overwrite vanilla damage calculation functions
// * damage/armour for items
//========================================
func void AF_EnhancedDamage_InitWeapons()
{
MEM_Info("=====================================AF_EnhancedDamage_InitWeapons=====================================");
MEM_Info("Registering damage calculation functions.");
AFED_SetFunc(AFED_Holy, calcHoly);
AFED_SetFunc(AFED_Pure, calcPure);
MEM_Info("Setting damage.");
AFED_SetDamage_Item(ItMw_1h_Vlk_Dagger, AFED_Holy, 10);
AFED_SetDamage_Item(ItMw_1h_Vlk_Dagger, AFED_Pure, 20);
AFED_SetArmour_Item(ItSh_RoundShield, AFED_Holy, 15);
AFED_SetArmour_Item(ItSh_RoundShield, AFED_Pure, 25);
AFED_SetDamage_Item(ItRw_Bow_L_01, AFED_Holy, 9);
AFED_SetDamage_Item(ItRw_Bow_L_01, AFED_Pure, 22);
// These ore G1 items :)
// AFED_SetDamage_Item(ItMw_1H_Sword_Short_01, AFED_Holy, 10);
// AFED_SetDamage_Item(ItMw_1H_Sword_Short_01, AFED_Pure, 20);
// AFED_SetArmour_Item(VLK_ARMOR_L, AFED_Holy, 15);
// AFED_SetArmour_Item(VLK_ARMOR_L, AFED_Pure, 25);
// AFED_SetDamage_Item(ItRw_Bow_Small_01, AFED_Holy, 9);
// AFED_SetDamage_Item(ItRw_Bow_Small_01, AFED_Pure, 22);
};
//========================================
// Initialisation function for AF_EnhancedDamage
//========================================
func void AF_EnhancedDamage_Init ()
{
MEM_Info("=====================================G12_OnDmgEvent_Init=====================================");
//Addresses taken from Lehona, original post:
//https://forum.worldofplayers.de/forum/threads/1434640-Erh%C3%B6hter-Schaden-f%C3%BCr-Untote-durch-Nahkampfwaffen?p=24356119&viewfull=1#post24356119
const int once = 0;
if (!once)
{
// Insert all items to ItemHelper's inventory
// Items need to exist to have InstanceID :)
GetItemHelper();
NPC_InsertAllItems(ITEM_HELPER_INST);
// Create a list that holds the ammunition instanceIDs
// ItRw_Bolt, ItRw_Arrow, etc.
AFED_Ammo(_@(ITEM_HELPER_INST));
/* This was previous version - not as clean :)
// Creates new AIVar - to hold handle of the AFDamage object for NPCs
AFED_AIVAR_Hndl = AF_AIVAR_CreateAIVar(); */
// Create hash table to hold handles for items and NPCs (instanceIDs are used as keys)
AFED_HT = HT_Create();
// Initiates the array for damage calculation functions
AFED_InitDamCalcArr();
// Initiates the array for vanilla damage calculation functions
AFED_InitVanillaDamCalcArr();
// Engine hooks
HookEngine(MEMINT_SwitchG1G2(7567329, 6736583), MEMINT_SwitchG1G2(6, 5), "_hook_OnDmg_Event");
// HookEngine(7798240, 6, "_hook_oCObjectFactory_CreateItem");
HookEngine(MEMINT_SwitchG1G2(6879552, 7545792), MEMINT_SwitchG1G2(7, 7), "_hook_oCNpc_EquipItem");
HookEngine(MEMINT_SwitchG1G2(6880192, 7546560), MEMINT_SwitchG1G2(7, 6), "_hook_oCNpc_UnequipItem");
// I hook this function, and then use FF to run a function that refreshes items in the status screen only once :)
// HookEngine(MEMINT_SwitchG1G2(4683824, 4713824), MEMINT_SwitchG1G2(7, 7), "_hook_oCStatusScreen_Show");
once = 1;
};
// Initiates all assigned weapon damage/armour
AF_EnhancedDamage_InitWeapons();
};
//========================================
// Default damage calc functions, that can be changed by the user
// source: https://forum.worldofplayers.de/forum/threads/127320-Damage-System?p=2198181&viewfull=1#post2198181
//========================================
func int calcFistDefault(var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb)
{ // total damage = attacker strength - victim protection
return attackerSymb.attribute[ATR_STRENGTH] - MEM_ReadIntArray(_@(victimSymb.protection), attackerSymb.damagetype);
};
func int calcMeleeDefault(var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb)
{
// MEM_Info("============== calcMeleeDefault ==============");
var int rndm; rndm = r_MinMax(1, 100);
var int critical;
var int dmgType; dmgType = weaponSymb.damagetype;
var int totalDmg;
if (weaponSymb.flags & ITEM_SWD || weaponSymb.flags & ITEM_AXE)
{
if (Npc_GetTalentValue(attackerSymb, NPC_TALENT_1H) >= rndm)
{
critical = TRUE;
};
}
else if (weaponSymb.flags & ITEM_2HD_SWD || weaponSymb.flags & ITEM_2HD_AXE)
{
if (Npc_GetTalentValue(attackerSymb, NPC_TALENT_2H) >= rndm)
{
critical = TRUE;
};
};
if (!critical)
{ // Normal : (weapon damage + strength - enemy armour protection - 1) / 10 = total damage, if (total damage < 5) then total damage = 5
totalDmg = (weaponSymb.damageTotal + attackerSymb.attribute[ATR_STRENGTH] - MEM_ReadIntArray(_@(victimSymb.protection), dmgType) - 1) / 10;
if (totalDmg < 5)
{
return 5;
};
return totalDmg;
}
else
{ // Critical: weapon damage + strength - enemy armour protection = total damage, if (total damage < 5) then total damage = 5
totalDmg = (weaponSymb.damageTotal + attackerSymb.attribute[ATR_STRENGTH] - MEM_ReadIntArray(_@(victimSymb.protection), dmgType) );
if (totalDmg < 5)
{
return 5;
};
return totalDmg;
};
};
func int calcRangedDefault(var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb)
{ // total damage = weapon damage + attacker dexterity - victim armour protection
return weaponSymb.damageTotal + attackerSymb.attribute[ATR_STRENGTH] - MEM_ReadIntArray(_@(victimSymb.protection), weaponSymb.damagetype);
};
//========================================
// Debug print - prints NPCs armour values
// into zSpy in order of definition
//========================================
func void AFED_PrintArmour(var c_npc inst)
{
repeat(i, AFED_Max); var int i;
MEM_Info(IntToString(AFED_GetArmour_Npc(inst, i)));
end;
};
func void AFED_PrintDamage(var c_npc inst)
{
repeat(i, AFED_Max); var int i;
MEM_Info(IntToString(AFED_GetDamage_Npc(inst, i)));
end;
};
func string AFED_PrintMelee(var c_npc inst)
{
var c_item itm;
if ( NPC_IsInFightMode(inst, FMODE_MELEE) )
{
itm = NPC_GetReadiedWeapon (inst);
}
else if ( NPC_HasEquippedMeleeWeapon(inst) )
{
itm = NPC_GetEquippedMeleeWeapon (inst);
}
else
{
return "Defined NPC does not have melee weapon equipped or in hand.";
};
repeat(i, AFED_Max); var int i;
MEM_Info(IntToString(AFED_GetDamage_Item(itm, i)));
end;
return "Printed to zSpy";
};
func string AFED_PrintRanged(var c_npc inst)
{
var c_item itm;
if (NPC_IsInFightMode(inst, FMODE_FAR))
{
itm = NPC_GetReadiedWeapon (inst);
}
else if (NPC_HasEquippedRangedWeapon (inst))
{
itm = NPC_GetEquippedRangedWeapon (inst);
}
else
{
return "Defined NPC does not have melee weapon equipped or in hand.";
};
repeat(i, AFED_Max); var int i;
MEM_Info(IntToString(AFED_GetDamage_Item(itm, i)));
end;
return "Printed to zSpy";
};
//========================================
// DEMO
//========================================
func int calcHoly (var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb, var int damageType, var c_item arrowSymb)
{
var int dmg;
var int resistance;
if (Hlp_IsEmptyInstance(weaponSymb)) // if weapon is empty instance - calculate damage from attacker (fists and animals)
{
dmg = AFED_GetDamage_Npc(attackerSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else if (Hlp_IsEmptyInstance(arrowSymb)) // if arrow is empty -> its melee weapon
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
dmg += AFED_GetDamage_Item(arrowSymb, damageType);
resistance = AFED_GetArmour_Npc(victimSymb, damageType);
};
if (dmg - resistance > 0)
{
MEM_Info(ConcatStrings("Holy damage: ", IntToString(dmg - resistance)));
return dmg - resistance;
}
else
{
MEM_Info(ConcatStrings("Holy damage: ", IntToString(0)));
return 0;
};
};
func int calcPure (var C_ITEM weaponSymb, var C_NPC attackerSymb, var C_NPC victimSymb, var int damageType, var c_item arrowSymb)
{
// pure damage ignores resistances :)
var int dmg;
var int resistance;
if (Hlp_IsEmptyInstance(weaponSymb)) // if weapon is empty instance - calc damage from attacker (fists and animals)
{
dmg = AFED_GetDamage_Npc(attackerSymb, damageType);
// resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else if (Hlp_IsEmptyInstance(arrowSymb)) // if arrow is empty -> its melee weapon
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
// resistance = AFED_GetArmour_Npc(victimSymb, damageType);
}
else
{
dmg = AFED_GetDamage_Item(weaponSymb, damageType);
dmg += AFED_GetDamage_Item(arrowSymb, damageType);
// resistance = AFED_GetArmour_Npc(victimSymb, damageType);
};
MEM_Info(ConcatStrings("Pure damage: ", IntToString(dmg)));
return dmg;
};
Please let me know if there are any problems and what you think. How do I improve my code or how do I improve my usage of LeGo, especially PermMem (it is amazing, but I still don't understand it that much)
Geändert von Auronen (25.07.2021 um 19:34 Uhr)
Grund: Nota about buggy function
Hello guys ,
I made another feature. I always disliked, how, when you are selecting a reward in a dialogue, you cannot see what the item is. Modders then resort to strings such as Digger's Trousers. Protection: 15, 5, 5. So I made a feature that allows you to preview the item (like you can see it in your inventory) you are going to get when you choose that dialogue option.
This is going to be a part of the next version of AFSP.
Here is the code of the feature:
Code:
/* AF_ItemPreview*
* Authors: Fawkes & Auronen
* Version: 0.01 first draft
*
* Description:
* This feature allows you to preview items during dialogues, when
* choosing a reward (Torrez in G1, Fisk (digger's trousers G1))
*
* How to use:
* For "full" dialogues you have to declare a variable "var int AFIP_ShowItem"
* and assign a item instance to it
* Example: var int AFIP_ShowItem; AFIP_ShowItem = ItMw_1h_Vlk_Dagger;
*
* And that's it!
*
* For Info_AddChoice types of dialogues it is a bit more elaborate
* In the same function you are declaring your Info choices you have to use
* AFIP_SetChoiceItem function, it takes two parameters:
* 1. function that runs when you choose that option
* 2. item instance
* In the function that runs when you select the option you declare a variable "var int AFIP_ShowItem"
*
* And that's it!
*
*
*
*
*
*/
/* NOTES:
*
*
*
*/
/* TODO:
*
*
* implement "rotate for inventory" function with user rotation?
*
* hook this to "oCItemContainer::DrawItemInfo" to have more rows for AF_EnhancedDamage.d?
* - this would need a more detailed API to set individual rows and other stuff
*
*/
//========================================
// Some wrapper functions for easier
// item positioning for LeGo Render.d
//========================================
func int Render_AddItemCenter(var int itemInst, var int x, var int y, var int w, var int h)
{
return +Render_AddItemPrio(itemInst, x-(w>>1), y-(h>>1), x+((w+1)>>1), y+((h+1)>>1), 0);
};
func int Render_AddItemCenterPrio(var int itemInst, var int x, var int y, var int w, var int h, var int priority)
{
return +Render_AddItemPrio(itemInst, x-(w>>1), y-(h>>1), x+((w+1)>>1), y+((h+1)>>1), priority);
};
func int Render_IsOpen(var int rndrHndl)
{
var RenderItem itm; itm = get(rndrHndl);
if (itm.view_open) {
return TRUE;
} else {
return FALSE;
};
};
//========================================
// Constants
//========================================
/* NOTE: I got these values from the game
* set to 1280x720 at 1.000 scale in SP
* This needs to be dynamic, but I do not
* know, how to get to the real formula
*/
const int AFIP_view = 0;
const int AFIP_rndObj = 0;
// dimensions
const int AFIP_height = 1845;
const int AFIP_width = 4608;
// position
const int AFIP_x = 1792;
const int AFIP_y = 6006;
const int AFIP_currentItem = 0;
const string AFIP_texture = "Inv_Back_Steal.tga";
const string AFIP_font = "FONT_OLD_10_WHITE.TGA";
const string AFIP_varName = "AFIP_SHOWITEM";
//========================================
// returns int value as string, returns
// empty string, when 0
//========================================
func string countValue (var int val)
{
if (val) { return IntToString(val); }
else { return ""; };
};
//========================================
// Deletes both of the views
//========================================
func void AFIP_Delete()
{
if (AFIP_rndObj) { Render_Remove(AFIP_rndObj); };
if (AFIP_view) { View_Delete(AFIP_view); };
AFIP_rndObj = 0;
AFIP_view = 0;
};
//========================================
// Creates and shows the item preview
//========================================
/* NOTES:
- item is passed into the function as a parser ID
(just the name of the item instance)
- it fits one more row (not used here)
- it is set up (hardcoded) to mimic the in game item preview (roughly)
*/
func void AFIP_Create(var int itemIn)
{
AFIP_currentItem = itemIn;
var int itemPtr; itemPtr = Itm_GetPtr(itemIn);
var oCItem itm; itm = _^(itemPtr);
Print_GetScreenSize();
if (!AFIP_view) // not really needed, but just to be sure
{
AFIP_view = View_Create(1, 1, 1000, 1000);
};
View_SetTexture(AFIP_view, AFIP_texture);
var string heading; heading = itm.description;
var int width; width = Print_ToVirtual(Print_GetStringWidth(heading, AFIP_font), PS_X) * PS_VMAX / AFIP_width;
var int height; height = 900; // this should be calculated based on the height of the view?
var int head; head = PS_VMax/30;
var int marg; marg = PS_VMax/64;
var int marg1; marg1 = PS_VMax/32;
View_AddText(AFIP_view, PS_VMax/2 - width / 2, head, heading, AFIP_font);
var string txt;
// Lines - left side (text)
repeat(i, 6); var int i;
txt = MEM_ReadStringArray(_@s(itm.text), i);
View_AddText(AFIP_view, PS_VMax/15 - head, head + marg + (i+2)*height, txt, AFIP_font);
end;
// Lines - right side (values)
repeat(j, 6); var int j;
txt = countValue(MEM_ReadIntArray(_@(itm.count), j));
View_AddText(AFIP_view, PS_VMax - marg1 - 2*Print_ToVirtual(Print_GetStringWidth(txt, AFIP_font), PS_X), head + marg + (j+2)*height,txt , AFIP_font);
end;
// Resizing and moving
View_Resize(AFIP_view, AFIP_width, AFIP_height);
View_Move (AFIP_view, AFIP_x, PS_VMax/2);
if (!AFIP_rndObj) // not really needed, but just to be sure
{
AFIP_rndObj = Render_AddItemCenterPrio(itemIn , PS_VMax/2 + 1500, PS_VMax/2 + AFIP_height/2, AFIP_height - 400, Print_ToRatio(AFIP_height - 400, PS_Y), 1);
};
Render_AddView(AFIP_view);
};
// Taken from Ikarus_doc.d
func void SetVarTo (var string variableName, var int value) {
var int symPtr;
symPtr = MEM_GetParserSymbol (variableName);
if (symPtr) { //!= 0
var zCPar_Symbol sym;
sym = MEM_PtrToInst (symPtr);
if ((sym.bitfield & zCPar_Symbol_bitfield_type)
== zPAR_TYPE_INT) {
sym.content = value;
} else {
MEM_Error ("SetVarTo: Die Variable ist kein Integer!");
};
} else {
MEM_Error ("SetVarTo: Das Symbol existiert nicht!");
};
};
//========================================
// Sets the item, for Info_AddChoice type
// dialogues
//========================================
func void AFIP_SetChoiceItem(var func action, var int item )
{
var int fncID; fncID = MEM_GetFuncID(action);
var zCPar_Symbol symfunc; symfunc = _^(MEM_ReadIntArray (contentSymbolTableAddress, fncID));
var string infoFuncName; infoFuncName = symfunc.name;
// building the symbol name
var string symbName; symbName = ConcatStrings(infoFuncName, ".");
symbName = ConcatStrings(symbName, AFIP_varName);
SetVarTo(symbName, item);
};
//========================================
// Unused because it doesn't work, but it
// would be cleaner (I think :) )
//========================================
func void AFIP_Info_AddChoice(var int menu, var string text, var func action, var int item )
{
// this doesn't work for this particular technique - func action has a different symbol -> AFIP_Info_AddChoice.action
MEM_Info("== AFIP_Info_AddChoice ==");
/*
var int fncID; fncID = MEM_GetFuncID(action);
var zCPar_Symbol asd; asd = _^(MEM_ReadIntArray (contentSymbolTableAddress, fncID));
PVs("action.name", asd.name);
var zCPar_Symbol menuSymb; menuSymb = _^(MEM_ReadIntArray (contentSymbolTableAddress, menu));
Info_AddChoice(menu, text, action);
// zCViewDialogChoice__AddChoice( text, fncID);
var zCPar_Symbol symfunc; symfunc = _^(MEM_ReadIntArray (contentSymbolTableAddress, fncID));
var string infoFuncName; infoFuncName = symfunc.name;
var string symbName; symbName = ConcatStrings(infoFuncName, ".");
symbName = ConcatStrings(symbName, AFIP_varName);
PVs("symbName", symbName);
SetVarTo(symbName, item);
*/
};
//========================================
// function by Fawkes
// returns oCInfo * to the info that is
// currently highlighted in a dialogue
//========================================
func int InfoManager_GetSelectedInfo ()
{
if (InfoManager_HasFinished ()) { return 0; };
var int choiceView; choiceView = MEM_InformationMan.DlgChoice;
if (!choiceView) { return 0; };
var zCViewDialogChoice dlg; dlg = _^ (choiceView);
const int cINFO_MGR_MODE_IMPORTANT = 0;
const int cINFO_MGR_MODE_INFO = 1;
const int cINFO_MGR_MODE_CHOICE = 2;
const int cINFO_MGR_MODE_TRADE = 3;
var zCArray arr; arr = _^ (choiceView + 172);
if (arr.array)
{
if (MEM_InformationMan.Mode == cINFO_MGR_MODE_INFO)
{
var C_NPC slf; slf = _^ (MEM_InformationMan.npc);
var C_NPC her; her = _^ (MEM_InformationMan.player);
return oCInfoManager_GetInfoUnimportant (slf, her, dlg.ChoiceSelected);
} else
//Choices - have to be extracted from oCInfo.listChoices_next
//MEM_InformationMan.Info is oCInfo pointer
if (MEM_InformationMan.Mode == cINFO_MGR_MODE_CHOICE)
{
return MEM_InformationMan.Info;
};
};
return 0;
};
//========================================
// function by Fawkes
// returns oCInfoChoice * to the info that is
// currently highlighted in a dialogue
//========================================
func int InfoManager_GetSelectedInfoChoice () {
if (InfoManager_HasFinished ()) { return 0; };
var int choiceView; choiceView = MEM_InformationMan.DlgChoice;
if (!choiceView) { return 0; };
var zCViewDialogChoice dlg; dlg = _^ (choiceView);
const int cINFO_MGR_MODE_IMPORTANT = 0;
const int cINFO_MGR_MODE_INFO = 1;
const int cINFO_MGR_MODE_CHOICE = 2;
const int cINFO_MGR_MODE_TRADE = 3;
var zCArray arr; arr = _^ (choiceView + 172);
if (arr.array) {
//Choices - have to be extracted from oCInfo.listChoices_next
//MEM_InformationMan.Info is oCInfo pointer
if (MEM_InformationMan.Mode == cINFO_MGR_MODE_CHOICE) {
if (MEM_InformationMan.Info) {
var oCInfo dlgInstance;
dlgInstance = _^ (MEM_InformationMan.Info);
if (dlgInstance.listChoices_next) {
var oCInfoChoice dlgChoice;
var int list; list = dlgInstance.listChoices_next;
var zCList l;
var int i; i = 0;
while (list);
l = _^ (list);
if (l.data) {
if (i == dlg.ChoiceSelected) {
return l.data;
};
};
list = l.next;
i += 1;
end;
};
};
};
};
return 0;
};
//========================================
// returns item (its symbol table ID), that
// is set to be shown in selected dialogue's
// condition function or in case of oCInfoChoice
// in its "action" function
//========================================
func int AFIP_HasItem()
{
// get selected oCInfo
var int infoPtr; infoPtr = InfoManager_GetSelectedInfo();
// get selected oCInfoChoice
var int infoChoicePtr; infoChoicePtr = InfoManager_GetSelectedInfoChoice();
if (infoPtr && !infoChoicePtr)
{
// MEM_Info("Info");
var oCInfo diaInfo; diaInfo = _^(infoPtr);
// getting the name of dialogues condition function
/*
Ideally this would in the info function (it would work even for Info_AddChoice dialogues) but in that
case the var is only initialized by Ikarus (to be 0), since the function didn't run - no value assigned
Should I be saving oCInfo* and oCInfoChoice* in a hash table?? Similarly like I do in AF_EnhancedDamage
*/
var zCPar_Symbol symfunc; symfunc = _^(MEM_ReadIntArray (contentSymbolTableAddress, /*diaInfo.information */ diaInfo.conditions));
var string infoFuncName; infoFuncName = symfunc.name;
// building the variables symbol name
var string symbName; symbName = ConcatStrings(infoFuncName, ".");
symbName = ConcatStrings(symbName, AFIP_varName);
// getting and checking if symbol with this name exists (if user defined item to be shown)
var int symPtr; symPtr = MEM_GetParserSymbol (symbName);
if (symPtr == 0)
{
return 0;
}
else
{
var zCPar_Symbol sym;
sym = _^(symPtr);
return sym.content;
};
}
else if ((infoPtr && infoChoicePtr) || (!infoPtr && infoChoicePtr))
{
/* class oCInfoChoice {
var string Text; //zSTRING
var int Function; //int //symbolindex
};*/
var oCInfoChoice diaInfoChoice; diaInfoChoice = _^(infoChoicePtr);
var zCPar_Symbol symChoiceFunc; symChoiceFunc = _^(MEM_ReadIntArray (contentSymbolTableAddress, diaInfoChoice.Function));
var string infoChoiceFuncName; infoChoiceFuncName = symChoiceFunc.name;
// building the variables symbol name
var string symbChoiceName; symbChoiceName = ConcatStrings(infoChoiceFuncName, ".");
symbChoiceName = ConcatStrings(symbChoiceName, AFIP_varName);
// getting and checking if symbol with this name exists (if user defined item to be shown)
var int symbPtr; symbPtr = MEM_GetParserSymbol (symbChoiceName);
if (symbPtr == 0)
{
return 0;
}
else
{
var zCPar_Symbol symb;
symb = _^(symbPtr);
return symb.content;
};
}
else
{
return 0;
};
};
//========================================
// HOOK: runs the frame function at the
// beginning of the dialogue
//========================================
func void _hook_oCInformationManager__OnInfoBegin()
{
FF_ApplyOnce(FF_AF_ItemPreview);
};
//========================================
// HOOK: removes the frame function at the
// end of the dialogue
//========================================
func void _hook_oCInformationManager__OnTermination()
{
FF_Remove(FF_AF_ItemPreview);
};
//========================================
// FF: checks if there is item to be
// shown and shows it
//========================================
func void FF_AF_ItemPreview()
{
// show only when the dialogue selection is active
if (MEM_InformationMan.IsWaitingForSelection)
{
// get the item, if there is any
var int itm; itm = AFIP_HasItem();
if (itm == 0 ) // if there is no item, delete view (if there's one) and end
{
AFIP_currentItem = 0;
AFIP_Delete();
return;
}
if (AFIP_currentItem != itm) // if the item changed delete the old view and create new one
{
AFIP_Delete();
AFIP_currentItem = 0;
AFIP_Create(itm);
return;
}
else // there is item to show
{
if (!AFIP_view) // == 0 // if the view doesn't already exists
{
AFIP_Create(itm);
};
};
}
else // if dialogue selection is inactive delete the view (checks are in the AFIP_Delete function)
{
AFIP_currentItem = 0;
AFIP_Delete();
return;
};
};
//========================================
// Init function
//========================================
func void AF_ItemPreview_Init()
{
const int once = 0;
if (!once) {
// 0072E430 .text Debug data ?OnTermination@oCInformationManager@@IAIXXZ
// 0x006631A0 protected: void __fastcall oCInformationManager::OnTermination(void)
HookEngine(MEMINT_SwitchG1G2(7529520, 6697376), MEMINT_SwitchG1G2(6, 7), "_hook_oCInformationManager__OnTermination");
// 0072D2E0 .text Debug data ?OnInfoBegin@oCInformationManager@@IAIXXZ
// 0x00661FF0 protected: void __fastcall oCInformationManager::OnInfoBegin(void)
HookEngine(MEMINT_SwitchG1G2(7525088, 6692848), 6, "_hook_oCInformationManager__OnInfoBegin");
once = 1;
};
};
/*
Nice try, but doesn't work :(
Maybe I have a bug somwhere down here?
// 0x00706E40 protected: virtual void __thiscall oCItemContainer::DrawItemInfo(class oCItem *,class zCWorld *)
func int oCItemContainer__DrawItemInfo(var int this, var int itemPtr) {
const int oCItemContainer__DrawItemInfo = 7368256;
const int call = 0;
if(CALL_Begin(call)) {
CALL_IntParam(MEM_GetIntAddress(_render_wld));
CALL_IntParam(MEM_GetIntAddress(itemPtr));
CALL__thiscall(MEM_GetIntAddress(this), oCItemContainer__DrawItemInfo);
call = CALL_End();
};
};
func void AFIP_Show(var c_npc npcInst, var c_item itm)
{
var int itemPtr; itemPtr = Itm_GetPtr(itm);
var oCNpc npc; npc = Hlp_GetNpc(npcInst);
oCItemContainer__DrawItemInfo(npc.inventory2_vtbl, itemPtr);
};
*/
And here is, how you use the feature in Daedalus oCInfo type dialogue:
Spoiler:(zum lesen bitte Text markieren)
Code:
instance DIA_Xardas_Reward (C_INFO)
{
npc = NONE_100_Xardas;
nr = 1; //3;
condition = DIA_Xardas_Reward_Condition;
information = DIA_Xardas_Reward_Info;
permanent = TRUE;
description = "I'll take the dagger.";
};
func int DIA_Xardas_Reward_Condition () {
// This is how you define a item to be shown when this dialogue is highlighted
var int AFIP_ShowItem; AFIP_ShowItem = ItMw_1h_Vlk_Dagger;
// if ( /* condition */ ) {
return TRUE;
// };
};
func void DIA_Xardas_Reward_Info () {
CreateInvItem (other, ItMw_1h_Vlk_Dagger);
AI_Output (other, self, "DIA_Xardas_Reward_14_00"); //I'll take dagger.
AI_Output (self, other, "DIA_Xardas_Reward_15_01"); //Here it is.
AI_StopProcessInfos (self);
};
oCInfoChoice type dialogue:
Spoiler:(zum lesen bitte Text markieren)
Code:
instance DIA_Torrez_Belohnung(C_Info)
{
npc = NONE_100_Xardas;
nr = 5;
condition = DIA_Torrez_Belohnung_Condition;
information = DIA_Torrez_Belohnung_Info;
permanent = 0;
description = "I am here to choose my reward.";
};
func int DIA_Torrez_Belohnung_Condition()
{
return TRUE;
};
func void DIA_Torrez_Belohnung_Info()
{
AI_Output(other,self,"DIA_Torrez_Belohnung_15_00"); //I am here to choose my reward.
AI_Output(self,other,"DIA_Torrez_Belohnung_04_01"); //Choose wisely.
Info_ClearChoices(DIA_Torrez_Belohnung);
Info_AddChoice(DIA_Torrez_Belohnung, "Elixir of the spirit", DIA_Torrez_Belohnung_ManaMax );
// Assigns item to a choice
AFIP_SetChoiceItem(DIA_Torrez_Belohnung_ManaMax, ItPo_Perm_Mana);
Info_AddChoice(DIA_Torrez_Belohnung, "Elixir of life", DIA_Torrez_Belohnung_Scrolls );
// Assigns item to a choice
AFIP_SetChoiceItem(DIA_Torrez_Belohnung_Scrolls, ItPo_Perm_Health);
Info_AddChoice(DIA_Torrez_Belohnung, "Ring of dexterity", DIA_Torrez_Belohnung_Dex );
// Assigns item to a choice
AFIP_SetChoiceItem(DIA_Torrez_Belohnung_Dex, ItRi_Dex_02);
Info_AddChoice(DIA_Torrez_Belohnung, "Ring of strength", DIA_Torrez_Belohnung_Str );
// Assigns item to a choice
AFIP_SetChoiceItem(DIA_Torrez_Belohnung_Str, ItRi_Str_02);
};
func void DIA_Torrez_Belohnung_Str()
{
var int AFIP_ShowItem; // You have to define a variable, that is going to contain the item
AI_Output(other,self,"DIA_Torrez_Belohnung_Str_15_00"); //I'll take the ring of strength.
AI_Output(self,other,"DIA_Torrez_Belohnung_Str_04_01"); //Experienced choice. Here it is.
// A nice coincidence - we don't have to manually select which item to insert, since we have it in AFIP_ShowItem variable
CreateInvItem (other, AFIP_ShowItem);
Info_ClearChoices(DIA_Torrez_Belohnung);
};
func void DIA_Torrez_Belohnung_Dex()
{
var int AFIP_ShowItem; // You have to define a variable, that is going to contain the item
AI_Output(other,self,"DIA_Torrez_Belohnung_Dex_15_00"); //I would like this ring of dexterity.
AI_Output(self,other,"DIA_Torrez_Belohnung_Dex_04_01"); //Dexterity wins against strength. Good choice.
// A nice coincidence - we don't have to manually select which item to insert, since we have it in AFIP_ShowItem variable
CreateInvItem (other, AFIP_ShowItem);
Info_ClearChoices(DIA_Torrez_Belohnung);
};
func void DIA_Torrez_Belohnung_Scrolls()
{
var int AFIP_ShowItem; // You have to define a variable, that is going to contain the item
AI_Output(other,self,"DIA_Torrez_Belohnung_Scrolls_15_00"); //Give me the elixir of life .
AI_Output(self,other,"DIA_Torrez_Belohnung_Scrolls_04_01"); //Use it well.
// A nice coincidence - we don't have to manually select which item to insert, since we have it in AFIP_ShowItem variable
CreateInvItem (other, AFIP_ShowItem);
Info_ClearChoices(DIA_Torrez_Belohnung);
};
func void DIA_Torrez_Belohnung_ManaMax()
{
var int AFIP_ShowItem; // You have to define a variable, that is going to contain the item
AI_Output(other,self,"DIA_Torrez_Belohnung_ManaMax_15_00"); //I would like to have the elixir of spirit!
AI_Output(self,other,"DIA_Torrez_Belohnung_ManaMax_04_01"); //A wise choice! You have picked the most precious of all gifts. Drink the potion and your power will grow!
// A nice coincidence - we don't have to manually select which item to insert, since we have it in AFIP_ShowItem variable
CreateInvItem (other, AFIP_ShowItem);
Info_ClearChoices(DIA_Torrez_Belohnung);
};
For it to run you need Ikarus and LeGo, as well as class definitions from AFSP (github link).
Please let me know what you think. I am a begginer at this How can I improve my code, is this a good way to do this? Is the hash table way better/safer?
Hello guys ,
I made another feature. I always disliked, how, when you are selecting a reward in a dialogue, you cannot see what the item is. Modders then resort to strings such as Digger's Trousers. Protection: 15, 5, 5. So I made a feature that allows you to preview the item (like you can see it in your inventory) you are going to get when you choose that dialogue option.
....
I am looking forward for your feedback!
Auronen
It doesn't quite work right with Gothic 2.
OnInfoBegin isn't always called.
Talking to Npc that says something at the start (e.g. Harad when you're his apprentice)
-> OnInfoBegin is not called
Talking to Orlan
-> OnInfoBegin is called.
To fix the issue on my side, i added one additional hook for oCInformationManager::SetNpc at the end of the method, after the npc check and other things, just before the (inlined!) call to ::OnImportantBegin
Code:
//========================================
// Init function
//========================================
func void AF_ItemPreview_Init()
{
const int once = 0;
if (!once) {
// ...
HookEngine(MEMINT_SwitchG1G2(7525088, 6692848), 6, "_hook_oCInformationManager__OnInfoBegin");
if (GOTHIC_BASE_VERSION == 2) {
// in G2 OnInfoBegin is sometimes not called.
// oCInformationManager::SetNpc @ 00660b74, size 6 <- always called at the beginning of a dialog
HookEngine(6687604, 6, "_hook_oCInformationManager__OnInfoBegin");
};
once = 1;
};
};
EDIT:
@Kirides is, of course, right. It has to be hooked reliably.
@neocromicon
That is very intereting, I just tested your code, and it runs, but I noticed something in the crash report. It looks like you are running the compilation with -zreparse, are you using zParserExtender Union plugin? I use that one too, but the proper parameter is -zReparse_GAME (for only the game parser to be parsed and compiled).
Do you have any other plugins installed?
EDIT:
@Kirides is, of course, right. It has to be hooked reliably.
@neocromicon
That is very intereting, I just tested your code, and it runs, but I noticed something in the crash report. It looks like you are running the compilation with -zreparse, are you using zParserExtender Union plugin? I use that one too, but the proper parameter is -zReparse_GAME (for only the game parser to be parsed and compiled).
Do you have any other plugins installed?
I Use Union 1.0k in my Mod Installation. But not any plugin or patch ect. The only think what i have found is Union.patch in System.
Hello Neocromicon,
Please make sure that you are using all required LeGo flags in LeGo_Init function (without LeGo_Render it crashed for me as well). I think these are required:
Hello Neocromicon,
Please make sure that you are using all required LeGo flags in LeGo_Init function (without LeGo_Render it crashed for me as well). I think these are required:
Hello folks,
Quite some time ago, I presented a feature that would allow modders to easily change font and color of a dialogue description (and, later on, to use dialogue descriptions as input fields and spinners) in this thread: https://forum.worldofplayers.de/foru...d-color-change
But trust me this is not reposting - meanwhile we have added these scripts to GitHub repository and created new features. All of the features work with both dialogue descriptions (C_Info.description) as well as with dialogue choices (Info_AddChoice):
Using overlays, we can change the formatting for only a certain part of a text.
Add the "o@ customFormat : yourText ~" modifier to your dialogue description (customFormat = modifiers for color or text alignment):
Code:
instance Info_Diego_Brief (C_INFO) {
npc = PC_Thief;
nr = 10;
condition = Info_Diego_Brief_Condition;
information = Info_Diego_Brief_Info;
permanent = 0;
description = "I have a o@h@00CC66 hs@66FFB2:letter~ for the o@h@00CCCC hs@66FFFF:High Magician~ of the Circle of o@h@FF8000 hs@FFFF7F:Fire~.";
};
How it works:
zCViewDialogChoice.m_listLines_array is an array containing all dialogue choices visible in the dialog choice box.
Number of dialogue choices is stored in zCViewDialogChoice.Choices.
I tried to extend the array zCViewDialogChoice.m_listLines_array and engine drew new dialogue choice on screen ... while cursor ignored my new dialogue choice - because it is most likely looking only at zCViewDialogChoice.Choices!
Code that handles overlays, splits dialogue description and inserts each 'overlay' into an array zCViewDialogChoice.m_listLines_array with the correct X/Y coordinates and required color.
Spinners allow you to use the left/right arrow keys to decrease/increase numerical value of the global variable InfoManagerSpinnerValue. There is a lot you can do with such a feature, e. g. improve cooking - select how much meat you want to cook without going through dialogue choices. In order to add a spinner to your dialogue, you have to add the "s@spinnerID " modifier to a dialogue description/choice, where spinnerID has to be a unique ID not containing any spaces. Working with spinners is more complex; the example below shows how you can use the _condition function to work with spinners (you can use this one as a template):
Code:
instance DIA_EIM_Spinner_01 (C_INFO) {
nr = 1;
npc = PC_Hero;
condition = DIA_EIM_Spinner_01_Condition;
information = DIA_EIM_Spinner_01_Info;
important = 0;
permanent = 1;
description = "dummy";
};
func int DIA_EIM_Spinner_01_Condition() {
//if (PLAYER_MOBSI_PRODUCTION == MOBSI_DIALOG_PAN) {
//These are in fact Global variables - we can exploit that for this feature - they will retain their value ;-)
var string lastSpinnerID;
var int value;
var int min;
var int max;
//Min/max values
min = 1;
max = NPC_HasItems (self, ItFoMuttonRaw);
//Check boundaries
if (value < min) { value = min; };
if (value > max) { value = max; };
var int isActive; isActive = Hlp_StrCmp (InfoManagerSpinnerID, "CookMeat");
//Setup spinner if spinner ID has changed
if (isActive) {
//What is current InfoManagerSpinnerID ?
if (!Hlp_StrCmp (InfoManagerSpinnerID, lastSpinnerID)) {
//Update value
InfoManagerSpinnerValue = value;
};
//Page Up/Down quantity
InfoManagerSpinnerPageSize = 5;
//Min/max value (Home/End keys)
InfoManagerSpinnerValueMin = min;
InfoManagerSpinnerValueMax = max;
//Update
value = InfoManagerSpinnerValue;
};
lastSpinnerID = InfoManagerSpinnerID;
var string newDescription; newDescription = "";
//if (max == 0) {
// newDescription = "d@ "; //disabled
//};
//Spinner ID CookMeat
newDescription = ConcatStrings (newDescription, "s@CookMeat Cook some meat: "); //Cook some meat
//Manually typed in number:
if (InfoManagerSpinnerNumberEditMode)
&& (TRUE) //change to FALSE if you don't want to allow manual typing
&& (isActive)
{
var string editedNumber;
editedNumber = InfoManagerSpinnerNumber;
editedNumber = ConcatStrings (editedNumber, "_");
//Check boundaries - if value is out, add red color overlay
if ((STR_ToInt (InfoManagerSpinnerNumber) < min) || (STR_ToInt (InfoManagerSpinnerNumber) > max)) {
editedNumber = ConcatStrings ("o@h@FF3030 hs@FF4646 :", editedNumber);
editedNumber = ConcatStrings (editedNumber, "~");
};
newDescription = ConcatStrings (newDescription, editedNumber);
} else {
newDescription = ConcatStrings (newDescription, IntToString (value));
};
newDescription = ConcatStrings (newDescription, " / ");
newDescription = ConcatStrings (newDescription, IntToString (max));
//Update description
DIA_EIM_Spinner_01.description = newDescription;
return TRUE;
//};
return FALSE;
};
func void DIA_EIM_Spinner_01_Info() {
};
You can also type a number in manually using numerical keyboard - if the number typed in is out of bounds, then the code above will use a red overlay to indicate a problem:
instance DIA_EIM_Spinner_02 (C_INFO) {
nr = 1;
npc = PC_Hero;
condition = DIA_EIM_Spinner_02_Condition;
information = DIA_EIM_Spinner_02_Info;
important = 0;
permanent = 1;
description = "dummy";
};
func int DIA_EIM_Spinner_02_Condition() {
//These are in fact Global variables - we can exploit that for this feature - they will retain their value ;-)
var string lastSpinnerID;
var int value;
var int min;
var int max;
//Min/max values
min = 1;
max = 3;
//Check boundaries
if (value < min) { value = min; };
if (value > max) { value = max; };
var int isActive; isActive = Hlp_StrCmp (InfoManagerSpinnerID, "ColorPicker");
//Setup spinner if spinner ID has changed
if (isActive) {
//What is current InfoManagerSpinnerID ?
if (!Hlp_StrCmp (InfoManagerSpinnerID, lastSpinnerID)) {
//Update value
InfoManagerSpinnerValue = value;
};
//Page Up/Down quantity
InfoManagerSpinnerPageSize = 5;
//Min/max value (Home/End keys)
InfoManagerSpinnerValueMin = min;
InfoManagerSpinnerValueMax = max;
//Update
value = InfoManagerSpinnerValue;
};
lastSpinnerID = InfoManagerSpinnerID;
var string newDescription; newDescription = "";
//Spinner ID ColorPicker
newDescription = ConcatStrings (newDescription, "s@ColorPicker ");
if (value == 1) {
newDescription = ConcatStrings (newDescription, "This is o@h@FF3030 hs@FF4646 :red~ color.");
} else
if (value == 2) {
newDescription = ConcatStrings (newDescription, "This is o@h@00CC66 hs@66FFB2 :green~ color.");
} else
if (value == 3) {
newDescription = ConcatStrings (newDescription, "This is o@h@6699FF hs@99CCFF :blue~ color.");
};
//Update description
DIA_EIM_Spinner_02.description = newDescription;
return TRUE;
};
func void DIA_EIM_Spinner_02_Info() {
};
We want to have all of our features available for both C_INFO dialogues and dialogue choices. Spinner functionality is also available for dialogue choices - however, it requires different handling. While with C_INFO dialogues we can easily work with the C_INFO.description property, with choices we have to use the custom function InfoManager_SetInfoChoiceText_BySpinnerID to update the choice text:
instance DIA_EIM_Spinner_Choices_01(C_Info) {
nr = 2;
npc = PC_Hero;
condition = DIA_EIM_Spinner_Choices_01_Condition;
information = DIA_EIM_Spinner_Choices_01_Info;
important = 0;
permanent = 1;
description = "Let's cook with choices ...";
};
func int DIA_EIM_Spinner_Choices_01_Condition() {
//if (PLAYER_MOBSI_PRODUCTION == MOBSI_DIALOG_PAN) {
//These are in fact Global variables - we can exploit that for this feature - they will retain their value ;-)
var string lastSpinnerID;
var int min;
var int max;
var int isActive;
var string newDescription;
var string editedNumber;
//-- Spinner Choice #1
var int value1; //Spinner #1 value
//Min/max values
min = 1;
max = NPC_HasItems (self, ItFoMuttonRaw);
//Check boundaries
if (value1 < min) { value1 = min; };
if (value1 > max) { value1 = max; };
isActive = Hlp_StrCmp (InfoManagerSpinnerID, "CookMeat");
//Setup spinner if spinner ID has changed
if (isActive) {
//What is current InfoManagerSpinnerID ?
if (!Hlp_StrCmp (InfoManagerSpinnerID, lastSpinnerID)) {
//Update value
InfoManagerSpinnerValue = value1;
};
//Page Up/Down quantity
InfoManagerSpinnerPageSize = 5;
//Min/max value (Home/End keys)
InfoManagerSpinnerValueMin = min;
InfoManagerSpinnerValueMax = max;
//Update
value1 = InfoManagerSpinnerValue;
};
newDescription = "";
if (max == 0) {
//newDescription = "d@ "; //disabled
};
//Spinner ID CookMeat
newDescription = ConcatStrings (newDescription, "s@CookMeat Cook some Meat: "); //Cook some meat
//Manually typed in number:
if (InfoManagerSpinnerNumberEditMode)
&& (TRUE) //change to FALSE if you don't want to allow manual typing
&& (isActive)
{
editedNumber = InfoManagerSpinnerNumber;
editedNumber = ConcatStrings (editedNumber, "_");
//Check boundaries - if value is out, add red color overlay
if ((STR_ToInt (InfoManagerSpinnerNumber) < min) || (STR_ToInt (InfoManagerSpinnerNumber) > max)) {
editedNumber = ConcatStrings ("o@h@FF3030 hs@FF4646 :", editedNumber);
editedNumber = ConcatStrings (editedNumber, "~");
};
newDescription = ConcatStrings (newDescription, editedNumber);
} else {
newDescription = ConcatStrings (newDescription, IntToString (value1));
};
newDescription = ConcatStrings (newDescription, " / ");
newDescription = ConcatStrings (newDescription, IntToString (max));
//Update choice description!
InfoManager_SetInfoChoiceText_BySpinnerID (newDescription, "CookMeat");
//-- Spinner Choice #2
var int value2; //Spinner #2 value
//Min/max values
min = 1;
max = NPC_HasItems (self, ItFoMutton);
//Check boundaries
if (value2 < min) { value2 = min; };
if (value2 > max) { value2 = max; };
isActive = Hlp_StrCmp (InfoManagerSpinnerID, "CookFish");
//Setup spinner if spinner ID has changed
if (isActive) {
//What is current InfoManagerSpinnerID ?
if (!Hlp_StrCmp (InfoManagerSpinnerID, lastSpinnerID)) {
//Update value
InfoManagerSpinnerValue = value2;
};
//Page Up/Down quantity
InfoManagerSpinnerPageSize = 5;
//Min/max value (Home/End keys)
InfoManagerSpinnerValueMin = min;
InfoManagerSpinnerValueMax = max;
//Update
value2 = InfoManagerSpinnerValue;
};
newDescription = "";
if (max == 0) {
//newDescription = "d@ "; //disabled
};
//Spinner ID CookMeat
newDescription = ConcatStrings (newDescription, "s@CookFish Cook some Fish: ");
//Manually typed in number:
if (InfoManagerSpinnerNumberEditMode)
&& (TRUE) //change to FALSE if you don't want to allow manual typing
&& (isActive)
{
editedNumber = InfoManagerSpinnerNumber;
editedNumber = ConcatStrings (editedNumber, "_");
//Check boundaries - if value is out, add red color overlay
if ((STR_ToInt (InfoManagerSpinnerNumber) < min) || (STR_ToInt (InfoManagerSpinnerNumber) > max)) {
editedNumber = ConcatStrings ("o@h@FF3030 hs@FF4646 :", editedNumber);
editedNumber = ConcatStrings (editedNumber, "~");
};
newDescription = ConcatStrings (newDescription, editedNumber);
} else {
newDescription = ConcatStrings (newDescription, IntToString (value2));
};
newDescription = ConcatStrings (newDescription, " / ");
newDescription = ConcatStrings (newDescription, IntToString (max));
//Update choice description!
InfoManager_SetInfoChoiceText_BySpinnerID (newDescription, "CookFish");
//--
lastSpinnerID = InfoManagerSpinnerID;
return TRUE;
//};
return FALSE;
};
func void DIA_EIM_Spinner_Choices_01_Info() {
Info_ClearChoices(DIA_EIM_Spinner_Choices_01);
Info_AddChoice(DIA_EIM_Spinner_Choices_01, DIALOG_BACK, DIA_EIM_Spinner_Choices_01_Back);
Info_AddChoice(DIA_EIM_Spinner_Choices_01, "s@CookFish Cook some Fish: ", DIA_EIM_Spinner_Choices_01_CookFish);
Info_AddChoice(DIA_EIM_Spinner_Choices_01, "s@CookMeat Cook some Meat: ", DIA_EIM_Spinner_Choices_01_CookMeat);
};
func void DIA_EIM_Spinner_Choices_01_Back () {
Info_ClearChoices(DIA_EIM_Spinner_Choices_01);
};
func void DIA_EIM_Spinner_Choices_01_CookMeat () {
PrintS (IntToString (InfoManagerSpinnerValue));
DIA_EIM_Spinner_Choices_01_Info ();
};
func void DIA_EIM_Spinner_Choices_01_CookFish () {
PrintS (IntToString (InfoManagerSpinnerValue));
DIA_EIM_Spinner_Choices_01_Info ();
};
Options are limitless - as everything is handled from the condition function, you can get more creative with what you actually do ...
EIM 'passive' features
There are mainly 3 things that EIM does automatically:
1. it constantly evaluates _condition functions for available dialogues (you might have figured that out from the spinner examples - in which spinner dialogue condition updates description text)
2. it takes care of horizontal text scrolling; if given dialogue text is too long, EIM will scroll through it in a loop
3. it overrides controls for Key_Tab - this no longer confirms dialogue choice; instead it is now possible to confirm choices with Key_Space.
EIM customization
There are several options for customization; you can change the default text alignment or text color for all dialogues (in enhancedInfoManager.d):
Code:
//Default dialog colors
const string InfoManagerDefaultDialogColorSelected = "FFFFFF"; //G1 standard dialog - white color FFFFFF
const string InfoManagerDefaultColorDialogGrey = "C8C8C8"; //G1 standard dialog - grey color C8C8C8
const string InfoManagerDefaultFontDialogSelected = ""; //Default font for selected dialog choice (if blank default Gothic version will be used)
const string InfoManagerDefaultFontDialogGrey = ""; //Default font for greyed (if blank default Gothic version will be used)
const string InfoManagerDisabledDialogColorSelected = "808080"; //Disabled color - selected
const string InfoManagerDisabledColorDialogGrey = "808080"; //Disabled color - grey
//Default text alignment
const int InfoManagerDefaultDialogAlignment = ALIGN_LEFT; //ALIGN_CENTER, ALIGN_LEFT, ALIGN_RIGHT defined in LeGo