Ich schaffte es, Quickbar an Gothic 1 zu arbeiten, aber nach dem Neustart des Spiels werden die ausgewählten Gegenstände nicht gespeichert. Worin kann es einen Grund geben?
Spoiler:(zum lesen bitte Text markieren)
Code:
var int QS_Data[10]; //CQuickSlot*var int QS_BackGround; //zCView*
var int QS_VRender; //
const int QS_SlotBackX = 512;
const int QS_SlotBackY = 128;
const int QS_SlotBackMargin = 45;
const string QS_Texture = "QUICKSLOTS.tga";
// ----;
const int QS_Category_Weapon = 1;
const int QS_Category_Item = 2;
const int QS_Category_Shield = 3;
const int QS_Category_Ranged = 4;
const int QS_Category_Torch = 5;
const int QS_Category_Magic = 6;
var int QS_InvOpen; //BOOL
class CQuickSlot
{
var int ItemID; //Hlp_GetItem->oCItem*
var int pRender; //zCRender*
var int Category; //int
};
instance QuickSlot(CQuickSlot);
//EquipWeapon -> By Sektenspinner
func void Equip_FarWeapon (var C_NPC slf, var int ItemInst) {
if (!Npc_HasItems (slf, ItemInst)) {
CreateInvItems (slf, ItemInst, 1);
};
if (!Npc_GetInvItem(slf, ItemInst)) {
MEM_AssertFail("Unexpected behaviour in EquipWeapon.");
return;
};
if ((item.mainflag == ITEM_KAT_NF) && (Npc_HasReadiedMeleeWeapon(slf)))
|| ((item.mainflag == ITEM_KAT_FF) && (Npc_HasReadiedRangedWeapon(slf))) {
MEM_Warn ("EquipWeapon: Caller wants to equip a weapon while weapon of the same type is readied. Ignoring request.");
return;
};
//if (item.flags & ITEM_ACTIVE)
//&& (!EquipWeapon_TogglesEquip) {
// /* calling EquipWeapon would unequip the weapon. */
// MEM_Info ("EquipWeapon: This weapon is already equipped. Ignoring request.");
// return;
//};
const int oCNpc__EquipFarWeapon = 6909696; // 0x0073A310
CALL_PtrParam(_@(item));
CALL__thiscall(_@(slf), oCNpc__EquipFarWeapon);
};
func oCItem Hlp_GetItem(var int ID)
{
var zCPar_Symbol symb; symb = _^ (MEM_GetSymbolByIndex (ID));
MEM_PtrToInst (symb.offset);
};
func void QS_MoveTo(var int hndl, var int x, var int y)
{
var zCView v; v = View_Get(hndl);
var int w; w = v.psizex;
var int h; h = v.psizey;
ViewPtr_MoveToPxl(getPtr(hndl), x-(w>>1), y-(h>>1));
};
func int QS_SlotIsEmpty(var int Slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(QS_Ptr)
{
return FALSE;
};
return TRUE;
};
func void QS_CreateSlot(var int slot, var int InstID)
{
var int QS_Ptr; QS_Ptr = new(QuickSlot);
var CQuickSlot QS; QS = get(QS_Ptr);
QS.ItemID = InstID;
if(!Hlp_IsValidHandle(QS_BackGround))
{
var int QS_X; QS_X = Print_Screen[PS_X]/2;
var int QS_Y; QS_Y = QS_SlotBackMargin;
// ---------------------------------------- ;
QS_BackGround = View_CreateCenterPxl(QS_X, QS_Y, QS_SlotBackX, QS_SlotBackY);
View_SetTexture(QS_BackGround, QS_Texture);
QS_VRender = Render_AddView(QS_BackGround);
};
if(!Hlp_IsValidHandle(QS_BackGround)){ return; };
var zCView v; v = View_Get(QS_BackGround);
var int hlp; hlp = slot; if(slot == 0){ hlp = 10; };
var int x; x = v.pposx + (160 + 50*(hlp - 4));
if(slot == 1)
{
x = v.pposx + 51;
}
else if(slot == 2)
{
x = v.pposx + 100;
};
var int rPtr; rPtr = Render_AddItemCenterPxl(InstID, x, v.pposy+64, 64, 52);
Render_OpenView(rPtr);
QS.pRender = rPtr;
MEM_WriteStatArr(QS_Data, slot, QS_Ptr);
rPtr = 0;
};
func void QS_RefreshRender(var int slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr || !Hlp_IsValidHandle(QS_BackGround))
{
return;
};
var CQuickSlot QS; QS = get(QS_Ptr);
if(Hlp_IsValidHandle(QS.pRender))
{
Render_Remove(QS.pRender);
Render_CloseView(QS.pRender);
QS.pRender = 0;
};
var zCView v; v = View_Get(QS_BackGround);
var int hlp; hlp = slot;
if(slot == 0){ hlp = 10; };
var int x; x = v.pposx + (160 + 50*(hlp - 4));
if(slot == 1)
{
x = v.pposx +51;
}
else if(slot == 2)
{
x = v.pposx + 100;
};
var int rPtr; rPtr = Render_AddItemCenterPxl(QS.ItemID, x, v.pposy+64, 64, 52);
Render_OpenView(rPtr);
QS.pRender = rPtr;
rPtr = 0;
};
func void QS_RemoveBackGround()
{
var int i; i = 0;
var int c; c = 0;
repeat(i, 10);
if(QS_SlotIsEmpty(i))
{
c += 1;
};
end;
if(c == 10)
{
if(Hlp_IsValidHandle(QS_BackGround))
{
Render_CloseView(QS_VRender);
View_Delete(QS_BackGround);
QS_BackGround = 0;
QS_VRender = 0;
};
};
};
func void QS_ClearSlot(var int slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr){ return; };
var CQuickSlot QS; QS = get(QS_Ptr);
if(Hlp_IsValidHandle(QS.pRender))
{
Render_Remove(QS.pRender);
};
QS.ItemID = 0;
QS.Category = 0;
QS.pRender = 0;
delete(QS_Ptr);
QS_Ptr = 0;
MEM_WriteStatArr(QS_Data, slot, QS_Ptr);
QS_RemoveBackGround();
};
func int QS_GetSlotItem(var int Slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr){ return 0; };
var CQuickSlot QS; QS = get(QS_Ptr);
return QS.ItemID;
};
func void QS_CheckItem(var c_item it, var int slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr){ return; };
var CQuickSlot QS; QS = get(QS_Ptr);
if (it.mainflag == ITEM_KAT_NF) { QS.Category = QS_Category_Weapon; };
if (STR_Len (it.scemeName)) { QS.Category = QS_Category_Item; };
if (it.flags & ITEM_SHIELD) { QS.Category = QS_Category_Shield; };
if (it.flags & (ITEM_BOW | ITEM_CROSSBOW)) { QS.Category = QS_Category_Ranged; };
if (it.flags & ITEM_TORCH) { QS.Category = QS_Category_Torch; };
if (it.mainflag == ITEM_KAT_RUNE) { QS.Category = QS_Category_Magic; };
};
func void QS_CompareSlots(var int InstID)
{
var int i; i = 0;
repeat(i, 10);
if(InstID == QS_GetSlotItem(i))
{
QS_ClearSlot(i);
};
end;
};
func void QS_Do(var c_item it, var int slot)
{
var int InstID; InstID = Hlp_GetInstanceID(it);
if(QS_SlotIsEmpty(slot))
{
QS_CompareSlots(InstID);
QS_CreateSlot(slot, InstID);
QS_CheckItem(it, slot);
}
else
{
var int Old; Old = QS_GetSlotItem(slot);
QS_ClearSlot(slot);
if(InstID != Old)
{
QS_CompareSlots(InstID);
QS_CreateSlot(slot, InstID);
QS_CheckItem(it, slot);
};
Old = 0;
};
};
func void QS_Magic(var int slot, var int key)
{
var oCNpc her; her = Hlp_GetNpc(hero);
var int spell; //oCSpell*
CALL_PtrParam(key);
CALL__thiscall(her.mag_book, 4664992); //oCSpell * __thiscall oCMag_Book:etSpellByKey(int) 0x00479C60
spell = CALL_RetValAsPtr();
if(!spell)
{
if(QS_GetSlotItem(slot))
{
var c_item i; i = Hlp_GetItem(QS_GetSlotItem(slot));
if(i.mainflag == ITEM_KAT_RUNE)
{
QS_ClearSlot(slot);
};
};
return;
};
var int ID; //oCItem*
CALL_PtrParam(spell);
CALL__thiscall(her.mag_book, 4664896); //oCItem * __thiscall oCMag_Book:etSpellItem(oCSpell *) 0x00479C00
ID = CALL_RetValAsPtr();
if(!ID){ return; };
var c_item it; it = _^(ID);
if(QS_GetSlotItem(slot) != Hlp_GetInstanceID(it))
{
QS_Do(it, slot);
};
};
func int DontInsertThis(var int ID)
{
var oCItem it; it = Hlp_GetItem(ID);
var c_item it2; it = Hlp_GetItem(ID);
if(Hlp_StrCmp(STR_Prefix(it._zCObject_objectName, 4), "itke"))
{
return true;
}
//else if(it.flags & (ITEM_BELT))
//{
// return true;
//}
else if(it.mainflag == ITEM_KAT_MAGIC
|| it.mainflag == ITEM_KAT_MUN
|| it.mainflag == ITEM_KAT_ARMOR
|| it.mainflag == ITEM_KAT_NONE)
{
return true;
};
return false;
};
func void QS_Hook()
{
var oCNpc her;
her = Hlp_GetNpc(hero);
//var c_item it;
//it = _^ (MEM_ReadInt (ESP+324+4));
var c_item it;
it = _^(MEM_ReadInt(ESP+120));
var int InstID;
InstID = Hlp_GetInstanceID(it);
if(!Npc_HasItems(hero, InstID)
|| !her.inventory2_oCItemContainer_frame
|| DontInsertThis(InstID))
{
return;
};
if(MEM_KeyState(KEY_4) == KEY_PRESSED){ QS_Do(it, 4); };
if(MEM_KeyState(KEY_5) == KEY_PRESSED){ QS_Do(it, 5); };
if(MEM_KeyState(KEY_6) == KEY_PRESSED){ QS_Do(it, 6); };
if(MEM_KeyState(KEY_7) == KEY_PRESSED){ QS_Do(it, 7); };
if(MEM_KeyState(KEY_8) == KEY_PRESSED){ QS_Do(it, 8); };
if(MEM_KeyState(KEY_9) == KEY_PRESSED){ QS_Do(it, 9); };
if(MEM_KeyState(KEY_0) == KEY_PRESSED){ QS_Do(it, 0); };
};
func void QS_UseItem(var int slot)
{
var oCNpc her; her = Hlp_GetNpc (hero);
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr){ return; };
var CQuickSlot QS; QS = get(QS_Ptr);
if(QS.ItemID && QS.Category && QS.Category != QS_Category_Magic)
{
if(C_BodyStateContains (hero, BS_STAND) && !QS_InvOpen)
{
if(QS.Category == QS_Category_Weapon && her.fmode == 0){ EquipWeapon(hero, QS.ItemID); };
if(QS.Category == QS_Category_Item )
{
AI_RemoveWeapon(hero);
AI_StandUpQuick(hero);
AI_UseItem(hero, QS.ItemID);
};
if(QS.Category == QS_Category_Shield && her.fmode == 0){ EquipWeapon(hero, QS.ItemID); };
if(QS.Category == QS_Category_Torch && her.fmode == 0){ oCNpc_Equip(_@(hero), _@(QS.ItemID)); };
if(QS.Category == QS_Category_Ranged && her.fmode == 0){ Equip_FarWeapon(hero, QS.ItemID); };
};
};
};
func void QS_Use()
{
if(QS_InvOpen)
{
return;
};
if(MEM_KeyState(KEY_4) == KEY_PRESSED){ QS_UseItem(4); };
if(MEM_KeyState(KEY_5) == KEY_PRESSED){ QS_UseItem(5); };
if(MEM_KeyState(KEY_6) == KEY_PRESSED){ QS_UseItem(6); };
if(MEM_KeyState(KEY_7) == KEY_PRESSED){ QS_UseItem(7); };
if(MEM_KeyState(KEY_8) == KEY_PRESSED){ QS_UseItem(8); };
if(MEM_KeyState(KEY_9) == KEY_PRESSED){ QS_UseItem(9); };
if(MEM_KeyState(KEY_0) == KEY_PRESSED){ QS_UseItem(0); };
};
func int InvIsOpen()
{
var oCNpc her; her = Hlp_GetNpc(hero);
return her.inventory2_oCItemContainer_frame;
};
func void OpenInv()
{
if(ECX != _@(hero))
{
return;
};
QS_InvOpen = true;
if(Hlp_IsValidHandle(QS_BackGround))
{
QS_MoveTo(QS_BackGround, Print_Screen[PS_X] / 2, QS_SlotBackMargin);
};
var int i; i = 0;
repeat(i, 10);
QS_RefreshRender(i);
end;
};
func void CloseInv()
{
QS_InvOpen = false;
if(Hlp_IsValidHandle(QS_BackGround))
{
QS_MoveTo(QS_BackGround, Print_Screen[PS_X] / 2, Print_Screen[PS_Y] - QS_SlotBackMargin);
};
var int i; i = 0;
repeat(i, 10);
QS_RefreshRender(i);
end;
};
func void QS_HasItems(var int slot)
{
if(!Npc_HasItems(hero, QS_GetSlotItem(slot)))
{
QS_ClearSlot(slot);
};
};
func void QS_PrintAmount(var int slot)
{
var int QS_Ptr; QS_Ptr = MEM_ReadStatArr(QS_Data, slot);
if(!QS_Ptr){ return; };
var CQuickSlot QS; QS = get(QS_Ptr);
var oCItem it; it = Hlp_GetItem(QS.itemID);
if(QS.category == QS_Category_Item || Hlp_StrCmp(it.name, NAME_Spruchrolle))
{
var int a; a = Npc_HasItems(hero, QS.itemID);
if(Hlp_IsValidHandle(QS_BackGround))
{
var RenderItem r; r = get(QS.pRender);
View_DeleteText(r.view);
if(a > 1)
{
View_AddText(r.view, 4096, 4096, IntToString(a), PF_Font);
};
};
};
};
func void QS_RenderWeapons()
{
var c_item it;
if(Npc_HasEquippedMeleeWeapon(hero))
{
it = Npc_GetEquippedMeleeWeapon(hero);
if(QS_GetSlotItem(1) != Hlp_GetInstanceID(it))
{
if(!QS_SlotIsEmpty(1))
{
QS_ClearSlot(1);
};
QS_CreateSlot(1, Hlp_GetInstanceID(it));
};
}
else if(Npc_HasReadiedMeleeWeapon(hero))
{
it = Npc_GetReadiedWeapon(hero);
if(QS_GetSlotItem(1) != Hlp_GetInstanceID(it))
{
if(!QS_SlotIsEmpty(1))
{
QS_ClearSlot(1);
};
QS_CreateSlot(1, Hlp_GetInstanceID(it));
};
}
else
{
if(!QS_SlotIsEmpty(1))
{
QS_ClearSlot(1);
};
};
if(Npc_HasEquippedRangedWeapon(hero))
{
it = Npc_GetEquippedRangedWeapon(hero);
if(QS_GetSlotItem(2) != Hlp_GetInstanceID(it))
{
if(!QS_SlotIsEmpty(2))
{
QS_ClearSlot(2);
};
QS_CreateSlot(2, Hlp_GetInstanceID(it));
};
}
else if(Npc_HasReadiedRangedWeapon(hero))
{
it = Npc_GetReadiedWeapon(hero);
if(QS_GetSlotItem(2) != Hlp_GetInstanceID(it))
{
if(!QS_SlotIsEmpty(2))
{
QS_ClearSlot(2);
};
QS_CreateSlot(2, Hlp_GetInstanceID(it));
};
}
else
{
if(!QS_SlotIsEmpty(2))
{
QS_ClearSlot(2);
};
};
};
func void QS_DoFrame()
{
if(Npc_IsInState(hero,ZS_Dead) == FALSE) && (hero.attribute[0] > 1)
{
if(Hlp_IsValidHandle(QS_BackGround))
{
QS_HasItems(4);
QS_HasItems(5);
QS_HasItems(6);
QS_HasItems(7);
QS_HasItems(8);
QS_HasItems(9);
QS_HasItems(0);
QS_PrintAmount(4);
QS_PrintAmount(5);
QS_PrintAmount(6);
QS_PrintAmount(7);
QS_PrintAmount(8);
QS_PrintAmount(9);
QS_PrintAmount(0);
//
QS_Use();
// render broni
if(MEM_Game.singleStep)
{
View_DeleteText(QS_BackGround);
};
};
QS_RenderWeapons();
QS_Magic(4, KEY_4 - 4);
QS_Magic(5, KEY_5 - 4);
QS_Magic(6, KEY_6 - 4);
QS_Magic(7, KEY_7 - 4);
QS_Magic(8, KEY_8 - 4);
QS_Magic(9, KEY_9 - 4);
QS_Magic(0, KEY_0 - 4);
};
};
func void QS_Init()
{
const int INIT = 0;
if (!INIT) {
HookEngineF(oCNpc__OpenInventory, 6, OpenInv);
HookEngineF(oCNpc__CloseInventory, 6, CloseInv);
HookEngineF(7490528, 7, CloseInv); //CDocumentManager::Show(int)
HookEngineF(6714517, 7, QS_Hook);
FF_ApplyOnce(QS_DoFrame);
INIT = 1;
};
};
Geändert von Orc Hunter UA (15.07.2018 um 17:25 Uhr)
Beim Laden des Spielstands kommt eine Fehlermeldung "unbekannte Instanz (Zeile 4)"
Wir benutzen zusätzlich noch die LeGo-Talente, so dass eigentlich einige Arrays gespeichert sein sollten. Für den Helden zum Beispiel steht im Helden aivar (hero.aivar[AIV_TALENT]) auch eine mögliche Adresse drin.
Wenn ihr die Talents benutzt, heißt das wohl, dass auch noch andere Handles/Arrays fehlen?
Abgesehen davon: Eure Handles gehen bei 358271 los, deckt sich das (ungefähr) mit anderen Savegames? Das sind ja schon recht hohe Werte... Aber richtig nummeriert sind sie, also eigentlich okay.
Das ist auf jedenfall das erste Mal, dass mir der Fehler unterkommt. Ohne den irgendwie zu reproduzieren, wüsste ich jetzt nicht, wie ich den finden geschweige denn beheben sollte.
Wenn ihr die Talents benutzt, heißt das wohl, dass auch noch andere Handles/Arrays fehlen?
Abgesehen davon: Eure Handles gehen bei 358271 los, deckt sich das (ungefähr) mit anderen Savegames? Das sind ja schon recht hohe Werte... Aber richtig nummeriert sind sie, also eigentlich okay.
Das ist auf jedenfall das erste Mal, dass mir der Fehler unterkommt. Ohne den irgendwie zu reproduzieren, wüsste ich jetzt nicht, wie ich den finden geschweige denn beheben sollte.
Ja, es fehlen diverse Talent-Arrays und Respawn-Objekte.
Hier mal ein Beispiel von später im Spiel:
Könnte es sein, dass das fehlerhafte erste handle zu Problemen mit späteren handles geführt hat, so dass diese einmal nicht mehr gespeichert wurden?
Sollten die Referenzen eigentlich von 1 losgehen und dann hochgezählt werden? Dann sind das ja wirklich viel zu hohe Zahlen (wir haben ja keine 4 Million Monster + Npc)
Werden Referenzen neu vergeben, wenn alte gelöscht werden?
Gibt es etwas, wonach ich gut im Code suchen kann, um zu sehen, wo solche Instanzen angelegt werden?
oder einen Counter der höchsten Referenz, um schnell mitzubekommen, wann / wo ein paar hunderttausend hinzukommen?
Die Tester merken noch an, dass sich die Speicher- und Ladezeiten im Spiel nach einer Weile teils drastisch erhöhen. Zusamenhang möglich?
Die Nummer wird hochgezählt und es werden keine Nummern neu vergeben.
Mit numHandles() kannst du dir die Anzahl der momentan "lebenden" Handles geben lassen und in der Variable nextHandle steht der Counter.
Erstellt ihr vielleicht jeden Frame Handles mit new()?
Edit: Vielleicht sollte man bei toten NPCs/Monstern auch mal die Talent-Handles aufräumen/löschen? Daran hab ich noch gar nicht gedacht, aber wenn man das mit Respawn verbindet, könnte man da wohl potentiell einige Handles erzeugen...
How to center text in View_AddText? X and Y coordinates are dependent on texture size, not screen resolution, so using Print_GetStringWidth is pointless in this case.
“Da ist auch noch ein anderer Geruch in der Luft, der Geruch von Feuern, die in der Ferne brennen, mit einem Hauch Zimt darin - so riecht das Abenteuer!”
― aus Walter Moers' "Die 13 1/2 Leben des Käpt'n Blaubär"
Erstellt ihr vielleicht jeden Frame Handles mit new()?
Ich habe die Stelle bei uns gefunden, an der die handles erstellt werden.
Code:
func void LoA_ChangeFocusBar(){
if (SYSVAR_FEATURES & FEATURE_ARROWDAMAGE) {
var oCViewStatusBar bar; bar = _^(MEM_Game.focusBar);
var int h; h = wrap(zCView@, bar.value_bar);
var oCNpc _HERO; _HERO = Hlp_GetNpc(hero);
if (_HERO.focus_vob && (Hlp_Is_oCNpc(_HERO.focus_vob))) { // NEUE BAR erstellen
var C_Npc victim; victim = _^(_HERO.focus_vob);
if (victim.aivar[AIV_POISONED] > 0)
&& victim.aivar[AIV_POISONED] != 1000
&& victim.attribute[ATR_HITPOINTS] > 0{
View_SetTexture(h, LOA_POISONBAR);
}else {
View_SetTexture(h, LOA_HPBAR);
};
};
release(h);
};
};
(alle 100 Millisekunden)
Ziel ist es, die Fokusbar grün zu gestalten, wenn der Npc vergiftet ist.
Um zu vermeiden, jedes Mal ein neues Handle zu erstellen, habe ich jetzt das hier ausprobiert:
Code:
var int h_LoA_ChangeFocusBar_zCView;
func void LoA_ChangeFocusBar(){
if (SYSVAR_FEATURES & FEATURE_ARROWDAMAGE) {
var oCViewStatusBar bar; bar = _^(MEM_Game.focusBar);
if !Hlp_IsValidHandle(h_LoA_ChangeFocusBar_zCView)
{
h_LoA_ChangeFocusBar_zCView = new(zCView@);
};
setPtr(h_LoA_ChangeFocusBar_zCView, bar.value_bar);
var oCNpc _HERO; _HERO = Hlp_GetNpc(hero);
if (_HERO.focus_vob && (Hlp_Is_oCNpc(_HERO.focus_vob))) { // NEUE BAR erstellen
var C_Npc victim; victim = _^(_HERO.focus_vob);
if (victim.aivar[AIV_POISONED] > 0)
&& victim.aivar[AIV_POISONED] != 1000
&& victim.attribute[ATR_HITPOINTS] > 0{
View_SetTexture(h_LoA_ChangeFocusBar_zCView, LOA_POISONBAR);
}else {
View_SetTexture(h_LoA_ChangeFocusBar_zCView, LOA_HPBAR);
};
};
};
};
Allerdings klappt es noch nicht so ganz. Wenn ich Gothic beende, starte, und dann ein Spiel lade, dann wird die Balken-Textur auf den gesamten Bildschirm gestreckt. Was läuft schief? Das passiert selbst dann, wenn ich vorm Laden den Code ändere, so dass obige Funktion leer ist - es scheint also damit zusammenzuhängen, dass ich vorm Speichern etwas an den Texturen geändert habe.
Ich habe die Stelle bei uns gefunden, an der die handles erstellt werden.
Code:
func void LoA_ChangeFocusBar(){
if (SYSVAR_FEATURES & FEATURE_ARROWDAMAGE) {
var oCViewStatusBar bar; bar = _^(MEM_Game.focusBar);
var int h; h = wrap(zCView@, bar.value_bar);
var oCNpc _HERO; _HERO = Hlp_GetNpc(hero);
if (_HERO.focus_vob && (Hlp_Is_oCNpc(_HERO.focus_vob))) { // NEUE BAR erstellen
var C_Npc victim; victim = _^(_HERO.focus_vob);
if (victim.aivar[AIV_POISONED] > 0)
&& victim.aivar[AIV_POISONED] != 1000
&& victim.attribute[ATR_HITPOINTS] > 0{
View_SetTexture(h, LOA_POISONBAR);
}else {
View_SetTexture(h, LOA_HPBAR);
};
};
release(h);
};
};
(alle 100 Millisekunden)
Ziel ist es, die Fokusbar grün zu gestalten, wenn der Npc vergiftet ist.
Um zu vermeiden, jedes Mal ein neues Handle zu erstellen, habe ich jetzt das hier ausprobiert:
Code:
var int h_LoA_ChangeFocusBar_zCView;
func void LoA_ChangeFocusBar(){
if (SYSVAR_FEATURES & FEATURE_ARROWDAMAGE) {
var oCViewStatusBar bar; bar = _^(MEM_Game.focusBar);
if !Hlp_IsValidHandle(h_LoA_ChangeFocusBar_zCView)
{
h_LoA_ChangeFocusBar_zCView = new(zCView@);
};
setPtr(h_LoA_ChangeFocusBar_zCView, bar.value_bar);
var oCNpc _HERO; _HERO = Hlp_GetNpc(hero);
if (_HERO.focus_vob && (Hlp_Is_oCNpc(_HERO.focus_vob))) { // NEUE BAR erstellen
var C_Npc victim; victim = _^(_HERO.focus_vob);
if (victim.aivar[AIV_POISONED] > 0)
&& victim.aivar[AIV_POISONED] != 1000
&& victim.attribute[ATR_HITPOINTS] > 0{
View_SetTexture(h_LoA_ChangeFocusBar_zCView, LOA_POISONBAR);
}else {
View_SetTexture(h_LoA_ChangeFocusBar_zCView, LOA_HPBAR);
};
};
};
};
Allerdings klappt es noch nicht so ganz. Wenn ich Gothic beende, starte, und dann ein Spiel lade, dann wird die Balken-Textur auf den gesamten Bildschirm gestreckt. Was läuft schief? Das passiert selbst dann, wenn ich vorm Laden den Code ändere, so dass obige Funktion leer ist - es scheint also damit zusammenzuhängen, dass ich vorm Speichern etwas an den Texturen geändert habe.
Wie sollte ich das besser angehen?
Habe ich jetzt so gelöst:
Code:
func void LoA_ChangeFocusBar(){
if (SYSVAR_FEATURES & FEATURE_ARROWDAMAGE) {
var oCNpc _HERO; _HERO = Hlp_GetNpc(hero);
if (_HERO.focus_vob && (Hlp_Is_oCNpc(_HERO.focus_vob))) { // NEUE BAR erstellen
var C_Npc victim; victim = _^(_HERO.focus_vob);
var oCViewStatusBar bar; bar = _^(MEM_Game.focusBar);
if (victim.aivar[AIV_POISONED] > 0)
&& victim.aivar[AIV_POISONED] != 1000
&& victim.attribute[ATR_HITPOINTS] > 0
{
if !Hlp_StrCmp(ViewPtr_GetTexture(bar.value_bar),LOA_POISONBAR)
{
ViewPtr_SetTexture(bar.value_bar, LOA_POISONBAR);
};
}else {
if !Hlp_StrCmp(ViewPtr_GetTexture(bar.value_bar),LOA_HPBAR)
{
ViewPtr_SetTexture(bar.value_bar, LOA_HPBAR);
};
};
};
};
};
Also direkt ViewPtr_SetTexture benutzt, um handles hier zu vermeiden.
Frage: Ist es performanter, mit
Code:
Hlp_StrCmp(ViewPtr_GetTexture,...))
abzufragen, ob sich die Textur geändert hat und nur dann die Textur des Views zu ändern, oder sollte ich einfach jedes Mal direkt die Textur überschreiben?
Die Funktion wird häufig (alle 100ms) aufgerufen, daher hätte ich gerne eine "schöne" Lösung, falls da jemand eine Empfehlung für hat.
abzufragen, ob sich die Textur geändert hat und nur dann die Textur des Views zu ändern, oder sollte ich einfach jedes Mal direkt die Textur überschreiben?
Die Funktion wird häufig (alle 100ms) aufgerufen, daher hätte ich gerne eine "schöne" Lösung, falls da jemand eine Empfehlung für hat.
Raum für bessere Performance gibt es denke ich. Die FrameFunction läuft ja durchgehend und muss dann aber immer noch fragen, ob überhaupt ein NPC im Fokus ist. Du könntest direkt das Zeichnen der gegnerischen Healthbar an Adresse X hooken (dann wird die Funktion nicht fortlaufen vergeblich ausgeführt und man spart sich das Abfragen ob jemand im Fokus ist) und von da aus den lokalen Pointer auf den Texturnamen an Adresse X+Y überschreiben, je nachdem ob der NPC in Frage vergiftet ist oder nicht. Das bedarf aber natürlich ein bisschen Nachforschung in der Engine. Hier in Semi-Pseudo-Code mit Platzhaltern X, X+Y, len(X) und register, wobei register eines der HookEngine-Registervariablen ist, in dem der Zeiger auf den NPC gespeichert ist.
Code:
func void overridefocushealthbar() {
const int once = FALSE;
if (!once) {
MemoryProtectionOverride(X+Y, 4);
var int origPtr; origPtr = MEM_ReadInt(X+Y);
once = TRUE;
};
const string poisontex = "POISONBAR.TEX";
var C_Npc npc; npc = _^(register);
if (npc.aivar[AIV_POISONED]) {
MEM_WriteInt(X+Y, _@s(poisontex));
} else {
MEM_WriteInt(X+Y, origPtr);
};
};
// [...]
//Initialization
HookEngineF(X, len(X), overridefocushealthbar);
Ich habe mir gedacht, die "Forschung" in der Engine noch etwas weiter zu de-mystifizieren. Im Editing-Wiki nun also einige Informationen zur Benutzung von IDA, knappe Erklärungen zu Assembly und co. und ein Schritt-für-Schritt-Beispiel wie ich das overrideBars-Skript, zum Anpassen von HP-, Mana-, Schwimm- und Focus-Bar, erstellt habe.
Das ist auf jedenfall das erste Mal, dass mir der Fehler unterkommt. Ohne den irgendwie zu reproduzieren, wüsste ich jetzt nicht, wie ich den finden geschweige denn beheben sollte.
In der Gothic.rpt habe ich noch Einträge zu einigen vorangegangen Abstürzen gefunden. Darunter auch solche:
Zum ersten: zu 0x0153368A (von CallFunc, =22230666) findet DecDat kein Symbol. Gleiches gilt auch für die 4. Access Violation (0x3A215060 = 975261792)
Meine Vermutung, welcher Code den Fehler ausgelöst haben könnte:
Der Code wird seit der Version, in der der Fehler auftritt, direkt vorm Speichern / Laden ausgeführt. Kann da jemand möglicherweise Komplikationen mit PermMem erkennen?
Code:
FF_ApplyOnce(DetectKeyPress);
...
func void FixInventory(var int Slotnumber)
{
if (Npc_GetInvItemBySlot (hero, 0, Slotnumber) > 0)
{
// if bow or crossbow ammo
if ((item.mainflag&ITEM_KAT_MUN)
&& ((item.flags&ITEM_CROSSBOW)
|| (item.flags&ITEM_BOW)
)
)
{
// recreate inventory item
var int num_it;
var int itemid;
itemid = Hlp_GetInstanceID (item);
num_it = NPC_HasItems (hero, itemid);
NPC_RemoveInvItems (hero, itemid, num_it);
CreateInvItems (hero, itemid, num_it);
};
// recursion
FixInventory(Slotnumber+1);
}
else
{
// in the end
RestoreAmmoOfHero();
};
};
...
var int key_pressed_already;
func void DetectKeyPress()
{
// using MEM_KeyPressed instead of MEM_KeyState
// because other functions check the same keys
if (MEM_KeyPressed(KEY_ESCAPE)
|| MEM_KeyPressed(KEY_F5)
|| MEM_KeyPressed(KEY_F9))
{
if !key_pressed_already
{
FixInventory(0);
key_pressed_already = true;
};
}
else
{
key_pressed_already = false;
};
};
Es wird also ESC (sowie Quicksave und Quickload) abgefangen und dann das Inventar durchgegangen. Bolzen und Pfeile werden entfernt und wieder eingefügt. Anschließend wird geschaut, dass die Munition noch richtig ausgewählt ist:
Code:
//******************************************************
// Utility functions
//******************************************************
func int getRangedWeapon(var c_npc npc) {
var c_item temp;
var int itmPtr;
itmPtr = 0;
if (Npc_HasEquippedRangedWeapon(hero)) {
temp = Npc_GetEquippedRangedWeapon(hero);
} else if (Npc_HasReadiedWeapon(hero)) {
temp = Npc_GetReadiedWeapon(hero);
};
if (!Hlp_IsValidItem(temp)) {
return 0;
};
if (temp.flags & ITEM_BOW) || (temp.flags & ITEM_CROSSBOW) {
itmPtr = _@(temp);
};
return +itmPtr;
};
func int itemIsBow(var c_item itm) {
return itm.flags & ITEM_BOW;
};
func int itemIsCrossbow(var c_item itm) {
return itm.flags & ITEM_CROSSBOW;
};
func int itemIsActive(var c_item itm) {
return itm.flags & ITEM_ACTIVE;
};
func void updateInventoryItemStatus(var c_npc npc, var int instanceId, var int isActive) {
Npc_GetInvItem(npc, instanceId);
if (!Hlp_IsValidItem(item)) {
return;
};
if (isActive) {
item.flags = item.flags | ITEM_ACTIVE;
} else {
item.flags = item.flags &~ ITEM_ACTIVE;
};
};
//************************************
// update and restore functions
//************************************
/*
* @param bow: specifies if ammo is meant for bow weapon; if bow is false, it is implied, that ammo is meant for crossbows.
*/
func void UpdateAmmo(var int ammo, var int bonus_damage, var int bow) {
var int old_mark_instanceId;
if (bow) {
old_mark_instanceId = GLOBAL_ARROW_INSTANCE;
} else {
old_mark_instanceId = GLOBAL_BOLT_INSTANCE;
};
if (ammo != old_mark_instanceId) {
//alte Markierung loswerden:
updateInventoryItemStatus(hero, old_mark_instanceId, FALSE);
};
//neue Markierung setzen (nach Laden auch kaputt):
updateInventoryItemStatus(hero, ammo, TRUE);
if (bow) {
GLOBAL_ARROW_INSTANCE = ammo;
} else {
GLOBAL_BOLT_INSTANCE = ammo;
};
};
func void UpdateCrossbow (var c_item crossBow, var int ammo, var int bonus_damage){
if (ammo == 0) { ammo = ItRw_Bolt; }; //Init!
updateInventoryItemStatus(hero, GLOBAL_ARROW_INSTANCE, FALSE);
if (Npc_GetInvItem(hero, GLOBAL_ARROW_INSTANCE)){ //remove marker from arrowinstance
item.flags = item.flags &~ ITEM_ACTIVE;
};
//------------------------------------------------
// Armbrust anpassen:
//------------------------------------------------
Current_Bolt_Bonus_Damage = bonus_damage;
if (Hlp_IsValidItem (crossBow)) {
crossBow.munition = ammo;
};
};
//&& (crossBow.flags & ITEM_CROSSBOW)
// && !(bow.flags & ITEM_CROSSBOW)
func void UpdateBow (var c_item bow, var int ammo, var int bonus_damage){
if (ammo == 0) { ammo = ItRw_Arrow; }; //Init!
updateInventoryItemStatus(hero, GLOBAL_BOLT_INSTANCE, FALSE);
if (Npc_GetInvItem(hero, GLOBAL_BOLT_INSTANCE)){ //remove marker from boltinstance
item.flags = item.flags &~ ITEM_ACTIVE;
};
//------------------------------------------------
// Bogen anpassen:
//------------------------------------------------
Current_Arrow_Bonus_Damage = bonus_damage;
if (Hlp_IsValidItem (bow)) {
bow.munition = ammo;
};
};
func void UpdateBowAndAmmo (var int ammo, var int bonus_damage){
//Print("UpdateBowAndAmmo");
if (ammo == 0) { ammo = ItRw_Arrow; }; //Init!
updateInventoryItemStatus(hero, GLOBAL_BOLT_INSTANCE, FALSE);
if (Npc_GetInvItem(hero, GLOBAL_BOLT_INSTANCE)){ //remove marker from boltinstance
item.flags = item.flags &~ ITEM_ACTIVE;
};
//------------------------------------------------
// Bogen anpassen:
//------------------------------------------------
var C_ITEM Bow;
if (Npc_HasEquippedRangedWeapon(hero)) {
Bow = Npc_GetEquippedRangedWeapon (hero);
};
if (Npc_HasReadiedWeapon(hero)) {
Bow = Npc_GetReadiedWeapon (hero);
};
if (Bow.flags & ITEM_BOW) {
if (Hlp_IsValidItem(Bow)) {
UpdateBow(Bow, ammo, bonus_damage);
};
UpdateAmmo(ammo, bonus_damage, TRUE);
};
};
func void UpdateCrossbowAndAmmo (var int ammo, var int bonus_damage){
if (ammo == 0) { ammo = ItRw_Bolt; }; //Init!
updateInventoryItemStatus(hero, GLOBAL_ARROW_INSTANCE, FALSE);
if (Npc_GetInvItem(hero, GLOBAL_ARROW_INSTANCE)){ //remove marker from arrowinstance
item.flags = item.flags &~ ITEM_ACTIVE;
};
//------------------------------------------------
// Armbrust anpassen:
//------------------------------------------------
var C_ITEM crossBow;
if (Npc_HasEquippedRangedWeapon(hero)) {
crossBow = Npc_GetEquippedRangedWeapon (hero);
};
if (Npc_HasReadiedWeapon(hero)) {
crossBow = Npc_GetReadiedWeapon (hero);
};
if (crossBow.flags & ITEM_CROSSBOW) {
if (Hlp_IsValidItem(crossBow)) {
UpdateCrossbow(crossBow, ammo, bonus_damage);
};
UpdateAmmo(ammo, bonus_damage, FALSE);
};
};
func void RestoreAmmoOfHero() {
var C_ITEM ranged;
if (Npc_HasEquippedRangedWeapon(hero)){ // Bow Ammo updatefunction
ranged = Npc_GetEquippedRangedWeapon(hero);
} else if (Npc_HasReadiedWeapon(hero)) {
ranged = Npc_GetReadiedWeapon(hero);
};
if (!Hlp_IsValidItem(ranged)){
return;
};
if (ranged.flags & ITEM_BOW){
UpdateBowAndAmmo (GLOBAL_ARROW_INSTANCE, Current_Arrow_Bonus_Damage);
};
if (ranged.flags & ITEM_CROSSBOW){
UpdateCrossbowAndAmmo(GLOBAL_BOLT_INSTANCE, Current_Bolt_Bonus_Damage);
};
};
func void UpdateRangedWeapon(var c_item itm) {
if (itm.flags & ITEM_BOW) {
UpdateBow(itm, GLOBAL_ARROW_INSTANCE, Current_Arrow_Bonus_Damage);
UpdateAmmo(GLOBAL_ARROW_INSTANCE, Current_Arrow_Bonus_Damage, TRUE);
} else if (itm.flags & ITEM_CROSSBOW) {
//Print("crossbow will be updated!");
UpdateCrossbow(itm, GLOBAL_BOLT_INSTANCE, Current_Bolt_Bonus_Damage);
UpdateAmmo(GLOBAL_BOLT_INSTANCE, Current_Bolt_Bonus_Damage, FALSE);
};
};
func void RestoreAmmoStateOfNpc(var c_npc npc) {
//get equipped ranged weapon
var int itmPtr;
var c_item ranged;
itmPtr = getRangedWeapon(npc);
if (itmPtr == 0) {
return;
};
ranged = _^(itmPtr);
if (!Hlp_IsValidItem(ranged)) {
return;
};
if (itemIsBow(ranged)) {
updateInventoryItemStatus(hero, GLOBAL_ARROW_INSTANCE, TRUE);
} else if (itemIsCrossbow(ranged)) {
updateInventoryItemStatus(hero, GLOBAL_BOLT_INSTANCE, TRUE);
};
};
Ich habe mir gedacht, die "Forschung" in der Engine noch etwas weiter zu de-mystifizieren. Im Editing-Wiki nun also einige Informationen zur Benutzung von IDA, knappe Erklärungen zu Assembly und co. und ein Schritt-für-Schritt-Beispiel wie ich das overrideBars-Skript, zum Anpassen von HP-, Mana-, Schwimm- und Focus-Bar, erstellt habe.
Über Rückmeldung zur Verständlichkeit und zur Korrektheit würde ich mich freuen.
Wirklich gut
Ich denke Bilder von IDA könnten helfen (wenn sie nicht zu sehr abschrecken), wenn man das Programm das erste Mal öffnet erschlägt einen das Interface fast.
Und den Quick-Filter mit Strg+F gibt es zumindest in IDA 5.5 nicht, da muss man auf Alt+T zurückgreifen, um eine normale Suche zu bekommen (und Strg+T um zum nächsten Ergebnis zu springen). Allerdings gibt es eigentlich keinen Grund, so eine alte IDA-Version zu verwenden.
Ich habe vor Jahren mal ein IDA-Plugin geschrieben, dass das Inspizieren von Daedalus-Symbolen ermöglicht hat. Abgesehen davon, dass ich das Plugin schon lange verschwunden ist und nicht ausgereift war: Besteht an sowas Interesse? Die meisten werden IDA vermutlich nur zur statischen Analyse verwenden.
Wirklich gut
Ich denke Bilder von IDA könnten helfen (wenn sie nicht zu sehr abschrecken), wenn man das Programm das erste Mal öffnet erschlägt einen das Interface fast.
Und den Quick-Filter mit Strg+F gibt es zumindest in IDA 5.5 nicht, da muss man auf Alt+T zurückgreifen, um eine normale Suche zu bekommen (und Strg+T um zum nächsten Ergebnis zu springen). Allerdings gibt es eigentlich keinen Grund, so eine alte IDA-Version zu verwenden.
Ich glaube das mit Strg+F kam erst mit 6.0 . In der neusten Freeware (7.0) geht Strg+F zumindest.
Allerdings haben sie da dafür den Debugger verbannt :/
Zitat von Lehona
Ich habe vor Jahren mal ein IDA-Plugin geschrieben, dass das Inspizieren von Daedalus-Symbolen ermöglicht hat. Abgesehen davon, dass ich das Plugin schon lange verschwunden ist und nicht ausgereift war: Besteht an sowas Interesse? Die meisten werden IDA vermutlich nur zur statischen Analyse verwenden.
Wirklich gut
Ich denke Bilder von IDA könnten helfen (wenn sie nicht zu sehr abschrecken), wenn man das Programm das erste Mal öffnet erschlägt einen das Interface fast.
Ich habe mal eine Reihe von Bildern eingefügt. Das Abschreckende an IDA habe ich in extra bunten Beschriftungen erstickt.
Zitat von Lehona
Ich habe vor Jahren mal ein IDA-Plugin geschrieben, dass das Inspizieren von Daedalus-Symbolen ermöglicht hat. Abgesehen davon, dass ich das Plugin schon lange verschwunden ist und nicht ausgereift war: Besteht an sowas Interesse? Die meisten werden IDA vermutlich nur zur statischen Analyse verwenden.
Wie hat man sich das Plugin denn praktisch vorzustellen? Je nach dem wie viel Arbeit dir das macht, kann es denke ich nicht schaden. Das kann das allgemeine Interesse an IDA nur fördern.
Wenn man sich mit dem Debugger eingeklinkt hat, konnte man einen Hotkey drücken, um ein beliebiges Symbol (via Name) in der Konsole auszugeben. Habe das damals in C geschrieben, aber mittlerweile würde ich wohl eher Python nehmen (selbst IDA 5.5 kann Python ausführen), dann könnte man vermutlich auch ein paar nützliche Python-Funktionen verfügbar machen. Nur Symbole ausgeben ist recht einfach, zu 95% kann man das eh aus Ikarus kopieren. 'N Traum wäre immer noch ein richtiger Daedalus-Debugger, das wäre aber vermutlich ein bisschen mehr Arbeit :P Dennoch machbar.
Ich würde gerne von den Scripten heraus den Helden dazu anzuweisen, dass er aufhört ein Mobsi zu verwenden.
Prinzipiell könnte man das über AI_Standup regeln, aber da werden die Animationen abgebrochen, was recht unschön aussieht.
Im Spiel geht das ja normalerweise über STRG oder mit den Richtungstasten.
Intern wird also sicherlich eine Event-Message an den Helden verschickt, nur kriege ich leider nicht raus, welche das sein müsste. Hat das Jemand mal zufällig herausgesucht?