Archiv verlassen und diese Seite im Standarddesign anzeigen : [Scriptsammlung] ScriptBin
Es fliegen so viele nützliche Scripte (klein bis groß) hier im Forum herum, in denen teilweise sehr viel Aufwand steckt, dass es Schade ist, dass sie hier im Forum mehr oder weniger untergehen: Im Endeffekt sind sie nur über die Signaturen der Ersteller zu finden, wenn die denn noch aktiv sind. Vergangene Versuche, irgendwelche Listen von "interessanten Scripten" zu führen, sind auch immer wieder fehlgeschlagen (nicht mehr aktuell und nur größere Scripte). Die kürzeren Scripte, die teilweise auf Nachfrage in Threads angefertigt werden, verschwinden komplett von der Bildfläche: Es gibt keine Links, die dort hinführen und wenn man den Thread nicht zufällig gelesen hat, gibt es keine Chance, sie zu finden.
Daher habe ich eine wilde Sammlung von Scripten, den ScriptBin, zusammengestellt. Das soll einerseits eine zentrale Ressource sein, durch die man stöbern und eventuell einen Nutzen ziehen kann, andererseits eine nützliche Basis für weitere Scripte, die jemand der Öffentlichkeit bereitstellen möchte. Es gab schon öfter Posts, in denen jemand ein Script gepostet hat, um Vobs zu bewegen. Jedesmal musste man entweder auf einen alten Post von Sekti verweisen oder die dafür nötigen Funktionen (SetPositionWorldVec, VobPositionUpdated, ...) selber zum Post hinzufügen - und das ist sicherlich nur eines von vielen Beispielen. Es gibt eine einfache Lösung für ein Problem, die die Broadcasts von Sektenspinner benutzt? Dann muss man erstmal den Link rauskramen, wenn man so eine Lösung posten möchte.
Ein weiterer Anreiz (vor allem für Neulinge) sollte die einfache Installation sein: Hat man die .zip-Datei entpackt, muss man nur noch einen einzigen Eintrag in der Gothic.src vornehmen. Vor allem die Installation von Ikarus* hat einige da sicherlich verwirrt.
Vorneweg: Keine der Scripte in dieser Sammlung machen irgendetwas, wenn sie nicht aufgerufen werden. Wenn ihr nur eine einzige Funktion benutzt, werden euch also auch keine anderen Funktionen irgendwie dazwischenfunken (z.B. bei Crash bei Weltwechsel oder was auch immer). Kurzum: Es geht nichts kaputt, wenn ihr den ScriptBin installiert.
Wer kein Ikarus benutzen möchte hat es auch nicht weiter schwierig, die Ordnerstruktur trennt zwischen "normalen" Scripten und Scripten, die Ikarus benötigen.
Mal ein paar Beispiele, was für Scripte bisher enthalten sind und welche "Umfang" sie haben:
// Banale Scripte, die kaum mehr als ein Einzeiler sind:
func int max(var int i, var int j) {
if (i > j) {
return i;
};
return j;
};
func int max3(var int i1, var int i2, var int i3) {
return max(max(i1, i2), i3);
};
func int max4(var int i1, var int i2, var int i3, var int i4) {
return max(max3(i1, i2, i3), i4);
};
func string i2s(var int i) {
return IntToString(i);
};
func string cs2 (var string s1, var string s2) {
return ConcatStrings (s1, s2);
};
func string cs3 (var string s1, var string s2, var string s3) {
return cs2 (cs2 (s1, s2), s3);
};
//Schon interessanter
func int Log_GetTopicStatus(var string name) {
const int logMan = 11191608; //0xaac538
var zCList list; list = _^(logMan);
while(list.next);
list = _^(list.next);
if (list.data) {
if (Hlp_StrCmp(MEM_ReadString(list.data), name)) {
return MEM_ReadInt(list.data + 24);
};
};
end;
return -1;
};
Außerdem habe ich auch größere Scripte, die inhaltlich in dieses Paket passen, hinzugefügt, wie z.B. die Broadcasts von Sektenspinner oder die "Insert Anything"-Scripte von mud-freak. In diesem Fall wurden jeweils die Autoren genannt und es gibt einen Link zum dazugehörigen Thread/Post. Da die Scripte bereits frei verfügbar waren, hoffe ich, dass das im Sinne der Autoren ist :)
Natürlich konnte ich nicht alle Posts sichten, die hier im Editing gepostet wurden. Bisher sind also vor allem Scripte enthalten, an die ich mich selber noch erinnern konnte plus einige wenige, die noch bei mir auf der Platte rumflogen. Sollte es hier eine positive Rückmeldung geben, würde ich mich freuen, wenn ihr weitere Scripte postet, die gut dazu passen könnten: Entweder fällt euch noch ein Post hier im Forum ein oder ihr habt eigene Ideen, die einfach nicht "groß genug" für einen eigenen Thread waren?
Aber ans Hauptsächliche: Hier (https://app.assembla.com/spaces/LM/subversion-2/source/HEAD)ist der Link. Ihr könnt einfach oben rechts auf "Download" klicken, dann solltet ihr eine .zip-Datei herunterladen. Die einfach in den Scripts\Content-Ordner entpacken und die ScriptBin.src in der Gothic.src eintragen (für ein paar weitere Informationen siehe ReadMe.txt).
Vielleicht können wir es zusammen den Anfängern ein bisschen einfacher und übersichtlicher machen ;)
*Ikarus ist ebenfalls im ScriptBin enthalten. Normalerweise nicht so elegant, aber Sektenspinner ist schon lange inaktiv, also ist es unwahrscheinlich, dass er noch aktiv daran arbeiten wird in der Zukunft. Ich bin mir sicher, diese Sammlung von Scripten ist in seinem Interesse.
mud-freak
25.05.2017, 17:49
Ich find die Idee gut. Vielleicht könntest du im Einleitungspost noch eine Liste der enthaltenen Skripte aufnehmen, damit man direkt einen Überblick hat.
Wäre nicht verkehrt, wenn diese Skript-Collection eine Art Standard wird.
Das SearchWaypointByName ist etwas "veraltet". Hier hatte ich eine simplere Variante vorgeschlagen. Als Funktion könnte man das dann so aufnehmen (Code ungetestet):
func int SearchWaypointByName(var string waypoint) {
const int zCWayNet__GetWaypoint = 8061744; //0x7B0330
var int waynetPtr; waynetPtr = _@(MEM_Waynet);
var int wpNamePtr; wpNamePtr = _@s(waypoint);
const int call = 0;
if (Call_Begin(call)) {
CALL__fastcall(_@(waynetPtr), _@(wpNamePtr), zCWayNet__GetWaypoint);
call = CALL_End();
};
return CALL_RetValAsInt();
};
Frank-95
26.05.2017, 08:00
Thank you lehona for your efforts in seizing the scripts!
Wenn du magst, kannst du auch hier (https://forum.worldofplayers.de/forum/threads/28219-Editing-FAQ?p=15755256&viewfull=1#post15755256) einige sinnvolle Skripte entnehmen.
Wie bist du auf ScriptBin gekommen? Bin hat mich erstmal an binary erinnert, oder meinst du den Papierkorb? :)
Wenn du magst, kannst du auch hier (https://forum.worldofplayers.de/forum/threads/28219-Editing-FAQ?p=15755256&viewfull=1#post15755256) einige sinnvolle Skripte entnehmen.
Wie bist du auf ScriptBin gekommen? Bin hat mich erstmal an binary erinnert, oder meinst du den Papierkorb? :)
Gute Idee.
Wenn man erst so wilde Ideen wie "daedalus - all you may need" -> "DAYMN" hat, klingt Script-Papierkorb gar nicht mal so schlecht :)
Außerdem sollte es keinen "Namen" (wie Ikarus oder LeGo) bekommen, weil es eben kein, uhm, "Produkt" von mir ist. Nur eine Sammlung.
Ist das ein WIP oder bleibt dies die einzige Version davon?
Der Sinn der Sache ist es, noch weiter zu wachsen. Bisher ist es ja nur eine sehr kleine Ansammlung.
Dokumentation müsste natürlich auch noch geschrieben werden...
Because i finished work in Ikarus and now i'm using AST(Agama Script Tools) i decided to share my scripts :)
Some Engine scripts:
func void SetAsPlayer (var C_NPC slf)
{
const int oCNpc__SetAsPlayer = 7612064;
CALL__thiscall (MEM_InstToPtr (slf), oCNpc__SetAsPlayer);
};
func int Menu_ReadInt(var string category, var string name) //int
{
var string value; value = Mem_GetGothOpt(category, name);
return STR_ToInt(value);
};
func void Menu_WriteInt(var string category, var string name, var int value)
{
MEM_SetGothOpt(category,name, IntToString(value));
};
func int oCNpc_GetModel(var int npc) //zCModel*
{
const int oCNpc__GetModel = 7571232;
CALL__thiscall(MEM_InstToPtr(npc), oCNpc__GetModel);
return CALL_RetValAsInt();
};
func int AniIsActive(var c_npc slf, var string aniname) //BOOL
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel_AniIsActive = 5727888;//0x00576690
CALL_zStringPtrParam(Str_Upper(aniname));
CALL__thiscall(ptr,zCModel_AniIsActive);
return CALL_RetValAsInt();
};
func int zCVisual_LoadVisual(var string vis) //zCVisual*
{
const int zCVisual__LoadVisual = 6318800; //0x00606AD0
CALL_zStringPtrParam(STR_Upper(vis));
CALL__cdecl(zCVisual__LoadVisual);
return CALL_RetValAsInt();
};
func int StringIsEmpty(var string str) //BOOL
{
if(STR_Len(str)>=1)
{
return true;
};
return false;
};
func void Set_ItemVisual(var int itm, var string vis)
{
const int oCItem__SetVisual = 7411984; //0x00711910
CALL_PtrParam(zCVisual_LoadVisual(vis));
CALL__thiscall(MEM_InstToPtr(itm),oCItem__SetVisual);
};
func int zCModel_SearchNode(var int model, var string node) //zCModelNodeInst*
{
CALL_zStringPtrParam(Str_Upper(node));
CALL__thiscall(model, zCModel__SearchNode);
return CALL_RetValAsInt();
};
func void Ext_PutInSlot(var int pnpc, var string node, var string visual)
{
const int zCModel__SetNodeVisual = 5739168;
var int model; model = oCNpc_GetModel(pnpc);
var int vis; vis = zCVisual_LoadVisual(visual);
var int nde; nde = zCModel_SearchNode(model,node);
CALL_IntParam(1); //3 param
CALL_PtrParam(vis); //2 param - vis
CALL_PtrParam(nde); //1 param get node
CALL__thiscall(model, zCModel__SetNodeVisual);
};
func void Ext_PutInSlot2(var int pnpc, var string node, var int vob) //Used to mounts system (hero, "NODE", her.focus_vob)
{
var int model; model = oCNpc_GetModel(pnpc);
var int nde; nde = zCModel_SearchNode(model,node);
CALL_PtrParam(1); //1 param get node
var int vb; vb = MEM_InstGetOffset(vob);
CALL_PtrParam(MEM_ReadInt(vb+200)); //2 param - vis
CALL_PtrParam(nde); //1 param get node
CALL__thiscall(model, zCModel__SetNodeVisual);
};
func int Get_AniIDFromAniName(var int slf, var string aniName) //int
{
const int zCModel__AniIDFromAniName = 6365296; //0x00612070
var int model; model = oCNpc_GetModel(slf);
CALL_zStringPtrParam(Str_Upper(aniName));
CALL__thiscall(model,zCModel__AniIDFromAniName);
return CALL_RetValAsInt();
};
func int GetAniFromAniID(var c_npc slf, var string aniName) //zCModelAni*
{
const int zCModel__GetAniFromAniID = 4665168; //0x00472F50
var int model; model = oCNpc_GetModel(slf);
var int ani; ani = Get_AniIDFromAniName(slf,aniName);
CALL_PtrParam(ani);
CALL__thiscall(model,zCModel__GetAniFromAniID);
return CALL_RetValAsInt();
};
func void Set_AniFPS(var c_npc slf, var string aniName, var int FPS)
{
var int ptr;
ptr = GetAniFromAniID(slf, aniName);
MEM_WriteInt(ptr+176, mkf(FPS));
};
func int GetNodeBBox3D(var int slf, var string bone) //Get Node BBox - Hitboxes or Check collision
{
var int model; model = oCNpc_GetModel(slf);
var int node; node = zCModel_SearchNode(model,bone);
CALL_PtrParam(node);
CALL_RetValIsStruct(24); //6*4
CALL__thiscall(model,zCModel__GetBBox3DNodeWorld);
return CALL_RetValAsPtr();
};
func oCItem Hlp_GetItem(var int inst) //oCItem*
{
var zCPar_Symbol symb; symb = _^(MEM_GetSymbolByIndex(inst));
_^(symb.offset);
};
Fade Screen
/********************
EXAMPLE TO USE:
func void fade()
{
//SetColor
FadeScreen_Color_R = 71;
FadeScreen_Color_G = 71;
FadeScreen_Color_B = 71;
FadeScreen_Color_Over = 1; //??? I don't remember xD
//SetStatus and multipler
FadeScreen_Multipler = 5; //1-10
Fade_Status = 1;
};
********************/
var int FadeScreen_Color_R;
var int FadeScreen_Color_G;
var int FadeScreen_Color_B;
var int FadeScreen_Color_A;
var int FadeScreen_Color_Over;
var int Fade_Status;
var int FadeScreen_Multipler;
func void FadeScreen_Function() //PerFrame
{
MEM_InitGlobalInst();
MEM_GAME.array_view_visible[0] = true;
MEM_GAME.array_view_enabled[0] = true;
MEM_CAMERA.screenFadeEnabled = true;
MEM_CAMERA.screenFadeColor = RGBA(FadeScreen_Color_R, FadeScreen_Color_G, FadeScreen_Color_B, FadeScreen_Color_A);
var zCView ptr; ptr = _^(MEM_GAME.array_view[0]);
ptr.alphafunc = FadeScreen_Color_Over;
if(Fade_Status == 1)
{
if(FadeScreen_Color_A<255)
{
FadeScreen_Color_A += FadeScreen_Multipler;
}
else if (FadeScreen_Color_A>=255)
{
FadeScreen_Color_A = 255;
Fade_Status =2;
};
}
else if (Fade_Status == 2)
{
if(FadeScreen_Color_A >= FadeScreen_Multipler)
{
FadeScreen_Color_A -= FadeScreen_Multipler;
}
else if (FadeScreen_Color_A <= FadeScreen_Multipler)
{
FadeScreen_Color_A= 0;
Fade_Status =0;
};
};
};
This is all at this time. (I couldn't found my next scripts :/ )
Regards Siemekk.
Fisk2033
28.05.2017, 09:43
Evtl. passt es ja in die Sammlung. :)
Hello §wink
Fisk2033 told me that you are interested in features presented on my YouTube channel. Most of them I never published, because it was made for polish mod - Dziedzictwo (Legacy in english). (http://themodders.org/index.php?board=421.0) Unfortunately our team leader Kaczka (Duck in english) died in car accident 2 months ago. All materials and scenario he had and not proper to asking his familly in mourning for files of some game. I can share some scripts, but a lot of them are unfinished yet like quickslot bar (which uses LeGo quickslot bar texture) or melee weapon damage system which allows attack any npc, dual wielding, armor wearing like in The Elders Scrolls (yes, it's possible with script packets). Some scripts (like cursor) are only "sketch" to check whether it is possible to do, so it don't make sense now.
Here is cinema scope script partly made by OrcWarrior. Call FF_ApplyOnce (CinemaScope); in INIT_Global.
func void CinemaScope()
{
var int ptr;
var int tmp;
var int CinemaScopeAlpha;
MEM_InitGlobalInst ();
MEM_GAME.array_view_visible[3] = true;
MEM_GAME.array_view_enabled[3] = true;
MEM_Camera.cinemaScopeEnabled = true;
MEM_Camera.cinemaScopeColor = RGBA (0, 0, 0, CinemaScopeAlpha);
if (InfoManager_HasFinished())
{
if (CinemaScopeAlpha >= 5)
{
CinemaScopeAlpha -= 10;
};
if (CinemaScopeAlpha == 5)
{
CinemaScopeAlpha = 0;
};
}
else
{
if (CinemaScopeAlpha <= 250)
{
CinemaScopeAlpha += 10;
};
if (CinemaScopeAlpha == 250)
{
CinemaScopeAlpha = 255;
};
};
ptr = MEM_InstGetOffset (MEM_GAME)+52;
ptr = MEM_ReadInt (ptr);
ptr = ptr+64;
MEM_WriteInt (ptr, 0);
ptr = MEM_InstGetOffset (MEM_GAME)+60;
ptr = MEM_ReadInt (ptr);
ptr = ptr+4;
MEM_WriteInt (ptr, 0);
ptr = ptr+12;
MEM_WriteInt (ptr, 1);
ptr = MEM_ReadInt (MEMINT_oCInformationManager_Address+28);
MEM_WriteInt (ptr+56, STR_ToInt (MEM_GetGothOpt ("VIDEO", "zVidResFullscreenX"))/6);
ptr = ptr+60;
if (Print_Screen[PS_Y] <= 768) //low res fix
{
tmp = Print_Screen[PS_Y] * 21 / 2 - ((Print_Screen[PS_Y] * 2 / 24) % 1);
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, Print_Screen[PS_Y] - tmp);
}
else
{
tmp = Print_Screen[PS_Y] * 113 / 128;
tmp = tmp - (tmp % 1);
if (Print_Screen[PS_Y] - tmp > 120)
{
tmp = tmp + (Print_Screen[PS_Y] - tmp - 120) / 2;
tmp = tmp - (tmp % 1);
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, 120);
}
else
{
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, Print_Screen[PS_Y] - tmp);
};
};
ptr = ptr+28;
MEM_WriteInt (ptr, 0);
};
Anit-cheat:
func void DisableCommand (var string command)
{
if (Hlp_StrCmp (STR_SubStr (MEM_ReadString (MEM_ReadInt (ESP+4)), 0, STR_Len (command)), command))
{
MEM_WriteString (MEM_ReadInt (ESP+4), "");
};
};
func void DisableKey (var int key)
{
if (MEM_ReadInt (ESP+4) == key)
{
MEM_WriteInt (ESP+4, -1);
};
};
func void MarvinTestKeys ()
{
DisableKey (KEY_F8); //example
};
func void EvaluateCommand ()
{
DisableCommand ("CHEAT GOD"); //example
};
func void AntiCheat_Init ()
{
HookEngineF (7882848, 7, EvaluateCommand);
HookEngineF (7329120, 6, MarvinTestKeys);
};
Hit dodging:
const int AIV_Dodge = 94; //percent chance to dodge hit
func void Dodge ()
{
var oCNpc attacker; attacker = _^ (ECX);
var oCAniCtrl_Human ani; ani = _^ (attacker.anictrl);
if (Hlp_Is_oCNpc (ani.hitTarget))
{
var c_npc target; target = _^ (ani.hitTarget);
if (Hlp_Random (100)+1 <= target.aivar[AIV_Dodge])
{
ani.hitTarget = 0;
if (Npc_IsPlayer (target))
{
PrintS ("Unik!");
}
else if (Npc_IsPlayer (attacker))
{
PrintS (ConcatStrings (target.name, " unika ciosu."));
};
};
};
};
func void Dodge_Init ()
{
HookEngineF (7668064, 6, Dodge); //left
HookEngineF (7671040, 7, Dodge); //right
HookEngineF (7673808, 7, Dodge); //run
HookEngineF (7664640, 6, Dodge); //combo
};
mud-freak
05.08.2017, 14:41
Name:
Wld_StopEffect_Ext
Voraussetzungen:
Ikarus 1.2
Kompatibilität:
Gothic 1 und Gothic 2
Beschreibung:
Schon immer hat mich an Wld_PlayEffect genervt, dass man Effekte nicht verlässlich stoppen kann. Wld_StopEffect beendet nur den erst gefundenen Effekt mit dem Namen, was häufig dazu geführt hat, dass Effekte in der Welt hängen geblieben sind. Ausserdem gibt es in Gothic 1 gar kein Wld_StopEffect, wodurch man gestartete Effekte gar nicht erst beenden konnte.
Ich möchte hier Wld_StopEffect_Ext vorstellen. Ein Skript, mit dem man genau bestimmen kann welcher Effekt beendet werden soll (und ob nur einer oder alle übereinstimmenden). Das ganze kompatibel mit Gothic 2 und Gothic 1. Die Funktion tut haargenau das, was Wld_StopEffect in der Engine tut, nur mit erweiterter Funktionalität.
Genauer:
Es gibt drei zusätzliche Funktionsparamenter und einen Rückgabewert:
func int Wld_StopEffect_Ext(string effect, int origin, int target, int stopAll)
Parameter zwei und drei sind Ursprungsobjekt und Zielobjekt (so wie man das von Wld_PlayEffect kennt). Übergeben wird jeweils eine Instanz. So kann mal sowohl NPCs und Items, als auch jegliche andere Instanz übergeben, die von der Klasse zCVob erbt. Diese Parameter können auch null sein, wenn sie nicht festgelegt werden sollen.
Der vierte Parameter lässt entscheiden, ob nur der erst gefundene Effekt gestoppt werden soll (0), so wie bei Wld_StopEffect, oder alle Effekte gestoppt werden sollen, die mit den Voraussetzungen übereinstimmen (1).
Der Rückgabewert gibt an, wie viele Effekte gestoppt wurden. Null bedeutet, dass entweder keine passenden Effekte gefunden wurden, oder die Argumente fehlerhaft waren. Mit diesem Rückgabewert hat man aus der Skripten nun auch mehr Kontrolle über was geschieht.
Beispiel:
var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops exactly and only the first effect
// Wld_StopEffect for Gothic 1
func void Wld_StopEffect(var string effect) {
Wld_StopEffect_Ext(effect, 0, 0, 0);
};
Aufgrund der vielen Speicheradressen für sowohl Gothic 1, als auch Gothic 2, wirkt das Skript enorm lang, ist aber eigentlich ziemlich kurz und simpel.
Ich habe das Skript schon in die Form der übrigen Skripte aus dem ScriptBin gebracht (Kopfzeile im Skript), kann also direkt so übernommen werden.
// Extended functionality for Wld_StopEffect()
// See https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin?p=25548652&viewfull=1#post25548652
// Made by mud-freak (@szapp) 2017-08-05 (modified 2017-08-09)
//
// Compatible with Gothic 1 and Gothic 2
// Requirements: Ikarus 1.2
/*
* Check the inheritance of a zCObject against a zCClassDef. Emulating zCObject::CheckInheritance() at 0x476E30 in G2.
* This function is used in Wld_StopEffect_Ext().
*/
func int objCheckInheritance(var int objPtr, var int classDef) {
if (!objPtr) || (!classDef) {
return 0;
};
const int zCClassDef_baseClassDef_offset = 60; //0x003C
// Iterate over base classes
var int curClassDef; curClassDef = MEM_GetClassDef(objPtr);
while((curClassDef) && (curClassDef != classDef));
curClassDef = MEM_ReadInt(curClassDef+zCClassDef_baseClassDef_offset);
end;
return (curClassDef == classDef);
};
/*
* Emulate the Gothic 2 external function Wld_StopEffect(), with additional settings: Usually it is not clear which
* effect will be stopped, leading to effects getting "stuck". Here, Wld_StopEffect is extended with additional checks
* for origin and/or target vob and whether to stop all matching FX or only the first one found (like in Wld_StopEffect)
* The function returns the number of stopped effects, or zero if none was found or an error occurred.
* Compatible with Gothic 1 and Gothic 2. This means there is finally the possibility to stop effects in Gothic 1!
*
* Examples:
* var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
* Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops only the first effect
*
* Calling Wld_StopEffect("EFFECT_1") corresponds to Wld_StopEffect_Ext("EFFECT_1", 0, 0, 0).
*
* Big parts if this function are taken from Wld_StopEffect. Gothic 2: sub_006E32B0() 0x6E32B0
*/
func int Wld_StopEffect_Ext(var string effectName, var int originInst, var int targetInst, var int all) {
// Gothic 1 addresses and offsets
const int zCVob__classDef_G1 = 9269976; //0x8D72D8
const int zCWorld__SearchVobListByClass_G1 = 6249792; //0x5F5D40
const int oCVisualFX__classDef_G1 = 8822272; //0x869E00
const int oCVisualFX__Stop_G1 = 4766512; //0x48BB30
const int oCVisualFX_originVob_offset_G1 = 1112; //0x0458
const int oCVisualFX_targetVob_offset_G1 = 1120; //0x0460
const int oCVisualFX_instanceName_offset_G1 = 1140; //0x0474
// Gothic 2 addresses and offsets
const int zCVob__classDef_G2 = 10106072; //0x9A34D8
const int zCWorld__SearchVobListByClass_G2 = 6439504; //0x624250
const int oCVisualFX__classDef_G2 = 9234008; //0x8CE658
const int oCVisualFX__Stop_G2 = 4799456; //0x493BE0
const int oCVisualFX_originVob_offset_G2 = 1192; //0x04A8
const int oCVisualFX_targetVob_offset_G2 = 1200; //0x04B0
const int oCVisualFX_instanceName_offset_G2 = 1220; //0x04C4
var int zCVob__classDef;
var int zCWorld__SearchVobListByClass;
var int oCVisualFX__classDef;
var int oCVisualFX__Stop;
var int oCVisualFX_originVob_offset;
var int oCVisualFX_targetVob_offset;
var int oCVisualFX_instanceName_offset;
zCVob__classDef = MEMINT_SwitchG1G2(zCVob__classDef_G1,
zCVob__classDef_G2);
zCWorld__SearchVobListByClass = MEMINT_SwitchG1G2(zCWorld__SearchVobListByClass_G1,
zCWorld__SearchVobListByClass_G2);
oCVisualFX__classDef = MEMINT_SwitchG1G2(oCVisualFX__classDef_G1,
oCVisualFX__classDef_G2);
oCVisualFX__Stop = MEMINT_SwitchG1G2(oCVisualFX__Stop_G1,
oCVisualFX__Stop_G2);
oCVisualFX_originVob_offset = MEMINT_SwitchG1G2(oCVisualFX_originVob_offset_G1,
oCVisualFX_originVob_offset_G2);
oCVisualFX_targetVob_offset = MEMINT_SwitchG1G2(oCVisualFX_targetVob_offset_G1,
oCVisualFX_targetVob_offset_G2);
oCVisualFX_instanceName_offset = MEMINT_SwitchG1G2(oCVisualFX_instanceName_offset_G1,
oCVisualFX_instanceName_offset_G2);
var int worldPtr; worldPtr = _@(MEM_World);
if (!worldPtr) {
return 0;
};
// Create array from all oCVisualFX vobs
var int vobArrayPtr; vobArrayPtr = MEM_ArrayCreate();
var zCArray vobArray; vobArray = _^(vobArrayPtr);
const int call = 0; var int zero;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(zero)); // Vob tree (0 == globalVobTree)
CALL_PtrParam(_@(vobArrayPtr)); // Array to store found vobs in
CALL_PtrParam(_@(oCVisualFX__classDef)); // Class definition
CALL__thiscall(_@(worldPtr), zCWorld__SearchVobListByClass);
call = CALL_End();
};
if (!vobArray.numInArray) {
MEM_ArrayFree(vobArrayPtr);
return 0;
};
effectName = STR_Upper(effectName);
var zCPar_Symbol symb;
// Validate origin vob instance
if (originInst) {
// Get pointer from instance symbol
if (originInst > 0) && (originInst < MEM_Parser.symtab_table_numInArray) {
symb = _^(MEM_ReadIntArray(contentSymbolTableAddress, originInst));
originInst = symb.offset;
} else {
originInst = 0;
};
if (!objCheckInheritance(originInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Origin is not a valid vob");
return 0;
};
};
// Validate target vob instance
if (targetInst) {
// Get pointer from instance symbol
if (targetInst > 0) && (targetInst < MEM_Parser.symtab_table_numInArray) {
symb = _^(MEM_ReadIntArray(contentSymbolTableAddress, targetInst));
targetInst = symb.offset;
} else {
targetInst = 0;
};
if (!objCheckInheritance(targetInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Target is not a valid vob");
return 0;
};
};
// Search all vobs for the matching name
var int stopped; stopped = 0; // Number of FX stopped
repeat(i, vobArray.numInArray); var int i;
var int vobPtr; vobPtr = MEM_ArrayRead(vobArrayPtr, i);
if (!vobPtr) {
continue;
};
// Search for FX with matching name
if (!Hlp_StrCmp(effectName, "")) {
var string effectInst; effectInst = MEM_ReadString(vobPtr+oCVisualFX_instanceName_offset);
if (!Hlp_StrCmp(effectInst, effectName)) {
continue;
};
};
// Search for a specific origin vob
if (originInst) {
var int originVob; originVob = MEM_ReadInt(vobPtr+oCVisualFX_originVob_offset);
if (originVob != originInst) {
continue;
};
};
// Search for a specific target vob
if (targetInst) {
var int targetVob; targetVob = MEM_ReadInt(vobPtr+oCVisualFX_targetVob_offset);
if (targetVob != targetInst) {
continue;
};
};
// Stop the oCVisualFX
const int call2 = 0; const int one = 1;
if (CALL_Begin(call2)) {
CALL_PtrParam(_@(one));
CALL__thiscall(_@(vobPtr), oCVisualFX__Stop);
call2 = CALL_End();
};
stopped += 1;
if (!all) {
break;
};
end;
MEM_ArrayFree(vobArrayPtr);
return stopped;
};
Was haltet ihr eigentlich davon, diesen Thread mal anzupinnen?
Fisk2033
09.08.2017, 07:39
Was haltet ihr eigentlich davon, diesen Thread mal anzupinnen?
Würde ich sehr begrüßen
Bei der Verwendung von Wld_StopEffect sollte der Anwender sich darüber im Klaren sein, dass beim Vergleich des Namens die Groß-/Kleinschreibung beachtet wird (ist auch bei der ZenGin-Implementierung so - die Namen sollten also immer groß geschrieben werden) und der Name des Effekts vom Level abhängen kann (Wld_PlayEffect("FOO", NULL, NULL, 42, 0, 0, 0) erzeugt einen Effekt mit dem Namen "FOO_L42" wenn auch eine Skriptinstanz mit diesem Namen existiert).
mud-freak
09.08.2017, 12:23
Bei der Verwendung von Wld_StopEffect sollte der Anwender sich darüber im Klaren sein, dass beim Vergleich des Namens die Groß-/Kleinschreibung beachtet wird (ist auch bei der ZenGin-Implementierung so - die Namen sollten also immer groß geschrieben werden) und der Name des Effekts vom Level abhängen kann (Wld_PlayEffect("FOO", NULL, NULL, 42, 0, 0, 0) erzeugt einen Effekt mit dem Namen "FOO_L42" wenn auch eine Skriptinstanz mit diesem Namen existiert).
Ich weiss nicht, ob ich dich falsch verstehe, aber da muss ich widersprechen. Alle Effektnamen werden von der Engine immer in Grossbuchstaben umgewandelt (auch wenn man sie als Wld_PlayEffect("foO", ...) o.Ä. startet, werden sie als "FOO" erstellt).
In der Funktion oben wird dann der übergebene Effektname in Grossbuchstaben umgewandelt (effectName = STR_Upper(effectName);), sodass ein Vergleich immer funktioniert.
Wo du aber Recht hast ist, dass ich das Effektlevel im Skript nicht berücksichtigt habe.
In der Funktion oben wird dann der übergebene Effektname in Grossbuchstaben umgewandelt (effectName = STR_Upper(effectName);), sodass ein Vergleich immer funktioniert.
Die ZenGin-External macht das nicht (https://github.com/REGoth-project/REGoth/blob/3fe4e8c5547e279690f85738ede7137054df9f5c/src/logic/scriptExternals/externalsGenerator/externals.game.g2.d#L419).
Deswegen müssen Skripter den Effektnamen immer groß schreiben, damit das Spiel den Effekt findet.
(ps: meine Dokumentation ist noch fehlerhaft, es wird, wie du schon geschrieben hast, nur der erste gefundene Effekt beendet)
Wo du aber Recht hast ist, dass ich das Effektlevel im Skript nicht berücksichtigt habe.
Das müssen die Skripter selbst wissen und ist automatisch kaum zu erkennen, bzw. lohnt sich eine Umsetzung kaum (zumal die ZenGin-External das auch nicht macht). Im Zweifelsfall führen sie die Funktion mit allen Stufen aus, welche sie verwenden/kennen. Ich bin nicht sicher ob das Feature für visuelle Effekte überhaupt verwendet wurde und wollte nur darauf hinweisen, dass (wenn beide Skriptinstanzen existieren) die stufenspezifische Instanz bevorzugt und als Name verwendet wird.
mud-freak
09.08.2017, 17:18
Da ist mir noch eine Verbesserungsmöglichkeit gekommen. Man kann auch Effekte stoppen nur anhand des Upsprungs- und/oder Zielobjekts. So lassen sich auch ganz einfach alle Effekte stoppen die etwas mit einem bestimmten Objekt zu tun haben:
var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
Wld_PlayEffect("SPELLFX_HEALSHRINE", Diego, Diego, 0, 0, 0, FALSE);
// Stop all effects involving Diego
Wld_StopEffect_Ext("", Diego, 0, 1); // Stops all effects with Diego as origin
Wld_StopEffect_Ext("", 0, Diego, 1); // Stops all effects with Diego as target
Code ist oben entsprechend aktualisiert.
Fisk2033
24.08.2017, 06:51
Falls das ScriptBin nochmal geupdated wird....
das muss unbedingt mit rein. :)
Was mich an Gothic 2 immer gestört hat ist, dass die Menu-Musik weiterläuft, wenn zu Spielstart das Intro startet. (In Gothic 1 ist das nicht der Fall). Ich habe eine Funktion geschrieben, die alle aktiven Sounds sofort stoppt (Musik ist nicht betroffen, die Menu-Musik ist ein "Sound", keine Musik). Man kann die Funktion also aus der Startup kurz vor starten des Intro-Videos aufrufen, um das Problem zu beheben und einem Intro vernünftig zu lauschen:
Beispiel:
// ------ World -------
FUNC VOID STARTUP_NewWorld()
{
// ------ StartUps der Unter-Parts ------
STARTUP_NewWorld_Part_Forest_01();
STARTUP_NewWorld_Part_Pass_To_OW_01();
// ------ INTRO - muss ganz am Ende der Startup stehen ------
Kapitel = 1; //Joly: Kann hier stehen bleiben!
stopAllSounds();
PlayVideo ("INTRO.BIK");
PlayVideo ("Addon_Title.BIK");
};
Auch wenn das Problem nicht bei Gothic 1 auftaucht, der Vollständigkeit halber mit Adressen für Gothic 1 und Gothic 2.
func void stopAllSounds() {
MEM_InitAll();
const int zsound_G1 = 9236044; //0x8CEE4C
const int zsound_G2 = 10072124; //0x99B03C
const int zCSndSys_MSS__RemoveAllActiveSounds_G1 = 5112224; //0x4E01A0
const int zCSndSys_MSS__RemoveAllActiveSounds_G2 = 5167008; //0x4ED7A0
var int zsoundPtr; zsoundPtr = MEMINT_SwitchG1G2(MEM_ReadInt(zsound_G1), MEM_ReadInt(zsound_G2));
const int call = 0;
if (CALL_Begin(call)) {
CALL__thiscall(_@(zsoundPtr), MEMINT_SwitchG1G2(zCSndSys_MSS__RemoveAllActiveSounds_G1,
zCSndSys_MSS__RemoveAllActiveSounds_G2));
call = CALL_End();
};
};
Wenn man diesen Befehl (einmal pro Session) mit false aufruft, regnet es nicht mehr durch Vobs hindurch:
func void rainThroughVobs(var int bool) {
OverrideMemoryProtection(6169210, 4);
if (!bool) {
// bool == false -> Es regnet nicht mehr durch
MEM_WriteByte(6169210, 224);
} else {
MEM_WriteByte(6169210, 226);
};
Der Dank geht an Showdown, ich hab das ganze nur nach Daedalus übersetzt.
Ebenfalls könnte man folgenden BugFix fürs MultiBook einbauen:
Kleiner, aber hässlicher Bug beim Multibook hier (http://forum.worldofplayers.de/forum/threads/790720-Scriptpaket-Zugriff-auf-ZenGine-Objekte?p=13659168&viewfull=1#post13659168). Wenn man das Buch mit Rechtsklick schließt statt mit Escape, dann spinnt alles rum....
habe die Funktion angepasst.
func int ZS_MultiPageBooks_Loop() { // Wichtig, damit eine Taste nicht mehrmals aufgerufen wird
AI_Wait (self, 0.1);
// Die nächste Seite aufrufen?
if (UpdatePage == TRUE)
{
MEM_CallByString(CurrentBook);
UpdatePage = FALSE;
};
// Tasten
var int LeftKey; LeftKey = MEM_KeyState(KEY_LEFTARROW);
var int RightKey; RightKey = MEM_KeyState(KEY_RIGHTARROW);
var int EscKey; EscKey = MEM_KeyState(KEY_ESCAPE);
var int MouseRightKey; MouseRightKey = MEM_KeyState(MOUSE_BUTTONRIGHT);
// Pfeil-Links gedrückt
if (LeftKey == KEY_PRESSED) {
// Ende des Buches noch nicht erreicht
if (CurrentPage > 0)
{
CurrentPage -= 2;
MEM_InsertKeyEvent (KEY_ESCAPE);
MEM_InsertKeyEvent (MOUSE_BUTTONRIGHT);
UpdatePage = TRUE;
return LOOP_CONTINUE;
}
// Ende des Buches erreicht, aber Loop ist eingeschaltet
else if (CurrentBookLoopable) {
CurrentPage = CurrentBookMaxPage - (CurrentBookMaxPage%2);
MEM_InsertKeyEvent (KEY_ESCAPE);
MEM_InsertKeyEvent (MOUSE_BUTTONRIGHT);
UpdatePage = TRUE;
return LOOP_CONTINUE;
};
};
// Pfeil-Rechts gedrückt
if (RightKey == KEY_PRESSED) {
// Ende des Buches noch nicht erreicht
if (CurrentPage < CurrentBookMaxPage-2)
{
CurrentPage += 2;
MEM_InsertKeyEvent (KEY_ESCAPE);
MEM_InsertKeyEvent (MOUSE_BUTTONRIGHT);
UpdatePage = TRUE;
return LOOP_CONTINUE;
}
// Ende des Buches erreicht, aber Loop ist eingeschaltet
else if (CurrentBookLoopable) {
CurrentPage = 0;
MEM_InsertKeyEvent (KEY_ESCAPE);
MEM_InsertKeyEvent (MOUSE_BUTTONRIGHT);
UpdatePage = TRUE;
return LOOP_CONTINUE;
};
};
// Wenn Escape gedrückt, Loop verlassen und Buch schließen
if (EscKey == KEY_PRESSED || MouseRightKey == KEY_PRESSED)
{
return LOOP_END;
};
};
Wer noch Seitenzahlen in dem Multibook haben will, hier sind paar funktionen zum automatischen generieren via nDoc_SetPagenumbers()
func void mDoc_SetPagenumbersLayout(var int DocID){ mDoc_SetMargins (DocID, LEFT_PAGE, 375, 453, 30, 20, 1); mDoc_SetMargins (DocID, RIGHT_PAGE, 160, 453, 30, 20, 1);};func void mDoc_CreatePageNumbers(var int DocID, var int Pagenumber){ if Pagenumber > 0 { mDoc_PrintLines (DocID, Pagenumber - 1, IntToString(Pagenumber)); mDoc_CreatePageNumbers(DocID, Pagenumber - 1); };};func void mDoc_SetPagenumbers(var int DocID, var int Pagenumbers){ mDoc_SetPagenumbersLayout(DocID); mDoc_CreatePageNumbers(DocID, Pagenumbers);};
Frank-95
30.08.2017, 23:06
Just added few LeGo functions to obtain coloured texts in views
Interface.d
func int Print_ColoredTextField(var int x, var int y, var string text, var string font, var int height, var int color)
{
var int cnt; cnt = STR_SplitCount(text, Print_LineSeperator);
var int i; i = 1;
var int ptr; ptr = Print_CreateTextPtrColored(STR_Split(text, Print_LineSeperator, 0), font, color);
var zCViewText txt; txt = _^(ptr);
txt.posx = x;
txt.posy = y;
var int list; list = List_Create(Ptr);
var int pos; pos = MEM_StackPos.position;
if (i >= cnt) {
return list;
};
ptr = Print_CreateTextPtrColored(STR_Split(text, Print_LineSeperator, i), font, color);
txt = _^(ptr);
txt.posx = x;
txt.posy = y+(height*i);
List_Add(list, ptr);
i+=1;
MEM_StackPos.position = pos;
};
View.d
func void ViewPtr_AddTextColored(var int ptr, var int x, var int y, var string text, var string font, var int color)
{
var zCView v; v = _^(ptr);
var int field; field = Print_ColoredTextField(x, y, text, font, Print_ToVirtual(Print_GetFontHeight(font), v.pposy+v.psizey), color);
if(v.textLines_next)
{
List_Concat(v.textLines_next, field);
}
else
{
v.textLines_next = field;
};
};
func void View_AddTextColored(var int hndl, var int x, var int y, var string text, var string font, var int color) {
ViewPtr_AddTextColored(getPtr(hndl), x, y, text, font, color);
};
More aivars.
const int Aivars_Max = 50;
const int Aivars_Data = 99;
class CAivar
{
var int Data[Aivars_Max];
};
INSTANCE CAivar@(CAivar);
func void Npc_SetAivar(var c_npc slf, var int offset , var int value)
{
if(!Hlp_IsValidHandle(slf.aivar[Aivars_Data]))
{
slf.aivar[Aivars_Data] = new(CAivar@);
};
if(!Hlp_IsValidHandle(slf.aivar[Aivars_Data]))
{
return;
};
var CAivar a; a = get(slf.aivar[Aivars_Data]);
MEM_WriteStatArr(a.data, offset, value);
};
func int Npc_GetAivar(var c_npc slf, var int offset)
{
if(Hlp_IsValidHandle(slf.aivar[Aivars_Data]))
{
var CAivar a; a = get(slf.aivar[Aivars_Data]);
return MEM_ReadStatArr(a.data, offset);
};
};
func void Npc_ClearAivars(var c_npc slf)
{
if(Hlp_IsValidHandle(slf.aivar[Aivars_Data]))
{
var int i; i = 0;
repeat(i, Aivars_Max);
Npc_SetAivar(slf, i, 0);
end;
};
};
Ich find die Idee gut. Vielleicht könntest du im Einleitungspost noch eine Liste der enthaltenen Skripte aufnehmen, damit man direkt einen Überblick hat.
Wäre nicht verkehrt, wenn diese Skript-Collection eine Art Standard wird.
Das SearchWaypointByName ist etwas "veraltet". Hier hatte ich eine simplere Variante vorgeschlagen. Als Funktion könnte man das dann so aufnehmen (Code ungetestet):
...
Merke ich mir, aber nicht bevor ich es nicht getestet habe :p
Because i finished work in Ikarus and now i'm using AST(Agama Script Tools) i decided to share my scripts :)
Some Engine scripts:
...
Fade Screen
...
This is all at this time. (I couldn't found my next scripts :/ )
Regards Siemekk.
Some of those miscellaneous functions sound good!
A fadescreen function should definitely be included, but your implementation is somewhat clunky and produces different results on different systems (because you don't account for FPS). I made a really short one (like below 20 lines) a while back which is easier to configure, I will add it once I have tested it.
Evtl. passt es ja in die Sammlung. :)
CinemaScope ist eine nette Idee, aber der Code ist ein komplettes Mysterium (ständig wird an irgendwelche undurchsichtigen Pointer geschrieben). Etwas vorzeigbares wäre sicherlich besser! Solange es funktioniert aber ein Anfang.
Das Anti-Cheat-Script ist auch gut, aber ich würde es eigentlich ungern so verfügbar machen - im Endeffekt stellt sich sowieso meistens raus, dass es ein Fehler war, die Cheats auszuschalten :p Wer selber so ein Script schreibt oder danach sucht hat (hoffentlich) etwas mehr darüber nachgedacht, wann es Sinn macht.
Das Dodge-Script ist ebenfalls gut, aber die Prints nehme ich mal lieber raus :P
[Wld_StopEffectEx]
Ist drin :A
Falls das ScriptBin nochmal geupdated wird....
das muss unbedingt mit rein. :)
Ebenfalls könnte man folgenden BugFix fürs MultiBook einbauen:
Natürlich wird es! Aber ein alter Mann ist nunmal kein D-Zug.
Hab die beiden Funktionen eingebaut und das MultiPage-Book Script angepasst.
Just added few LeGo functions to obtain coloured texts in views
Sounds nice but it looks like they'd belong in LeGo (where I will add them later :p).
More aivars.
...
LeGo already does this better with 'Talents'.
Cinemascope was wrote by OrcWarriorPL many years ago. I just ordered it, because script was practically illegible. Bottom part of script (strange formula) is for moving choice box bellow black bar when resolution is set at 1024x768 or lower. It's not necessary, because hardly anyone plays at these resolutions yet :P I think it may be written simplier, but I'm lazy xD
func void CinemaScope()
{
if (InfoManager_HasFinished () && alpha == 0)
{
return;
};
var int ptr;
var int tmp;
var int alpha;
MEM_GAME.array_view_visible[3] = true;
MEM_GAME.array_view_enabled[3] = true;
MEM_Camera.cinemaScopeEnabled = true;
MEM_Camera.cinemaScopeColor = RGBA (0, 0, 0, alpha);
if (InfoManager_HasFinished())
{
if (alpha >= 5)
{
alpha -= 10;
};
if (alpha == 5)
{
alpha = 0;
};
}
else
{
if (alpha <= 250)
{
alpha += 10;
};
if (alpha == 250)
{
alpha = 255;
};
//conversation box
ptr = MEM_GAME.array_view[1];
MEM_WriteInt (ptr+64, 0); //remove texture
MEM_WriteInt (ptr+72, 1); //move higher
//choice box
ptr = MEM_GAME.array_view[3];
MEM_WriteInt (ptr+4, 0); //disable zoom effect
ptr = MEM_ReadInt (MEMINT_oCInformationManager_Address+28);
MEM_WriteInt (ptr+56, Print_Screen[PS_X]/6); //pos x
MEM_WriteInt (ptr+96, 0); //remove texture
ptr = ptr+60;
//Choice box position fix by OrcWarrior:
if (Print_Screen[PS_Y] <= 768)
{
tmp = Print_Screen[PS_Y] * 21 / 2 - ((Print_Screen[PS_Y] * 2 / 24) % 1);
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, Print_Screen[PS_Y] - tmp);
}
else
{
tmp = Print_Screen[PS_Y] * 113 / 128;
tmp = tmp - (tmp % 1);
if (Print_Screen[PS_Y] - tmp > 120)
{
tmp = tmp + (Print_Screen[PS_Y] - tmp - 120) / 2;
tmp = tmp - (tmp % 1);
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, 120);
}
else
{
MEM_WriteInt (ptr, tmp);
ptr = ptr+8;
MEM_WriteInt (ptr, Print_Screen[PS_Y] - tmp);
};
};
};
};
Btw. Some time ago I wrote script of temporary regeneration for my personal use, as alternative of Buffs. You can set delay and ticks. Usage is very easy. Just call fuction RE_CastEffects per frame. Example:
RE_AddEffect (self, ATR_HITPOINTS, 120, 8, 1000);
Code:
class RegenerationEffect
{
var int instance;
var int attribute;
var int value;
var int cycles;
var int cycles_current;
var int delay;
var int time;
};
instance RegenerationEffect@ (RegenerationEffect);
func void RE_AddEffect (var c_npc slf, var int attribute, var int value, var int cycles, var int delayMS)
{
var int hndl; hndl = new (RegenerationEffect@);
var RegenerationEffect effect; effect = get (hndl);
effect.instance = Hlp_GetInstanceID (slf);
effect.attribute = attribute;
effect.value = value;
effect.cycles = cycles;
effect.cycles_current = cycles;
effect.delay = delayMS;
effect.time = MEM_Timer.totalTime;
};
func void RE_CastEffect (var int hndl)
{
var RegenerationEffect effect; effect = get (hndl);
if (MEM_Timer.totalTime - effect.time < effect.delay || MEM_Game.singleStep == true)
{
return;
};
if (effect.cycles_current > 0)
{
var c_npc slf; slf = Hlp_GetNpc (effect.instance);
var int value; value = roundf (divf (mkf (effect.value), mkf (effect.cycles)));
effect.cycles_current -= 1;
effect.time = MEM_Timer.totalTime;
if (effect.cycles_current == 0 && effect.value - (value*effect.cycles) > 0)
{
value += effect.value - (value*effect.cycles);
};
if (slf.attribute[ATR_HITPOINTS] > 0)
{
Npc_ChangeAttribute (slf, effect.attribute, value);
};
};
if (effect.cycles_current == 0 || slf.attribute[ATR_HITPOINTS] == 0)
{
delete (hndl);
};
};
func void RE_CastEffects ()
{
foreachHndl (RegenerationEffect@, RE_CastEffect);
};
About buff's - i "rewrited" buff's package from LeGo, to similary AST.
const int BUFF_NORMAL = 0;
const int BUFF_BAD = 1;
const int BUFF_GOOD = 2;
const int Buff_TextureSize = 64;
class CBuff
{
var int Type;
var int ID;
var int EndTime;
var int OldTime;
var int pSelf; // oCNpc*
var int OnApply;
var int OnTick;
var int OnRemove;
var int delay; //
var int Cycles;
};
var int m_arrBuffs; //zCArray<CBuff*>
var int Current_Buff; //CBuff*
func int SAVE_GetFuncID(var func f)
{
var int i; i = MEM_GetUseInstance();
var int res; res = MEM_GetFuncID(f);
MEM_SetUseInstance(i);
return res;
};
func void GetBuffsByNpc(var int Array_Ptr, var c_npc slf)
{
var int InstID; InstID = Hlp_GetInstanceID(slf);
if(!Array_Ptr || !m_arrBuffs)
{
return;
};
var int size; size = MEM_ArraySize(m_arrBuffs);
if(size)
{
var int i; i = 0;
repeat(i, size);
var int ptr; ptr = MEM_ArrayRead(m_arrBuffs, i);
if(Hlp_IsValidHandle(ptr))
{
var CBuff bf; bf = get(ptr);
if(bf.pSelf == InstID)
{
MEM_ArrayInsert(Array_Ptr, ptr);
};
};
end;
};
};
func int Npc_HasBuff(var c_npc slf, var int ID)
{
var int arr; arr = MEM_ArrayCreate();
GetBuffsByNpc(arr, slf);
if(arr)
{
var int size; size = MEM_ArraySize(arr);
if(size)
{
var int i; i = 0;
repeat(i, size);
var int ptr; ptr = MEM_ArrayRead(arr, i);
if(Hlp_IsValidHandle(ptr))
{
var CBuff bf; bf = get(ptr);
if(bf.ID == ID)
{
MEM_ArrayFree(arr);
return true;
};
};
end;
};
};
MEM_ArrayFree(arr);
return false;
};
func void Buff_Apply(var c_npc slf, var int _bf) //Operator new
{
var int InstID; InstID = Hlp_GetInstanceID(slf);
var int ptr; ptr = new(_bf);
var CBuff buff; buff = get(ptr);
buff.pSelf = InstID;
buff.EndTime = Timer();
buff.OldTime = Timer();
if(!m_arrBuffs)
{
m_arrBuffs = MEM_ArrayCreate();
};
if(!m_arrBuffs)
{
return;
};
MEM_ArrayInsert(m_arrBuffs, ptr);
//OnApply
if(buff.OnApply > 0)
{
var c_npc n; n = Hlp_GetNpc(InstID);
MEM_PushInstParam(n);
MEM_CallByID(buff.OnApply);
};
};
func void Buff_ApplyUnique(var c_npc slf, var int _bf)
{
var int hlp; hlp = new(_bf);
var CBuff b; b = get(hlp);
if(!Npc_HasBuff(slf, b.ID))
{
Buff_Apply(slf, _bf);
};
};
func void Buff_Delete(var int ptr)
{
if(m_arrBuffs)
{
var CBuff b; b = get(ptr);
if(b.OnRemove > 0)
{
var c_npc slf; slf = Hlp_GetNpc(b.pSelf);
MEM_PushInstParam(slf);
MEM_CallByID(b.OnRemove);
};
MEM_ArrayRemoveValue(m_arrBuffs, ptr);
if(MEM_ArraySize(m_arrBuffs) <= 0)
{
MEM_ArrayClear(m_arrBuffs);
m_arrBuffs = 0;
};
};
};
func void Buff_End()
{
Buff_Delete(Current_Buff);
};
func void Buff_CallFunc(var int ptr)
{
var CBuff b; b = get(ptr);
if(b.OnTick > 0)
{
var c_npc slf; slf = Hlp_GetNpc(b.pSelf);
MEM_PushInstParam(slf);
MEM_CallByID(b.OnTick);
};
};
func void Buff_Do(var int nr)
{
if(!m_arrBuffs)
{
return;
};
var int ptr; ptr = MEM_ArrayRead(m_arrBuffs, nr);
if(!Hlp_IsValidHandle(ptr))
{
return;
};
var int bPause;
var int NowTime;
var CBuff buff;
bPause = MEM_GAME.singleStep; //BOOL
Current_Buff = ptr;
NowTime = Timer();
buff = get(ptr);
if(!bPause)
{
if(NowTime >= buff.EndTime)
{
buff.EndTime += buff.delay;
Buff_CallFunc(ptr);
if(buff.Cycles != -1)
{
buff.Cycles -= 1;
if(buff.Cycles < 1 )
{
Buff_Delete(ptr);
return;
};
};
};
}
else
{
buff.EndTime += Timer() - buff.OldTime;
};
buff.OldTime = NowTime;
};
func void Buffs_Attach()
{
if(m_arrBuffs)
{
if(MEM_ArraySize(m_arrBuffs))
{
var int i; i = 0;
var int pos; pos = MEM_StackPos.position;
if(i < MEM_ArraySize(m_arrBuffs)) //for(int i = 0; i < m_arrBuffs.GetSize(); i++);
{
Buff_Do(i);
i += 1;
MEM_StackPos.position = pos;
};
};
};
};
func void Buff_RemoveByType(var c_npc slf, var int type)
{
var int arr; arr = MEM_ArrayCreate();
GetBuffsByNpc(arr, slf);
if(arr)
{
var int size; size = MEM_ArraySize(arr);
if(size)
{
var int i; i = 0;
repeat(i, size);
var int ptr; ptr = MEM_ArrayRead(arr, i);
if(Hlp_IsValidHandle(ptr))
{
var CBuff b; b = get(ptr);
if(type == b.Type)
{
Buff_Delete(ptr);
};
};
end;
};
};
MEM_ArrayFree(arr);
};
func void Buff_RemoveByID(var c_npc slf, var int id)
{
var int arr; arr = MEM_ArrayCreate();
GetBuffsByNpc(arr, slf);
if(arr)
{
var int size; size = MEM_ArraySize(arr);
if(size)
{
var int i; i = 0;
repeat(i, size);
var int ptr; ptr = MEM_ArrayRead(arr, i);
if(Hlp_IsValidHandle(ptr))
{
var CBuff b; b = get(ptr);
if(id == b.ID)
{
Buff_Delete(ptr);
};
};
end;
};
};
MEM_ArrayFree(arr);
};
func void Buff_SetCycles(var c_npc slf, var int ID, var int cycles)
{
var int arr; arr = MEM_ArrayCreate();
GetBuffsByNpc(arr, slf);
if(arr)
{
var int size; size = MEM_ArraySize(arr);
if(size)
{
var int i; i = 0;
repeat(i, size);
var int ptr; ptr = MEM_ArrayRead(arr, i);
if(Hlp_IsValidHandle(ptr))
{
var CBuff b; b = get(ptr);
if(id == b.ID)
{
b.cycles = cycles;
};
};
end;
};
};
MEM_ArrayFree(arr);
};
func int Buff_CreateView(var int x, var int y, var string texture)
{
var int ptr; ptr = View_CreateCenter(x, y,
Print_ToVirtual(Buff_TextureSize, PS_X),
Print_ToVirtual(Buff_TextureSize, PS_Y));
View_SetTexture(ptr, texture);
View_Open(ptr);
return ptr;
};
Example:
const int ID_DeadlyPoison = 1;
INSTANCE DeadlyPoison(CBuff)
{
Type = BUFF_BAD;
ID = ID_DeadlyPoison;
OnApply = SAVE_GetFuncID(Poison_Apply);
OnTick = SAVE_GetFuncID(Poison_Tick);
OnRemove = SAVE_GetFuncID(Poison_Remove);
delay = 500; //500 * 20 = 10000 = 10s
cycles = 20;
};
func void Poison_Apply(var c_npc slf)
{
PrintS(slf.name);
PrintS("Poison Apply");
};
func void Poison_Tick(var c_npc slf)
{
Npc_ChangeAttribute(slf, ATR_HITPOINTS, -5);
if(Npc_IsDead(slf))
{
Buff_End();
};
};
func void Poison_End(var c_npc slf)
{
PrintS("End");
};
Functions in this "Package":
Buff_Apply (c_npc slf, buff instance);
Buff_ApplyUnique (c_npc slf, buff instance);
Npc_HasBuff (c_npc slf, int Buff_ID);
Buff_RemoveByType (c_npc slf, int Buff_type(BAD|NORMAL|GOOD));
Buff_RemoveByID (c_npc slf, int Buff_ID);
Buff_SetCycles (c_npc slf, int Buff_ID, int cycles);
Buff_End(); //End current buff - Use in Buff Tick function!
To fully work, we must add in Init_Global(); FF_ApplyOnce(Buffs_Attach); after init lego.
Cinemascope was wrote by OrcWarriorPL many years ago. I just ordered it, because script was practically illegible. Bottom part of script (strange formula) is for moving choice box bellow black bar when resolution is set at 1024x768 or lower. It's not necessary, because hardly anyone plays at these resolutions yet :P I think it may be written simplier, but I'm lazy xD
That looks a lot better! I still have to ask... what's he computing with the %1 in the formula at the bottom? I had asked the last time this script showed up but no one was able to answer me :p
Btw. Some time ago I wrote script of temporary regeneration for my personal use, as alternative of Buffs. You can set delay and ticks. Usage is very easy. Just call fuction RE_CastEffects per frame. Example:
RE_AddEffect (self, ATR_HITPOINTS, 120, 8, 1000);
Code:
class RegenerationEffect
{
var int instance;
var int attribute;
var int value;
var int cycles;
var int cycles_current;
var int delay;
var int time;
};
instance RegenerationEffect@ (RegenerationEffect);
func void RE_AddEffect (var c_npc slf, var int attribute, var int value, var int cycles, var int delayMS)
{
var int hndl; hndl = new (RegenerationEffect@);
var RegenerationEffect effect; effect = get (hndl);
effect.instance = Hlp_GetInstanceID (slf);
effect.attribute = attribute;
effect.value = value;
effect.cycles = cycles;
effect.cycles_current = cycles;
effect.delay = delayMS;
effect.time = MEM_Timer.totalTime;
};
func void RE_CastEffect (var int hndl)
{
var RegenerationEffect effect; effect = get (hndl);
if (MEM_Timer.totalTime - effect.time < effect.delay || MEM_Game.singleStep == true)
{
return;
};
if (effect.cycles_current > 0)
{
var c_npc slf; slf = Hlp_GetNpc (effect.instance);
var int value; value = roundf (divf (mkf (effect.value), mkf (effect.cycles)));
effect.cycles_current -= 1;
effect.time = MEM_Timer.totalTime;
if (effect.cycles_current == 0 && effect.value - (value*effect.cycles) > 0)
{
value += effect.value - (value*effect.cycles);
};
if (slf.attribute[ATR_HITPOINTS] > 0)
{
Npc_ChangeAttribute (slf, effect.attribute, value);
};
};
if (effect.cycles_current == 0 || slf.attribute[ATR_HITPOINTS] == 0)
{
delete (hndl);
};
};
func void RE_CastEffects ()
{
foreachHndl (RegenerationEffect@, RE_CastEffect);
};
The script looks good but I don't think there's any use for it given that LeGo implements its own buffs - I think it's only marginally more work to implement a regen effect using them.
I think your script may have problems when such an effect is applied to monsters (e.g. more than one NPC of the given instance). I think the only and thus best way to save NPCs is Npc_GetID()/Npc_FindByID() (http://lego.worldofplayers.de/?Talents#Npc_GetID).
@Siemekk: I don't mind you rewriting stuff (although I have to ask why: It looks like you're still using LeGo functions in there anyway?), but I really see nothing to gain here. Why publish a script that is almost a literal copy?
I don't know how's look's buffs in LeGo - last time when i used this package i had crash in buff_apply. So i rewrited system - my script save all values to array. It's working, but if buff's from LeGo work, for others this script is no sense.
I don't know how's look's buffs in LeGo - last time when i used this package i had crash in buff_apply. So i rewrited system - my script save all values to array. It's working, but if buff's from LeGo work, for others this script is no sense.
Oh, that is understandable! For future reference: If you find crashes (or other bugs) when using LeGo, make a small post in the LeGo thread! I'd love to fix them.
That looks a lot better! I still have to ask... what's he computing with the %1 in the formula at the bottom? I had asked the last time this script showed up but no one was able to answer me :p
I don't know. This is also strange to me. This question should be addressed to OrcWarrior, but it's hard to get in touch with him. Here is original script: https://github.com/orcwarrior/Czas_Zaplaty/blob/master/Content/_Intern/I_Functions.d#L192
Be careful. Eyes can hurt from reading this xD
The script looks good but I don't think there's any use for it given that LeGo implements its own buffs - I think it's only marginally more work to implement a regen effect using them.
I think your script may have problems when such an effect is applied to monsters (e.g. more than one NPC of the given instance). I think the only and thus best way to save NPCs is Npc_GetID()/Npc_FindByID() (http://lego.worldofplayers.de/?Talents#Npc_GetID).
Thanks. I think this will help. I did not think about monsters, because I wrote this script for food and potions.
Name:
Wld_StopEffect_Ext
Voraussetzungen:
Ikarus 1.2
Kompatibilität:
Gothic 1 und Gothic 2
Beschreibung:
Schon immer hat mich an Wld_PlayEffect genervt, dass man Effekte nicht verlässlich stoppen kann. Wld_StopEffect beendet nur den erst gefundenen Effekt mit dem Namen, was häufig dazu geführt hat, dass Effekte in der Welt hängen geblieben sind. Ausserdem gibt es in Gothic 1 gar kein Wld_StopEffect, wodurch man gestartete Effekte gar nicht erst beenden konnte.
Ich möchte hier Wld_StopEffect_Ext vorstellen. Ein Skript, mit dem man genau bestimmen kann welcher Effekt beendet werden soll (und ob nur einer oder alle übereinstimmenden). Das ganze kompatibel mit Gothic 2 und Gothic 1. Die Funktion tut haargenau das, was Wld_StopEffect in der Engine tut, nur mit erweiterter Funktionalität.
Genauer:
Es gibt drei zusätzliche Funktionsparamenter und einen Rückgabewert:
func int Wld_StopEffect_Ext(string effect, int origin, int target, int stopAll)
Parameter zwei und drei sind Ursprungsobjekt und Zielobjekt (so wie man das von Wld_PlayEffect kennt). Übergeben wird jeweils eine Instanz. So kann mal sowohl NPCs und Items, als auch jegliche andere Instanz übergeben, die von der Klasse zCVob erbt. Diese Parameter können auch null sein, wenn sie nicht festgelegt werden sollen.
Der vierte Parameter lässt entscheiden, ob nur der erst gefundene Effekt gestoppt werden soll (0), so wie bei Wld_StopEffect, oder alle Effekte gestoppt werden sollen, die mit den Voraussetzungen übereinstimmen (1).
Der Rückgabewert gibt an, wie viele Effekte gestoppt wurden. Null bedeutet, dass entweder keine passenden Effekte gefunden wurden, oder die Argumente fehlerhaft waren. Mit diesem Rückgabewert hat man aus der Skripten nun auch mehr Kontrolle über was geschieht.
Beispiel:
var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops exactly and only the first effect
// Wld_StopEffect for Gothic 1
func void Wld_StopEffect(var string effect) {
Wld_StopEffect_Ext(effect, 0, 0, 0);
};
Aufgrund der vielen Speicheradressen für sowohl Gothic 1, als auch Gothic 2, wirkt das Skript enorm lang, ist aber eigentlich ziemlich kurz und simpel.
Ich habe das Skript schon in die Form der übrigen Skripte aus dem ScriptBin gebracht (Kopfzeile im Skript), kann also direkt so übernommen werden.
// Extended functionality for Wld_StopEffect()
// See https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin?p=25548652&viewfull=1#post25548652
// Made by mud-freak (@szapp) 2017-08-05 (modified 2017-08-09)
//
// Compatible with Gothic 1 and Gothic 2
// Requirements: Ikarus 1.2
/*
* Check the inheritance of a zCObject against a zCClassDef. Emulating zCObject::CheckInheritance() at 0x476E30 in G2.
* This function is used in Wld_StopEffect_Ext().
*/
func int objCheckInheritance(var int objPtr, var int classDef) {
if (!objPtr) || (!classDef) {
return 0;
};
const int zCClassDef_baseClassDef_offset = 60; //0x003C
// Iterate over base classes
var int curClassDef; curClassDef = MEM_GetClassDef(objPtr);
while((curClassDef) && (curClassDef != classDef));
curClassDef = MEM_ReadInt(curClassDef+zCClassDef_baseClassDef_offset);
end;
return (curClassDef == classDef);
};
/*
* Emulate the Gothic 2 external function Wld_StopEffect(), with additional settings: Usually it is not clear which
* effect will be stopped, leading to effects getting "stuck". Here, Wld_StopEffect is extended with additional checks
* for origin and/or target vob and whether to stop all matching FX or only the first one found (like in Wld_StopEffect)
* The function returns the number of stopped effects, or zero if none was found or an error occurred.
* Compatible with Gothic 1 and Gothic 2. This means there is finally the possibility to stop effects in Gothic 1!
*
* Examples:
* var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
* Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops only the first effect
*
* Calling Wld_StopEffect("EFFECT_1") corresponds to Wld_StopEffect_Ext("EFFECT_1", 0, 0, 0).
*
* Big parts if this function are taken from Wld_StopEffect. Gothic 2: sub_006E32B0() 0x6E32B0
*/
func int Wld_StopEffect_Ext(var string effectName, var int originInst, var int targetInst, var int all) {
// Gothic 1 addresses and offsets
const int zCVob__classDef_G1 = 9269976; //0x8D72D8
const int zCWorld__SearchVobListByClass_G1 = 6249792; //0x5F5D40
const int oCVisualFX__classDef_G1 = 8822272; //0x869E00
const int oCVisualFX__Stop_G1 = 4766512; //0x48BB30
const int oCVisualFX_originVob_offset_G1 = 1112; //0x0458
const int oCVisualFX_targetVob_offset_G1 = 1120; //0x0460
const int oCVisualFX_instanceName_offset_G1 = 1140; //0x0474
// Gothic 2 addresses and offsets
const int zCVob__classDef_G2 = 10106072; //0x9A34D8
const int zCWorld__SearchVobListByClass_G2 = 6439504; //0x624250
const int oCVisualFX__classDef_G2 = 9234008; //0x8CE658
const int oCVisualFX__Stop_G2 = 4799456; //0x493BE0
const int oCVisualFX_originVob_offset_G2 = 1192; //0x04A8
const int oCVisualFX_targetVob_offset_G2 = 1200; //0x04B0
const int oCVisualFX_instanceName_offset_G2 = 1220; //0x04C4
var int zCVob__classDef;
var int zCWorld__SearchVobListByClass;
var int oCVisualFX__classDef;
var int oCVisualFX__Stop;
var int oCVisualFX_originVob_offset;
var int oCVisualFX_targetVob_offset;
var int oCVisualFX_instanceName_offset;
zCVob__classDef = MEMINT_SwitchG1G2(zCVob__classDef_G1,
zCVob__classDef_G2);
zCWorld__SearchVobListByClass = MEMINT_SwitchG1G2(zCWorld__SearchVobListByClass_G1,
zCWorld__SearchVobListByClass_G2);
oCVisualFX__classDef = MEMINT_SwitchG1G2(oCVisualFX__classDef_G1,
oCVisualFX__classDef_G2);
oCVisualFX__Stop = MEMINT_SwitchG1G2(oCVisualFX__Stop_G1,
oCVisualFX__Stop_G2);
oCVisualFX_originVob_offset = MEMINT_SwitchG1G2(oCVisualFX_originVob_offset_G1,
oCVisualFX_originVob_offset_G2);
oCVisualFX_targetVob_offset = MEMINT_SwitchG1G2(oCVisualFX_targetVob_offset_G1,
oCVisualFX_targetVob_offset_G2);
oCVisualFX_instanceName_offset = MEMINT_SwitchG1G2(oCVisualFX_instanceName_offset_G1,
oCVisualFX_instanceName_offset_G2);
var int worldPtr; worldPtr = _at(MEM_World);
if (!worldPtr) {
return 0;
};
// Create array from all oCVisualFX vobs
var int vobArrayPtr; vobArrayPtr = MEM_ArrayCreate();
var zCArray vobArray; vobArray = _hat(vobArrayPtr);
const int call = 0; var int zero;
if (CALL_Begin(call)) {
CALL_PtrParam(_at(zero)); // Vob tree (0 == globalVobTree)
CALL_PtrParam(_at(vobArrayPtr)); // Array to store found vobs in
CALL_PtrParam(_at(oCVisualFX__classDef)); // Class definition
CALL__thiscall(_at(worldPtr), zCWorld__SearchVobListByClass);
call = CALL_End();
};
if (!vobArray.numInArray) {
MEM_ArrayFree(vobArrayPtr);
return 0;
};
effectName = STR_Upper(effectName);
var zCPar_Symbol symb;
// Validate origin vob instance
if (originInst) {
// Get pointer from instance symbol
if (originInst > 0) && (originInst < MEM_Parser.symtab_table_numInArray) {
symb = _hat(MEM_ReadIntArray(contentSymbolTableAddress, originInst));
originInst = symb.offset;
} else {
originInst = 0;
};
if (!objCheckInheritance(originInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Origin is not a valid vob");
return 0;
};
};
// Validate target vob instance
if (targetInst) {
// Get pointer from instance symbol
if (targetInst > 0) && (targetInst < MEM_Parser.symtab_table_numInArray) {
symb = _hat(MEM_ReadIntArray(contentSymbolTableAddress, targetInst));
targetInst = symb.offset;
} else {
targetInst = 0;
};
if (!objCheckInheritance(targetInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Target is not a valid vob");
return 0;
};
};
// Search all vobs for the matching name
var int stopped; stopped = 0; // Number of FX stopped
repeat(i, vobArray.numInArray); var int i;
var int vobPtr; vobPtr = MEM_ArrayRead(vobArrayPtr, i);
if (!vobPtr) {
continue;
};
// Search for FX with matching name
if (!Hlp_StrCmp(effectName, "")) {
var string effectInst; effectInst = MEM_ReadString(vobPtr+oCVisualFX_instanceName_offset);
if (!Hlp_StrCmp(effectInst, effectName)) {
continue;
};
};
// Search for a specific origin vob
if (originInst) {
var int originVob; originVob = MEM_ReadInt(vobPtr+oCVisualFX_originVob_offset);
if (originVob != originInst) {
continue;
};
};
// Search for a specific target vob
if (targetInst) {
var int targetVob; targetVob = MEM_ReadInt(vobPtr+oCVisualFX_targetVob_offset);
if (targetVob != targetInst) {
continue;
};
};
// Stop the oCVisualFX
const int call2 = 0; const int one = 1;
if (CALL_Begin(call2)) {
CALL_PtrParam(_at(one));
CALL__thiscall(_at(vobPtr), oCVisualFX__Stop);
call2 = CALL_End();
};
stopped += 1;
if (!all) {
break;
};
end;
MEM_ArrayFree(vobArrayPtr);
return stopped;
};
Was haltet ihr eigentlich davon, diesen Thread mal anzupinnen?
Was hat es mit dem _at auf sich? Das wird bei mir nicht gefunden und ich weiß auch nicht, wo es herkommen müsste.
_at() und _hat() sind _@() beziehungsweise _^(). Ich nehme an, da ist irgendwas beim schön formatieren kaputt gegangen? Vielleicht mag mud-freak auch einfach die Symbole nicht, ich hab gehört die Schweizer benutzen ein anderes Tastaturlayout als das deutsche.
Die Datei war im ScriptBin nicht in die zugehörige .src eingetragen, daher ist mir das nicht aufgefallen. Aktualisierte .src und Wld_StopEffect.d (die sich parsen lässt) sind jetzt auf dem SVN.
Danke, Leona, werd's morgen dann gleich testen.
Milky-Way
19.01.2018, 05:41
Name:
Wld_StopEffect_Ext
Voraussetzungen:
Ikarus 1.2
Kompatibilität:
Gothic 1 und Gothic 2
Beschreibung:
Schon immer hat mich an Wld_PlayEffect genervt, dass man Effekte nicht verlässlich stoppen kann. Wld_StopEffect beendet nur den erst gefundenen Effekt mit dem Namen, was häufig dazu geführt hat, dass Effekte in der Welt hängen geblieben sind. Ausserdem gibt es in Gothic 1 gar kein Wld_StopEffect, wodurch man gestartete Effekte gar nicht erst beenden konnte.
Ich möchte hier Wld_StopEffect_Ext vorstellen. Ein Skript, mit dem man genau bestimmen kann welcher Effekt beendet werden soll (und ob nur einer oder alle übereinstimmenden). Das ganze kompatibel mit Gothic 2 und Gothic 1. Die Funktion tut haargenau das, was Wld_StopEffect in der Engine tut, nur mit erweiterter Funktionalität.
Genauer:
Es gibt drei zusätzliche Funktionsparamenter und einen Rückgabewert:
func int Wld_StopEffect_Ext(string effect, int origin, int target, int stopAll)
Parameter zwei und drei sind Ursprungsobjekt und Zielobjekt (so wie man das von Wld_PlayEffect kennt). Übergeben wird jeweils eine Instanz. So kann mal sowohl NPCs und Items, als auch jegliche andere Instanz übergeben, die von der Klasse zCVob erbt. Diese Parameter können auch null sein, wenn sie nicht festgelegt werden sollen.
Der vierte Parameter lässt entscheiden, ob nur der erst gefundene Effekt gestoppt werden soll (0), so wie bei Wld_StopEffect, oder alle Effekte gestoppt werden sollen, die mit den Voraussetzungen übereinstimmen (1).
Der Rückgabewert gibt an, wie viele Effekte gestoppt wurden. Null bedeutet, dass entweder keine passenden Effekte gefunden wurden, oder die Argumente fehlerhaft waren. Mit diesem Rückgabewert hat man aus der Skripten nun auch mehr Kontrolle über was geschieht.
Beispiel:
var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops exactly and only the first effect
// Wld_StopEffect for Gothic 1
func void Wld_StopEffect(var string effect) {
Wld_StopEffect_Ext(effect, 0, 0, 0);
};
Aufgrund der vielen Speicheradressen für sowohl Gothic 1, als auch Gothic 2, wirkt das Skript enorm lang, ist aber eigentlich ziemlich kurz und simpel.
Ich habe das Skript schon in die Form der übrigen Skripte aus dem ScriptBin gebracht (Kopfzeile im Skript), kann also direkt so übernommen werden.
// Extended functionality for Wld_StopEffect()
// See https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin?p=25548652&viewfull=1#post25548652
// Made by mud-freak (@szapp) 2017-08-05 (modified 2017-08-09)
//
// Compatible with Gothic 1 and Gothic 2
// Requirements: Ikarus 1.2
/*
* Check the inheritance of a zCObject against a zCClassDef. Emulating zCObject::CheckInheritance() at 0x476E30 in G2.
* This function is used in Wld_StopEffect_Ext().
*/
func int objCheckInheritance(var int objPtr, var int classDef) {
if (!objPtr) || (!classDef) {
return 0;
};
const int zCClassDef_baseClassDef_offset = 60; //0x003C
// Iterate over base classes
var int curClassDef; curClassDef = MEM_GetClassDef(objPtr);
while((curClassDef) && (curClassDef != classDef));
curClassDef = MEM_ReadInt(curClassDef+zCClassDef_baseClassDef_offset);
end;
return (curClassDef == classDef);
};
/*
* Emulate the Gothic 2 external function Wld_StopEffect(), with additional settings: Usually it is not clear which
* effect will be stopped, leading to effects getting "stuck". Here, Wld_StopEffect is extended with additional checks
* for origin and/or target vob and whether to stop all matching FX or only the first one found (like in Wld_StopEffect)
* The function returns the number of stopped effects, or zero if none was found or an error occurred.
* Compatible with Gothic 1 and Gothic 2. This means there is finally the possibility to stop effects in Gothic 1!
*
* Examples:
* var C_NPC Diego; Diego = Hlp_GetNpc(PC_ThiefOW);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, Diego, 2, 150, DAM_FIRE, TRUE);
* Wld_PlayEffect("SPELLFX_SLEEPER_FIREBALL", self, hero, 2, 150, DAM_FIRE, TRUE);
* Wld_StopEffect_Ext("SPELLFX_SLEEPER_FIREBALL", 0, Diego, 0); // Stops only the first effect
*
* Calling Wld_StopEffect("EFFECT_1") corresponds to Wld_StopEffect_Ext("EFFECT_1", 0, 0, 0).
*
* Big parts if this function are taken from Wld_StopEffect. Gothic 2: sub_006E32B0() 0x6E32B0
*/
func int Wld_StopEffect_Ext(var string effectName, var int originInst, var int targetInst, var int all) {
// Gothic 1 addresses and offsets
const int zCVob__classDef_G1 = 9269976; //0x8D72D8
const int zCWorld__SearchVobListByClass_G1 = 6249792; //0x5F5D40
const int oCVisualFX__classDef_G1 = 8822272; //0x869E00
const int oCVisualFX__Stop_G1 = 4766512; //0x48BB30
const int oCVisualFX_originVob_offset_G1 = 1112; //0x0458
const int oCVisualFX_targetVob_offset_G1 = 1120; //0x0460
const int oCVisualFX_instanceName_offset_G1 = 1140; //0x0474
// Gothic 2 addresses and offsets
const int zCVob__classDef_G2 = 10106072; //0x9A34D8
const int zCWorld__SearchVobListByClass_G2 = 6439504; //0x624250
const int oCVisualFX__classDef_G2 = 9234008; //0x8CE658
const int oCVisualFX__Stop_G2 = 4799456; //0x493BE0
const int oCVisualFX_originVob_offset_G2 = 1192; //0x04A8
const int oCVisualFX_targetVob_offset_G2 = 1200; //0x04B0
const int oCVisualFX_instanceName_offset_G2 = 1220; //0x04C4
var int zCVob__classDef;
var int zCWorld__SearchVobListByClass;
var int oCVisualFX__classDef;
var int oCVisualFX__Stop;
var int oCVisualFX_originVob_offset;
var int oCVisualFX_targetVob_offset;
var int oCVisualFX_instanceName_offset;
zCVob__classDef = MEMINT_SwitchG1G2(zCVob__classDef_G1,
zCVob__classDef_G2);
zCWorld__SearchVobListByClass = MEMINT_SwitchG1G2(zCWorld__SearchVobListByClass_G1,
zCWorld__SearchVobListByClass_G2);
oCVisualFX__classDef = MEMINT_SwitchG1G2(oCVisualFX__classDef_G1,
oCVisualFX__classDef_G2);
oCVisualFX__Stop = MEMINT_SwitchG1G2(oCVisualFX__Stop_G1,
oCVisualFX__Stop_G2);
oCVisualFX_originVob_offset = MEMINT_SwitchG1G2(oCVisualFX_originVob_offset_G1,
oCVisualFX_originVob_offset_G2);
oCVisualFX_targetVob_offset = MEMINT_SwitchG1G2(oCVisualFX_targetVob_offset_G1,
oCVisualFX_targetVob_offset_G2);
oCVisualFX_instanceName_offset = MEMINT_SwitchG1G2(oCVisualFX_instanceName_offset_G1,
oCVisualFX_instanceName_offset_G2);
var int worldPtr; worldPtr = _@(MEM_World);
if (!worldPtr) {
return 0;
};
// Create array from all oCVisualFX vobs
var int vobArrayPtr; vobArrayPtr = MEM_ArrayCreate();
var zCArray vobArray; vobArray = _^(vobArrayPtr);
const int call = 0; var int zero;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(zero)); // Vob tree (0 == globalVobTree)
CALL_PtrParam(_@(vobArrayPtr)); // Array to store found vobs in
CALL_PtrParam(_@(oCVisualFX__classDef)); // Class definition
CALL__thiscall(_@(worldPtr), zCWorld__SearchVobListByClass);
call = CALL_End();
};
if (!vobArray.numInArray) {
MEM_ArrayFree(vobArrayPtr);
return 0;
};
effectName = STR_Upper(effectName);
var zCPar_Symbol symb;
// Validate origin vob instance
if (originInst) {
// Get pointer from instance symbol
if (originInst > 0) && (originInst < MEM_Parser.symtab_table_numInArray) {
symb = _^(MEM_ReadIntArray(contentSymbolTableAddress, originInst));
originInst = symb.offset;
} else {
originInst = 0;
};
if (!objCheckInheritance(originInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Origin is not a valid vob");
return 0;
};
};
// Validate target vob instance
if (targetInst) {
// Get pointer from instance symbol
if (targetInst > 0) && (targetInst < MEM_Parser.symtab_table_numInArray) {
symb = _^(MEM_ReadIntArray(contentSymbolTableAddress, targetInst));
targetInst = symb.offset;
} else {
targetInst = 0;
};
if (!objCheckInheritance(targetInst, zCVob__classDef)) {
MEM_Warn("Wld_StopEffect_Ext: Target is not a valid vob");
return 0;
};
};
// Search all vobs for the matching name
var int stopped; stopped = 0; // Number of FX stopped
repeat(i, vobArray.numInArray); var int i;
var int vobPtr; vobPtr = MEM_ArrayRead(vobArrayPtr, i);
if (!vobPtr) {
continue;
};
// Search for FX with matching name
if (!Hlp_StrCmp(effectName, "")) {
var string effectInst; effectInst = MEM_ReadString(vobPtr+oCVisualFX_instanceName_offset);
if (!Hlp_StrCmp(effectInst, effectName)) {
continue;
};
};
// Search for a specific origin vob
if (originInst) {
var int originVob; originVob = MEM_ReadInt(vobPtr+oCVisualFX_originVob_offset);
if (originVob != originInst) {
continue;
};
};
// Search for a specific target vob
if (targetInst) {
var int targetVob; targetVob = MEM_ReadInt(vobPtr+oCVisualFX_targetVob_offset);
if (targetVob != targetInst) {
continue;
};
};
// Stop the oCVisualFX
const int call2 = 0; const int one = 1;
if (CALL_Begin(call2)) {
CALL_PtrParam(_@(one));
CALL__thiscall(_@(vobPtr), oCVisualFX__Stop);
call2 = CALL_End();
};
stopped += 1;
if (!all) {
break;
};
end;
MEM_ArrayFree(vobArrayPtr);
return stopped;
};
Im ScriptBin fehlt die Funktion objCheckInheritance, bzw. ist sie nicht in dieser Datei zu finden. Die Funktion sollte aber mindestens unter den Voraussetzungen genannt und verlinkt werden.
mud-freak
19.01.2018, 06:33
Im ScriptBin fehlt die Funktion objCheckInheritance, bzw. ist sie nicht in dieser Datei zu finden. Die Funktion sollte aber mindestens unter den Voraussetzungen genannt und verlinkt werden.
Doch die ist dabei. Lehona hat sie nur von dem Skript abgetrennt und separat in der Datei /Ikarus_Scripts/Misc.d aufgenommen.
Milky-Way
19.01.2018, 06:39
Doch die ist dabei. Lehona hat sie nur von dem Skript abgetrennt und separat in der Datei /Ikarus_Scripts/Misc.d aufgenommen.
Ja, dann wäre es schön, wenn das im Kopf der Datei vermerkt oder verlinkt wäre. Ich könnte mir vorstellen, dass der ein oder andere so wie ich nicht alle Skripte nimmt, sondern sich die raussucht, die er /sie braucht :)
mud-freak
19.01.2018, 08:12
Ja, dann wäre es schön, wenn das im Kopf der Datei vermerkt oder verlinkt wäre. Ich könnte mir vorstellen, dass der ein oder andere so wie ich nicht alle Skripte nimmt, sondern sich die raussucht, die er /sie braucht :)
Gut, dem muss ich zustimmen. Das fänd ich auch gut.
Wo ich gerade mal hier bin: Mir ist aufgefallen, dass das InsertAnything-Skript fehlerhaft ist. Unter gewissen Umständen gehen Vobs nach Speichern und Laden verloren. Ausserdem ist es eher suboptimal implementiert. Beheben lässt sich das ganz einfach, aber das Skript ist einfach so unleserlich formatiert (80 Zeichen Zeilenlänge und kaum Leerzeilen), dass ich da gar keine Lust drauf habe.
Ich werde mir das heute vielleicht mal anschauen und es bei der Gelegenheit auch mal für Gothic 1 kompatibel machen.
mud-freak
19.01.2018, 22:36
Ich habe das Skript jetzt komplett geändert. Die Funktionen haben jetzt nicht mehr so viele Parameter und sind übersichtlicher (stattdessen kann man bei Bedarf weitere Funktionen nach dem Einfügen aufrufen, um bspw. eine Truhe zu füllen, oder eine Tür zu verschliessen).
Die Funktionen sind jetzt auch performanter (falls das eine Rolle spielt) und das ganze ist so aufgesetzt, dass man nun Objekte jeder beliebigen Art einfügen kann, die von zCVob erben. Für die meist genutzten habe ich aber Wrapper-Funktionen erstellt, um die Anzahl der Argumente klein und es übersichtlich zu halten.
Alles sollte jetzt auch unter Gothic 1 laufen, habe ich aber nicht mehr explizit alles durchgetestet.
Und hier ein kleines, nettes Beispiel:
const float pos[3] = { 9458.563477, 589.735718, -4732.891602 }; // World coordinates (use [ALT]+[P] in MARVIN-mode)
var int chestPtr;
chestPtr = InsertMobContainerPos("myNewChest", "CHESTBIG_OCCHESTLARGE.MDS", _@f(pos), 0);
SetMobName(chestPtr, "MOBNAME_CHEST"); // Set focus name
LockMobLockable(chestPtr, "ItKe_Key_01", "LRLR", TRUE); // A key and/or lock picking string can be set
FillMobContainer(chestPtr, "ItMi_Gold:23,ItKe_Key_01:1 "); // Whoops, dropped the key inside!
Und hier ein Beispiel, wie man Objekte jeglicher (von zCVob erbenden) Klasse einfügen kann. Das obere Beispiel kann man auch so schreiben. Man muss nur die Klassenbezeichnung wissen, hier "oCMobContainer".
chestPtr = InsertObjectPos("oCMobContainer", "myNewChest", "CHESTBIG_OCCHESTLARGE.MDS", _@f(pos), 0, 0);
Hier das Skript. Kann dann bei Gelegenheit im Scriptbin aktualisiert werden.
/*
* insertAnything.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25712257
*
* Create and insert any kind of object inheriting from the zCVob class in real time.
*
*
* The core function behind this is
*
* func int InsertObject(string class, string objName, string visual, int trafoPtr, int parentVobPtr)
*
* However, for the most common object classes there are specific functions with simpler signature for inserting at
* - way points (functions with "WP" postfix)
* - exact coordinates with optionally specifying the direction (functions with "Pos" postfix)
* - aligned at a full trafo-matrix (functions without postfix)
*
* Additionally, the objects may be inserted as child vobs of other vobs. (functions with "AsChild" postfix)
* Objects inheriting from the oCMob class can then be further adjusted with additional functions listed below.
*
* [A] = {Vob,Mob,MobInter,MobLockable,MobContainer,MobDoor,MobFire}
*
* func int Insert[A]WP (string objName, string visual, string waypoint)
* func int Insert[A]Pos (string objName, string visual, int posPtr, int dirPtr)
* func int Insert[A] (string objName, string visual, int trafoPtr)
*
* func int Insert[A]AsChildWP (string objName, string visual, string waypoint, int parentVobPtr)
* func int Insert[A]AsChildPos (string objName, string visual, int posPtr, int dirPtr, int parentVobPtr)
* func int Insert[A]AsChild (string objName, string visual, int trafoPtr, int parentVobPtr)
*
*
* [B] = {Trigger,TriggerScript,TriggerChangeLevel}
*
* func int Insert[B]Pos (string objName, int posPtr, int dirPtr)
* func int Insert[B] (string objName, int trafoPtr)
*
* func int InsertMoverPos (string objName, string visual, int posPtr, int dirPtr)
* func int InsertMover (string objName, string visual, int trafoPtr)
*
* func int InsertItemWP (string scriptInst, int amount, string waypoint)
* func int InsertItemPos (string scriptInst, int amount, int posPtr, int dirPtr)
* func int InsertItem (string scriptInst, int amount, int trafoPtr)
*
* func void InsertPileOfVobs (string objName, string visual, int trafoPtr, int amount, int cm2, int rotate)
* func void InsertPileOfItems (string scriptInst, int trafoPtr, int amount, int cm2, int rotate)
*
*
* Additional functions (not all functions are mentioned here)
*
* func void SetVobToFloor (int vobPtr)
* func void SetWPToFloor (int wpPtr)
*
*
* Further mob setter-functions
*
* func int SetMobName (int mobPtr, string symbolicFocusName)
* func void SetMobOwner (int mobPtr, string owner, string ownerGuild)
* func void SetMobMisc (int mobPtr, string triggerTarget, string useWithItem, string onStateFuncName)
* func void LockMobLockable (int mobPtr, string keyInstance, string pickLockStr, int isLocked)
* func void FillMobContainer (int mobPtr, string contents)
*
*
* Example
*
* // Inserting a locked chest with specific contents
*
* const float pos[3] = { 9458.563477, 589.735718, -4732.891602 }; // World coordinates (use [ALT]+[P] in MARVIN-mode)
*
* var int chestPtr;
* chestPtr = InsertMobContainerPos("myNewChest", "CHESTBIG_OCCHESTLARGE.MDS", _@f(pos), 0);
*
* SetMobName(chestPtr, "MOBNAME_CHEST"); // Set focus name
* LockMobLockable(chestPtr, "ItKe_Key_01", "LRLR", TRUE); // A key and/or lock picking string can be set
* FillMobContainer(chestPtr, "ItMi_Gold:23,ItKe_Key_01:1 "); // Whoops, dropped the key inside!
*
*/
/*
* Creates an "empty" trafo-matrix
*/
func void NewTrafo(var int trafoPtr) {
const int zMAT4__s_identity_G1 = 8845416; //0x86F868
const int zMAT4__s_identity_G2 = 9258472; //0x8D45E8
const int sizeof_zMAT4 = 64; // Same for G1 and G2
MEM_CopyBytes(MEMINT_SwitchG1G2(zMAT4__s_identity_G1, zMAT4__s_identity_G2), trafoPtr, sizeof_zMAT4);
};
/*
* Rotate trafo-matrix around its up-vector (change front-facing direction)
*/
func void RotateTrafoY(var int trafoPtr, var int degrees) {
const int zMAT4__PostRotateY_G1 = 5274256; //0x507A90
const int zMAT4__PostRotateY_G2 = 5339008; //0x517780
const int call = 0;
if (CALL_Begin(call)) {
CALL_FloatParam(_@(degrees));
CALL__thiscall(_@(trafoPtr), MEMINT_SwitchG1G2(zMAT4__PostRotateY_G1, zMAT4__PostRotateY_G2));
call = CALL_End();
};
};
/*
* Store the positional information of a trafo-matrix into a vector
*/
func void TrfToPos(var int trafoPtr, var int posPtr) {
MEM_WriteIntArray(posPtr, 0, MEM_ReadIntArray(trafoPtr, 3));
MEM_WriteIntArray(posPtr, 1, MEM_ReadIntArray(trafoPtr, 7));
MEM_WriteIntArray(posPtr, 2, MEM_ReadIntArray(trafoPtr, 11));
};
/*
* Store positional and/or directional information into a trafo-matrix
*/
func void PosDirToTrf(var int posPtr, var int dirPtr, var int trafoPtr) {
const int zMAT4__MakeOrthonormal_G1 = 5273152; //0x507640
const int zMAT4__MakeOrthonormal_G2 = 5337904; //0x517330
if (posPtr) {
MEM_WriteIntArray(trafoPtr, 3, MEM_ReadIntArray(posPtr, 0));
MEM_WriteIntArray(trafoPtr, 7, MEM_ReadIntArray(posPtr, 1));
MEM_WriteIntArray(trafoPtr, 11, MEM_ReadIntArray(posPtr, 2));
};
if (dirPtr) {
MEM_WriteIntArray(trafoPtr, 2, MEM_ReadIntArray(dirPtr, 0));
MEM_WriteIntArray(trafoPtr, 6, MEM_ReadIntArray(dirPtr, 1));
MEM_WriteIntArray(trafoPtr, 10, MEM_ReadIntArray(dirPtr, 2));
const int call = 0;
if (CALL_Begin(call)) {
CALL__thiscall(_@(trafoPtr), MEMINT_SwitchG1G2(zMAT4__MakeOrthonormal_G1, zMAT4__MakeOrthonormal_G2));
call = CALL_End();
};
};
};
/*
* Get ground level (y-coordinate) from a position
*/
func int GetGroundLvlPos(var int posPtr) {
const float dir[3] = { 0.0, -2000.0, 0.0 }; // Check for ground up to 2000 cm below (Gothic does 1000 cm)
const int zCWorld__TraceRayNearestHit_G1 = 6243008; //0x5F42C0
const int zCWorld__TraceRayNearestHit_G2 = 6429568; //0x621B80
const int flags = 33; // 0x21 (zTRACERAY_VOB_IGNORE_NO_CD_DYN | zTRACERAY_STAT_POLY)
const int ignoreList = 0;
var int worldPtr; worldPtr = MEM_Game._zCSession_world;
var int dirPtr; dirPtr = _@f(dir);
const int call = 0;
if (CALL_Begin(call)) {
CALL_IntParam(_@(flags));
CALL_PtrParam(_@(ignoreList));
CALL_PtrParam(_@(dirPtr));
CALL__fastcall(_@(worldPtr), _@(posPtr), MEMINT_SwitchG1G2(zCWorld__TraceRayNearestHit_G1,
zCWorld__TraceRayNearestHit_G2));
call = CALL_End();
};
// Check for intersection and return y-position
if (CALL_RetValAsInt()) && ((MEM_World.foundPoly) || (MEM_World.foundVob)) {
return MEM_World.foundIntersection[1];
} else {
return subf(MEM_ReadInt(posPtr+4), mkf(2000));
};
};
/*
* Get ground level (y-coordinate) from a trafo-matrix
*/
func int GetGroundLvl(var int trfPtr) {
var int pos[3];
TrfToPos(trfPtr, _@(pos));
return GetGroundLvlPos(_@(pos));
};
/*
* Search a way point by its name
*/
func int SearchWaypointByName(var string waypoint) {
const int zCWayNet__GetWaypoint_G1 = 7366448; //0x706730
const int zCWayNet__GetWaypoint_G2 = 8061744; //0x7B0330
var int waynetPtr; waynetPtr = MEM_World.wayNet;
var int wpNamePtr; wpNamePtr = _@s(waypoint);
const int call = 0;
if (Call_Begin(call)) {
CALL__fastcall(_@(waynetPtr), _@(wpNamePtr), MEMINT_SwitchG1G2(zCWayNet__GetWaypoint_G1,
zCWayNet__GetWaypoint_G2));
call = CALL_End();
};
return CALL_RetValAsPtr();
};
/*
* Get trafo-matrix from way point
*/
func void GetTrafoFromWP(var string wpName, var int trafoPtr) {
var int wpPtr; wpPtr = SearchWaypointByName(wpName);
if (!wpPtr) {
MEM_Warn("Way point not found!");
return;
};
var zCWaypoint wp; wp = _^(wpPtr);
PosDirToTrf(_@(wp.pos), _@(wp.dir), trafoPtr);
};
/*
* Set way point to correct height above ground level
*/
func void SetWPToFloor(var int wpPtr) {
const int zCWaypoint__CorrectHeight_G1 = 7365792; //0x7064A0
const int zCWaypoint__CorrectHeight_G2 = 8061088; //0x7B00A0
var int worldPtr; worldPtr = MEM_Game._zCSession_world;
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(worldPtr));
CALL__thiscall(_@(wpPtr), MEMINT_SwitchG1G2(zCWaypoint__CorrectHeight_G1, zCWaypoint__CorrectHeight_G2));
call = CALL_End();
};
};
/*
* Align a vob to a trafo-matrix
*/
func void AlignVobAt(var int vobPtr, var int trfPtr) {
if (!vobPtr) || (!trfPtr) {
return;
};
const int zCVob__SetTrafoObjToWorld_G1 = 6219616; //0x5EE760
const int zCVob__SetTrafoObjToWorld_G2 = 6405248; //0x61BC80
// Lift collision
var zCVob vob; vob = _^(vobPtr);
var int bits; bits = vob.bitfield[0];
vob.bitfield[0] = vob.bitfield[0] & ~zCVob_bitfield0_collDetectionStatic & ~zCVob_bitfield0_collDetectionDynamic;
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(trfPtr));
CALL__thiscall(_@(vobPtr), MEMINT_SwitchG1G2(zCVob__SetTrafoObjToWorld_G1, zCVob__SetTrafoObjToWorld_G2));
call = CALL_End();
};
// Restore bits
vob.bitfield[0] = bits;
};
/*
* Set vob to align at ground level
*/
func void SetVobToFloor(var int vobPtr) {
if (!vobPtr) {
return;
};
// Correct height
var zCVob vob; vob = _^(vobPtr);
var int half; half = subf(vob.trafoObjToWorld[7], vob.bbox3D_mins[1]);
vob.trafoObjToWorld[7] = GetGroundLvl(_@(vob.trafoObjToWorld));
vob.trafoObjToWorld[7] = addf(vob.trafoObjToWorld[7], half);
vob.trafoObjToWorld[7] = subf(vob.trafoObjToWorld[7], castToIntf(0.5));
// Update position
AlignVobAt(vobPtr, _@(vob.trafoObjToWorld));
};
/*
* Set a visual of a given vob by string
*/
func void VobSetVisual(var int vobPtr, var String visual) {
const int zCVob__SetVisual_G1 = 6123424; //0x5D6FA0
const int zCVob__SetVisual_G2 = 6301312; //0x602680
if (!vobPtr) {
return;
};
var int strPtr; strPtr = _@s(visual);
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(strPtr));
CALL__thiscall(_@(vobPtr), MEMINT_SwitchG1G2(zCVob__SetVisual_G1, zCVob__SetVisual_G2));
call = CALL_End();
};
};
/*
* Retrieve class definition address by string
*/
func int GetClassDefByString(var string classDefName) {
const int zCClassDef__GetClassDef_G1 = 5809264; //0x58A470
const int zCClassDef__GetClassDef_G2 = 5939168; //0x5A9FE0
var int classDefNamePtr; classDefNamePtr = _@s(classDefName);
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(classDefNamePtr));
CALL__cdecl(MEMINT_SwitchG1G2(zCClassDef__GetClassDef_G1, zCClassDef__GetClassDef_G2));
call = CALL_End();
};
return CALL_RetValAsPtr();
};
/*
* Create a new instance by class definition
*/
func int CreateNewInstance(var int classDefAddr) {
const int zCClassDef__createNewInstance_offset = 64; //0x40 Same for G1 and G2
// This is a non-recyclable call, because of varying function address
CALL__cdecl(MEM_ReadInt(classDefAddr+zCClassDef__createNewInstance_offset));
return CALL_RetValAsPtr();
};
/*
* Create new instance by class definition name
*/
func int CreateNewInstanceByString(var string classDefName) {
var int classDefAddr; classDefAddr = GetClassDefByString(classDefName);
if (!classDefAddr) {
MEM_Warn("ClassDef not found");
return 0;
};
return CreateNewInstance(classDefAddr);
};
/*
* Create and insert object
*/
func int InsertObject(var string class, var string objName, var string visual, var int trfPtr, var int parentPtr) {
var int vobPtr; vobPtr = CreateNewInstanceByString(class);
if (!vobPtr) {
return 0;
};
VobSetVisual(vobPtr, visual);
MEM_RenameVob(vobPtr, objName);
// Adjust bits
var zCVob vob; vob = _^(vobPtr);
vob.bitfield[0] = vob.bitfield[0] & (-67108865); //0xFBFFFFFF
vob.bitfield[0] = vob.bitfield[0] & ~zCVob_bitfield0_collDetectionStatic & ~zCVob_bitfield0_collDetectionDynamic;
// Set positional and rotational information
AlignVobAt(vobPtr, trfPtr);
// Get parent vob tree
var int vobTreePtr;
if (parentPtr) {
var zCVob parent; parent = _^(parentPtr);
vobTreePtr = parent.globalVobTreeNode;
} else {
vobTreePtr = _@(MEM_Vobtree); // Global vob tree
};
// Insert into world
const int oCWorld__AddVobAsChild_G1 = 7171232; //0x6D6CA0
const int oCWorld__AddVobAsChild_G2 = 7863856; //0x77FE30
var int worldPtr; worldPtr = MEM_Game._zCSession_world;
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(vobTreePtr));
CALL_PtrParam(_@(vobPtr));
CALL_PutRetValTo(0);
CALL__thiscall(_@(worldPtr), MEMINT_SwitchG1G2(oCWorld__AddVobAsChild_G1, oCWorld__AddVobAsChild_G2));
call = CALL_End();
};
// Set bits
vob.bitfield[0] = vob.bitfield[0] | zCVob_bitfield0_collDetectionStatic | zCVob_bitfield0_collDetectionDynamic;
vob.bitfield[2] = vob.bitfield[2] & ~zCVob_bitfield2_sleepingMode; // vob.SetSleeping(1);
// Decrease reference counter... why is that necessary?
vob._zCObject_refCtr -= 1;
if (vob._zCObject_refCtr <= 0) {
const int _scalar_deleting_destructor_offset = 12; // Same for G1 and G2
CALL_IntParam(1);
CALL__thiscall(vobPtr, MEM_ReadInt(vob._vtbl+_scalar_deleting_destructor_offset));
};
return vobPtr;
};
func int InsertObjectPos(var string class, var string nm, var string vis, var int pos, var int dir, var int par) {
var int trafo[16]; NewTrafo(_@(trafo));
PosDirToTrf(pos, dir, _@(trafo));
return InsertObject(class, nm, vis, _@(trafo), par);
};
func int InsertObjectWP(var string class, var string nm, var string vis, var string wpName, var int par) {
var int wpPtr; wpPtr = SearchWaypointByName(wpName);
if (!wpPtr) {
MEM_Warn(ConcatStrings("Way point not found: ", wpName));
return 0;
};
var zCWaypoint wp; wp = _^(wpPtr);
return InsertObjectPos(class, nm, vis, _@(wp.pos), _@(wp.dir), par);
};
/*
* zCVob/oCVob functions:
*
* InsertVobWP (string objName, string visual, string waypoint)
* InsertVobPos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertVob (string objName, string visual, int[16] *trafoMat)
*
* InsertVobAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertVobAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertVobAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertVobAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("zCVob", nm, vis, trf, par);
};
func int InsertVobAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("zCVob", nm, vis, pos, dir, par);
};
func int InsertVobAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("zCVob", nm, vis, wp, par);
};
func int InsertVob(var string nm, var string vis, var int trf) {
return InsertObject("zCVob", nm, vis, trf, 0);
};
func int InsertVobPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("zCVob", nm, vis, pos, dir, 0);
};
func int InsertVobWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("zCVob", nm, vis, wp, 0);
};
/*
* oCMob functions:
*
* InsertMobWP (string objName, string visual, string waypoint)
* InsertMobPos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMob (string objName, string visual, int[16] *trafoMat)
*
* InsertMobAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMOB", nm, vis, trf, par);
};
func int InsertMobAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMOB", nm, vis, pos, dir, par);
};
func int InsertMobAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMOB", nm, vis, wp, par);
};
func int InsertMob(var string nm, var string vis, var int trf) {
return InsertObject("oCMOB", nm, vis, trf, 0);
};
func int InsertMobPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMOB", nm, vis, pos, dir, 0);
};
func int InsertMobWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMOB", nm, vis, wp, 0);
};
/*
* oCMobInter functions:
*
* InsertMobInterWP (string objName, string visual, string waypoint)
* InsertMobInterPos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMobInter (string objName, string visual, int[16] *trafoMat)
*
* InsertMobInterAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobInterAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobInterAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobInterAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMobInter", nm, vis, trf, par);
};
func int InsertMobInterAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMobInter", nm, vis, pos, dir, par);
};
func int InsertMobInterAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMobInter", nm, vis, wp, par);
};
func int InsertMobInter(var string nm, var string vis, var int trf) {
return InsertObject("oCMobInter", nm, vis, trf, 0);
};
func int InsertMobInterPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMobInter", nm, vis, pos, dir, 0);
};
func int InsertMobInterWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMobInter", nm, vis, wp, 0);
};
/*
* oCMobLockable functions:
*
* InsertMobLockableWP (string objName, string visual, string waypoint)
* InsertMobLockablePos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMobLockable (string objName, string visual, int[16] *trafoMat)
*
* InsertMobLockableAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobLockableAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobLockableAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobLockableAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMobLockable", nm, vis, trf, par);
};
func int InsertMobLockableAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMobLockable", nm, vis, pos, dir, par);
};
func int InsertMobLockableAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMobLockable", nm, vis, wp, par);
};
func int InsertMobLockable(var string nm, var string vis, var int trf) {
return InsertObject("oCMobLockable", nm, vis, trf, 0);
};
func int InsertMobLockablePos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMobLockable", nm, vis, pos, dir, 0);
};
func int InsertMobLockableWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMobLockable", nm, vis, wp, 0);
};
/*
* oCMobContainer functions:
*
* InsertMobContainerWP (string objName, string visual, string waypoint)
* InsertMobContainerPos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMobContainer (string objName, string visual, int[16] *trafoMat)
*
* InsertMobContainerAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobContainerAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobContainerAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobContainerAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMobContainer", nm, vis, trf, par);
};
func int InsertMobContainerAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMobContainer", nm, vis, pos, dir, par);
};
func int InsertMobContainerAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMobContainer", nm, vis, wp, par);
};
func int InsertMobContainer(var string nm, var string vis, var int trf) {
return InsertObject("oCMobContainer", nm, vis, trf, 0);
};
func int InsertMobContainerPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMobContainer", nm, vis, pos, dir, 0);
};
func int InsertMobContainerWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMobContainer", nm, vis, wp, 0);
};
/*
* oCMobDoor functions:
*
* InsertMobDoorWP (string objName, string visual, string waypoint)
* InsertMobDoorPos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMobDoor (string objName, string visual, int[16] *trafoMat)
*
* InsertMobDoorAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobDoorAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobDoorAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobDoorAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMobDoor", nm, vis, trf, par);
};
func int InsertMobDoorAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMobDoor", nm, vis, pos, dir, par);
};
func int InsertMobDoorAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMobDoor", nm, vis, wp, par);
};
func int InsertMobDoor(var string nm, var string vis, var int trf) {
return InsertObject("oCMobDoor", nm, vis, trf, 0);
};
func int InsertMobDoorPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMobDoor", nm, vis, pos, dir, 0);
};
func int InsertMobDoorWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMobDoor", nm, vis, wp, 0);
};
/*
* oCMobFire functions:
*
* InsertMobFireWP (string objName, string visual, string waypoint)
* InsertMobFirePos (string objName, string visual, int[3] *pos, int[3] *dir)
* InsertMobFire (string objName, string visual, int[16] *trafoMat)
*
* InsertMobFireAsChildWP (string objName, string visual, string waypoint, int *parentVob)
* InsertMobFireAsChildPos(string objName, string visual, int[3] *pos, int[3] *dir, int *parentVob)
* InsertMobFireAsChild (string objName, string visual, int[16] *trafoMat, int *parentVob)
*/
func int InsertMobFireAsChild(var string nm, var string vis, var int trf, var int par) {
return InsertObject("oCMobFire", nm, vis, trf, par);
};
func int InsertMobFireAsChildPos(var string nm, var string vis, var int pos, var int dir, var int par) {
return InsertObjectPos("oCMobFire", nm, vis, pos, dir, par);
};
func int InsertMobFireAsChildWP(var string nm, var string vis, var string wp, var int par) {
return InsertObjectWP("oCMobFire", nm, vis, wp, par);
};
func int InsertMobFire(var string nm, var string vis, var int trf) {
return InsertObject("oCMobFire", nm, vis, trf, 0);
};
func int InsertMobFirePos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("oCMobFire", nm, vis, pos, dir, 0);
};
func int InsertMobFireWP(var string nm, var string vis, var string wp) {
return InsertObjectWP("oCMobFire", nm, vis, wp, 0);
};
/*
* "Abstract" vob functions:
*
* InsertTriggerPos (string objName, int[3] *pos, int[3] *dir)
* InsertTriggerScriptPos (string objName, int[3] *pos, int[3] *dir)
* InsertTriggerChangeLevelPos(string objName, int[3] *pos, int[3] *dir)
* InsertMoverPos (string objName, string visual, int[3] *pos, int[3] *dir)
*
* InsertTrigger (string objName, int[16] *trafoMat)
* InsertTriggerScript (string objName, int[16] *trafoMat)
* InsertTriggerChangeLevel (string objName, int[16] *trafoMat)
* InsertMover (string objName, string visual, int[16] *trafoMat)
*/
func int InsertTrigger(var string nm, var int trf) {
return InsertObject("zCTrigger", nm, "", trf, 0);
};
func int InsertTriggerPos(var string nm, var int pos, var int dir) {
return InsertObjectPos("zCTrigger", nm, "", pos, dir, 0);
};
func int InsertTriggerScript(var string nm, var int trf) {
return InsertObject("oCTriggerScript", nm, "", trf, 0);
};
func int InsertTriggerScriptPos(var string nm, var int pos, var int dir) {
return InsertObjectPos("oCTriggerScript", nm, "", pos, dir, 0);
};
func int InsertTriggerChangeLevel(var string nm, var int trf) {
return InsertObject("oCTriggerChangeLevel", nm, "", trf, 0);
};
func int InsertTriggerChangeLevelPos(var string nm, var int pos, var int dir) {
return InsertObjectPos("oCTriggerChangeLevel", nm, "", pos, dir, 0);
};
func int InsertMover(var string nm, var string vis, var int trf) {
return InsertObject("zCMover", nm, vis, trf, 0);
};
func int InsertMoverPos(var string nm, var string vis, var int pos, var int dir) {
return InsertObjectPos("zCMover", nm, vis, pos, dir, 0);
};
/*
* oCItem functions:
*
* InsertItemWP (string itemInstance, int amount, string waypoint)
* InsertItemPos(string itemInstance, int amount, int* pos, int* dir)
* InsertItem (string itemInstance, int amount, int* trafoMat)
*/
func int InsertItemWP(var string itmInst, var int amount, var string wp) {
var int symbIdx; symbIdx = MEM_GetSymbolIndex(itmInst);
Wld_InsertItem(symbIdx, wp);
var zCTree newTreeNode; newTreeNode = _^(MEM_World.globalVobTree_firstChild);
var int itmPtr; itmPtr = newTreeNode.data;
if (!Hlp_Is_oCItem(itmPtr)) {
// Item effect is spawned last
newTreeNode = _^ (newTreeNode.next);
itmPtr = newTreeNode.data;
if (!Hlp_Is_oCItem(itmPtr)) {
MEM_Warn(ConcatStrings("Could not insert item: ", itmInst));
return 0;
};
};
var oCItem itm; itm = _^(itmPtr);
if (itm.instanz != symbIdx) {
MEM_Warn(ConcatStrings("Could not insert item: ", itmInst));
return 0;
};
itm.amount = amount;
return itmPtr;
};
func int InsertItem(var string itmInst, var int amount, var int trf) {
var int itmPtr; itmPtr = InsertItemWP(itmInst, amount, MEM_FARFARAWAY);
AlignVobAt(itmPtr, trf);
return itmPtr;
};
func int InsertItemPos(var string itmInst, var int amount, var int pos, var int dir) {
var int trafo[16];
NewTrafo(_@(trafo));
PosDirToTrf(pos, dir, _@(trafo));
var int itmPtr; itmPtr = InsertItem(itmInst, amount, _@(trafo));
SetVobToFloor(itmPtr);
return itmPtr;
};
/*
* PileOf functions:
*
* InsertPileOf (string class, string objName, string visual, int[16]* trafoMat, int amount, int cm2, int rotate)
* InsertPileOfVobs ( string objName, string visual, int[16]* trafoMat, int amount, int cm2, int rotate)
* InsertPileOfItems( string itmInst, int[16]* trafoMat, int amount, int cm2, int rotate)
*/
func void InsertPileOf(var string cl, var string nm, var string vis, var int trf, var int am, var int cm, var int rot) {
const int sizeof_zMAT4 = 64; // Same for G1 and G2
repeat(i, am); var int i;
// Get new trafo from the original
var int trafo[16];
MEM_CopyBytes(trf, _@(trafo), sizeof_zMAT4);
// Increase height for better piling (will be "dropped")
trafo[ 7] = addf(trafo[ 7], mkf(200));
// Shift position (will be distributed over a square not a circle)
trafo[ 3] = addf(trafo[ 3], mkf(Hlp_Random(cm*2)-cm));
trafo[11] = addf(trafo[11], mkf(Hlp_Random(cm*2)-cm));
// Rotate
if (rot) {
RotateTrafoY(_@(trafo), mkf(Hlp_Random(360)));
};
// Create vob or item
var int vobPtr;
if (Hlp_StrCmp(cl, "oCItem")) {
vobPtr = InsertItem(nm, 1, _@(trafo));
} else {
var string name; name = ConcatStrings(nm, IntToString(i+1));
vobPtr = InsertObject(cl, name, vis, _@(trafo), 0);
};
SetVobToFloor(vobPtr);
end;
};
func void InsertPileOfVobs(var string nm, var string vis, var int trf, var int am, var int cm, var int rot) {
InsertPileOf("zCVob", nm, vis, trf, am, cm, rot);
};
func void InsertPileOfItems(var string itmInst, var int trf, var int am, var int cm, var int rot) {
InsertPileOf("oCItem", itmInst, "", trf, am, cm, rot);
};
/*
* Post-insert functions
*/
/*
* Set the focus name of a mob (see Text.d)
*/
func int SetMobName(var int mobPtr, var string symbolicFocusName) {
if (Hlp_Is_oCMob(mobPtr)) {
var oCMob mob; mob = _^(mobPtr);
mob.name = symbolicFocusName;
mob.focusNameIndex = MEM_GetSymbolIndex(symbolicFocusName);
};
return mobPtr;
};
/*
* Set mob owner. Thanks to Bisasam!
*/
func void SetMobOwner(var int mobPtr, var string owner, var string ownerGuild) {
if (!Hlp_Is_oCMob(mobPtr)) {
MEM_Warn("Not a oCMob!");
return;
};
var oCMob mob; mob = _^(mobPtr);
mob.ownerStr = owner;
mob.ownerGuildStr = ownerGuild;
};
/*
* Set some miscellaneous properties of mobs
*/
func void SetMobMisc(var int mobPtr, var string triggerTarget, var string useWithItem, var string onStateFuncName) {
if (!Hlp_Is_oCMobInter(mobPtr)) {
MEM_Warn("Not a oCMobInter!");
return;
};
var oCMobInter mob; mob = _^(mobPtr);
mob.triggerTarget = triggerTarget;
mob.useWithItem = useWithItem;
mob.onStateFuncName = onStateFuncName;
};
/*
* Lock a oCMobLockable
*/
func void LockMobLockable(var int mobPtr, var string keyInstance, var string pickLockStr, var int locked) {
if (!Hlp_Is_oCMobLockable(mobPtr)) {
MEM_Warn("Not a oCMobLocable!");
return;
};
var oCMobLockable mob; mob = _^(mobPtr);
mob.keyInstance = keyInstance;
mob.pickLockStr = pickLockStr;
if (locked) {
mob.bitfield = mob.bitfield | oCMobLockable_bitfield_locked;
};
};
/*
* Create contents of oCMobContainers by string
*/
func void FillMobContainer(var int mobPtr, var string contents) {
if (!Hlp_Is_oCMobContainer(mobPtr)) {
MEM_Warn("Not a oCMobContainer!");
return;
};
const int oCMobContainer__CreateContents_G1 = 6832208; //0x684050
const int oCMobContainer__CreateContents_G2 = 7496080; //0x726190
var int contentsPtr; contentsPtr = _@s(contents);
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(contentsPtr));
CALL__thiscall(_@(mobPtr), MEMINT_SwitchG1G2(oCMobContainer__CreateContents_G1,
oCMobContainer__CreateContents_G2));
call = CALL_End();
};
};
mud-freak
19.01.2018, 22:45
Hier ist noch ein Tipp zur Benutzung. Ich habe ein kleines Skript geschrieben, das die Trafo-Matrix vom Helden, dem Fokusvob oder einem Vob nach Name formatiert auf dem zSpy ausgibt. Das ganz sehr nützlich sein, um schnell an vernünftige Koordinaten um Einfügen von Vobs heranzukommen.
/*
* Print the current trafo-matrix to zSpy
*/
/*
* Send readable trafo of vob to zSpy
*/
func void printTrafo(var int vobPtr) {
if (!vobPtr) {
return;
};
var zCVob vob; vob = _^(vobPtr);
MEM_Info(ConcatStrings("Print trafo of ", vob._zCObject_objectname));
var int s; s = SB_New();
repeat(i, 4); var int i;
SB(" ");
repeat(j, 4); var int j;
SBf(MEM_ReadStatArr(_@(vob.trafoObjToWorld), j+i*4));
if (j+i*4 != 15) {
SB(", ");
};
end;
MEM_Info(SB_ToString());
SB_Clear();
end;
SB_Destroy();
};
/*
* Console command for hero trafo
*/
func string CC_PrintTrafoHero(var string _) {
printTrafo(_@(hero));
return "Trafo sent to zSpy";
};
func void CC_PrintTrafoHeroInit() {
CC_Register(CC_PrintTrafoHero, "print trafo hero", "Send the trafo coordinates of the hero to the zSpy");
};
/*
* Console command for focus trafo
*/
func string CC_PrintTrafoFocus(var string _) {
var oCNpc her; her = Hlp_GetNpc(hero);
printTrafo(her.focus_vob);
return "Trafo sent to zSpy";
};
func void CC_PrintTrafoFocusInit() {
CC_Register(CC_PrintTrafoFocus, "print trafo focus", "Send the trafo coordinates of the focus vob to the zSpy");
};
/*
* Console command for trafo by vob name
*/
func string CC_PrintTrafoVobByName(var string vobname) {
var int vobPtr; vobPtr = MEM_SearchVobByName(vobName);
if (!vobPtr) {
return "Vob not found";
};
printTrafo(vobPtr);
return "Trafo sent to zSpy";
};
func void CC_PrintTrafoVobByNameInit() {
CC_Register(CC_PrintTrafoVobByName, "print trafo vob ", "Send the trafo coordinates by vob name to the zSpy");
};
Initialisieren der Konsolenbefehle in der der Init_Global mit:
CC_PrintTrafoHeroInit();
CC_PrintTrafoFocusInit();
CC_PrintTrafoVobByNameInit();
Aufrufen der Funktionen von der Konsole jeweils mit:
print trafo hero
print trafo focus
print trafo vob
Man kann eine solche Trafo-Matrix dann einfach kopieren und ganz leicht so erstellen und an Insert-Funktionen "weitergeben":
const float trafo[16] = {
-0.225040, 0.000001, -0.974349, 9456.563477,
-0.000001, 1.000000, 0.000002, 589.735718,
0.974349, 0.000000, -0.225040, -4588.891602,
0.000000, 0.000000, 0.000000, 1.000000
};
var int ptr;
ptr = InsertVob("LMS_ENTRANCE_DOOR_02", "DOOR_WOODEN.MDS", _@f(trafo));
Cryp18Struct
21.01.2018, 14:31
Mir ist aufgefallen, dass das InsertAnything-Skript fehlerhaft ist. Unter gewissen Umständen gehen Vobs nach Speichern und Laden verloren.
Kannst du ausführen was für Umstände das sind?
Beim Wechsel zum neuen Skript verliert man leider einige der nützlichen Hilfsfunktionen die im alten Skript drin sind, im Scriptbin einfach ersetzten ist daher eventuell nicht ideal.
mud-freak
21.01.2018, 16:01
Kannst du ausführen was für Umstände das sind?
Nicht genau. Bei mir kam es bei Verwendung dazu, dass Vobs, die ich mit InsertVobAt eingefügt hatte, nach dem Spielladen mit Fehlermeldungen nicht mehr da waren. Ich vermute stark, dass es mit der Allokation von deren Speicher zusammenhing.
Das war seitens des Skripts übernommen und wird im neuen Skript jetzt von Gothic selbst verwaltet.
Beim Wechsel zum neuen Skript verliert man leider einige der nützlichen Hilfsfunktionen die im alten Skript drin sind, im Scriptbin einfach ersetzten ist daher eventuell nicht ideal.
Rückwärtskompatibilität habe ich aufgegeben, nachdem ich bemerkt habe, dass mehrere der Funktionen fehlerhaft waren (z.B. LevelVec) und ich wollte die Anzahl der Funktionsparameter verringern, sodass ich irgendwann mehr oder weniger von null gestartet bin, wodurch einige Funktionen einfach nicht mehr notwendig waren.
Gibt es bestimmte Funktionen die du vermisst? Sicher kann ich die dort wieder irgendwie einbauen.
Cryp18Struct
21.01.2018, 16:09
Gibt es bestimmte Funktionen die du vermisst? Sicher kann ich die dort wieder irgendwie einbauen.
Unter der Annahme das es keine Probleme gibt(und mir sind keine aufgefallen):
RemoveoCVobSafe/RemovezCVobSafe sind sehr praktisch. Sollte jeder haben. Und passt auch thematisch: wenn man dynamisch Objekte erzeugt kommt es bestimmt auch vor das man sie löschen möchte.
GetGroundAtTrf/GetGroundAtPos
AlignWPToFloor
GetTrafoFromWP
Hier bin ich mir jetzt nicht sicher ob das Teil des Pakets war oder ob ich das da nur reinkopiert hatte:
PileOfVobs/PileOfItems
mud-freak
21.01.2018, 16:25
Unter der Annahme das es keine Probleme gibt(und mir sind keine aufgefallen):
RemoveoCVobSafe/RemovezCVobSafe sind sehr praktisch. Sollte jeder haben. Und passt auch thematisch: wenn man dynamisch Objekte erzeugt kommt es bestimmt auch vor das man sie löschen möchte.
Vobs löschen ging schon die ganze Zeit mit den "Basis"-Skripten von Ikarus. Dort gibt es eine Funktion MEM_DeleteVob. Das wusste ich zu dem Zeitpunkt nicht und hatte sie selbst noch einmal erstellt.
Die anderen Funktionen, die du erwähnt hast, kann ich wieder (etwas verbessert) aufnehmen. Das schaue ich mir dann in den nächsten Tagen an. Die PileOf*-Funktionen waren eine Spielerei, die im Grunde nur x Items/Vobs mit zufälligem Radius y um eine Position z einfügen. Kann ich aber auch wieder mit reinnehmen.
mud-freak
22.01.2018, 17:14
Ich habe das Skript oben (u.a.) mit folgenden Funktionen ergänzt.
func int GetGroundLvlPos(int posPtr)
func int GetGroundLvl (int trafoPtr)
func void GetTrafoFromWP(string waypoint, int trafoPtr)
func void SetWPToFloor (int wpPtr)
func int InsertItem(string scriptInst, int amount, int trafoPtr)
func void InsertPileOf (string class, string objName, string visual, int trafoPtr, int amount, int cm2, int rotate)
func void InsertPileOfVobs ( string objName, string visual, int trafoPtr, int amount, int cm2, int rotate)
func void InsertPileOfItems( string scriptInst, int trafoPtr, int amount, int cm2, int rotate)
EDIT: Ich habe die Funktion SetVobToFloor verschönert; Vobs schliessen jetzt tatsächlich am Boden an und schweben nicht mehr 4cm darüber.
Cryp18Struct
22.01.2018, 22:09
Danke mud-freak.
Wäre ja auch Schade wenn die Funktionen einfach verschwinden.
Was RemoveoCVobSafe angeht: da hat man per Parameter die Möglichkeit alle Kinder gleich mit zu löschen im Gegensatz zu MEM_DeleteVob.
mud-freak
23.01.2018, 07:40
Das habe ich absichtlich rausgelassen, weil es extrem gefährlich ist. Vobs aller Art wechseln im Laufe des Spiels ständig den Vobtree. So wird z.B. der Held ein Kind eines Movers, wenn er damit verschoben wird. Möglicherweise wird er auch das Kind eines Mobs, das er gerade benutzt. Dabei würden mindestens Vobs auf unerklärliche Weise verschwinden, wenn nicht sogar das Spiel abstürzen wenn Referenzzähler nicht aufgehen oder plötzlich der Held gelöscht wird.
Wenn man im Spiel ein Vob mit allen Kindern löschen möchte, weiss man meistens auch welche Vobs das beinhalten sollte und kennt sie idealerweise mit Namen. Wenn nicht, kann man über dessen Vobtree iterieren (zCVob.globalVobTreeNode ist ein zCTree und in Ikarus dokumentiert) und nur die Vobs löschen, die in Frage kommen und als letztes das Vob selbst (alle anderen Kinder werden beim Löschen automatisch in den nächst höheren oder den globalen Vobtree verschoben).
Natürlich steht es dir aber frei die Funktion (RemoveoCVobSafe) auf eigene Gefahr trotzdem zu benutzen. Sie sollte nach wie vor funktionieren.
mud-freak
24.01.2018, 17:22
Ich habe mal wieder ein paar Skripte angesammelt und möchte sie hier teilen. Ob sie tatsächlich was für ScriptBin taugen, kann Lehona sich dann überlegen. Weil das nicht alles passt, schreibe mehrere Posts um es übersichtlich zu halten und für die Möglichkeit einzelne Skripts besser zu verlinken.
Und weil die Scripte sich teilweise gegenseitig voraussetzen (alle vermerkt) fange ich mit den simpelsten an.
strings.d – Miscellaneous string functions
/*
* strings.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717007
*
* Miscellaneous string functions
*
* - Requires Ikarus>=1.2.2
* - Compatible with Gothic 1 and Gothic 2
*
*
* func string STR_ReplaceOnce(string haystack, string needle, string replace)
* func string STR_ReplaceAll (string haystack, string needle, string replace)
*
* func string STR_Postfix(string str, int off)
* func string STR_TrimL(string str, string tok)
* func string STR_TrimR(string str, string tok)
* func string STR_Trim (string str, string tok)
* func string STR_TrimCharL(string str, int chr)
* func string STR_TrimCharR(string str, int chr)
* func string STR_TrimChar (string str, int chr)
*
* func int STR_ToFloat(string str)
*
* func int STR_IndexOfFirstNonNumeric(string str)
*/
/*
* Replace first occurrence of needle in haystack and replace it
*/
func string STR_ReplaceOnce(var string haystack, var string needle, var string replace) {
var zString zSh; zSh = _^(_@s(haystack));
var zString zSn; zSn = _^(_@s(needle));
if (!zSh.len) || (!zSn.len) {
return haystack;
};
var int startPos; startPos = STR_IndexOf(haystack, needle);
if (startPos == -1) {
return haystack;
};
var string destStr; destStr = "";
destStr = STR_Prefix(haystack, startPos);
destStr = ConcatStrings(destStr, replace);
destStr = ConcatStrings(destStr, STR_Substr(haystack, startPos+zSn.len, zSh.len-(startPos+zSn.len)));
return destStr;
};
/*
* Replace all occurrences of needle in haystack and replace them
*/
func string STR_ReplaceAll(var string haystack, var string needle, var string replace) {
var string before; before = "";
while(!Hlp_StrCmp(haystack, before));
before = haystack;
haystack = STR_ReplaceOnce(before, needle, replace);
end;
return haystack;
};
/*
* Complement to STR_Prefix in Ikarus
*/
func string STR_Postfix(var string str, var int off) {
return STR_SubStr(str, off, STR_Len(str)-off);
};
/*
* Retrieve the string position (starting at 0) of the first non-numeric character
*/
func int STR_IndexOfFirstNonNumeric(var string str) {
var int len; len = STR_Len(str);
var int buf; buf = STR_toChar(str);
var int valid; valid = FALSE;
var int index; index = 0;
while(index < len);
var int chr; chr = MEM_ReadInt(buf + index) & 255;
if (chr >= 48 /* 0 */) && (chr <= 57 /* 9 */) {
valid = TRUE;
} else if ((chr != 45 /*-*/) && (chr != 43 /*+*/)) || (index != 0) {
// Allow leading plus/minus, e.g. -5
break;
};
index += 1;
end;
// "-" is not a number
if (!valid) {
index = 0;
};
return index;
};
/*
* Retrieve a string trimmed from left by all characters in tok
* E.g. (" .. hello .. ", ". ") -> "hello .. "
*/
func string STR_TrimL(var string str, var string tok) {
var int lenS; lenS = STR_Len(str);
var int lenT; lenT = STR_Len(tok);
// Start from the beginning
var int startP; startP = 0;
while(startP < lenS);
var string ss; ss = STR_Substr(str, startP, 1);
var int cont; cont = FALSE;
var int t; t = 0;
while(t < lenT);
var string ts; ts = STR_Substr(tok, t, 1);
if (Hlp_StrCmp(ss, ts)) {
cont = TRUE;
break;
};
t += 1;
end;
if (!cont) {
break;
};
startP += 1;
end;
if (startP >= lenS) {
return "";
} else {
return STR_Substr(str, startP, lenS-startP);
};
};
/*
* Retrieve a string trimmed from right by all characters in tok
* E.g. (" .. hello .. ", ". ") -> " .. hello"
*/
func string STR_TrimR(var string str, var string tok) {
var int lenS; lenS = STR_Len(str);
var int lenT; lenT = STR_Len(tok);
// Start from the end
var int endP; endP = lenS-1;
while(endP >= startP);
var string ss; ss = STR_Substr(str, endP, 1);
cont = FALSE;
t = 0;
while(t < lenT);
var string ts; ts = STR_Substr(tok, t, 1);
if (Hlp_StrCmp(ss, ts)) {
cont = TRUE;
break;
};
t += 1;
end;
if (!cont) {
break;
};
endP -= 1;
end;
// Convert offset to length (0 -> 1, 1 -> 2, ...)
endP += 1;
if (0 >= endP) {
return "";
} else {
return STR_Substr(str, 0, endP-0);
};
};
/*
* Retrieve a string trimmed from left AND right by all characters in tok
* E.g. (" .. hello .. ", ". ") -> "hello"
*/
func string STR_Trim(var string str, var string tok) {
str = STR_TrimL(str, tok);
str = STR_TrimR(str, tok);
return str;
};
/*
* Trim string from left of a certain char
*/
func string STR_TrimCharL(var string str, var int chr) {
var int sPtr; sPtr = _@s(str);
const int zSTRING__TrimLeft_G1 = 4617312; //0x467460
const int zSTRING__TrimLeft_G2 = 4638256; //0x46C630
const int call = 0;
if (CALL_Begin(call)) {
CALL_IntParam(_@(chr));
CALL__thiscall(_@(sPtr), MEMINT_SwitchG1G2(zSTRING__TrimLeft_G1, zSTRING__TrimLeft_G2));
call = CALL_End();
};
return str;
};
/*
* Trim string from right of a certain char
*/
func string STR_TrimCharR(var string str, var int chr) {
var int sPtr; sPtr = _@s(str);
const int zSTRING__TrimRight_G1 = 4617632; //0x4675A0
const int zSTRING__TrimRight_G2 = 4638576; //0x46C770
const int call1 = 0;
if (CALL_Begin(call1)) {
CALL_IntParam(_@(chr));
CALL__thiscall(_@(sPtr), MEMINT_SwitchG1G2(zSTRING__TrimRight_G1, zSTRING__TrimRight_G2));
call1 = CALL_End();
};
return str;
};
/*
* Trim string from left AND right of a certain char
*/
func string STR_TrimChar(var string str, var int chr) {
str = STR_TrimCharL(str, chr);
str = STR_TrimCharR(str, chr);
return str;
};
/*
* Convert string to IEEE754 32-bit float
*/
func int STR_ToFloat(var string str) {
var zString zStr; zStr = _^(_@s(str));
var int ptr; ptr = zStr.ptr;
var int f;
var int fPtr; fPtr = _@(f);
const int _atoflt_G1 = 7876596; //0x782FF4
const int _atoflt_G2 = 8243410; //0x7DC8D2
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(ptr));
CALL_PtrParam(_@(fPtr));
CALL_PutRetValTo(0);
CALL__cdecl(MEMINT_SwitchG1G2(_atoflt_G1, _atoflt_G2));
call = CALL_End();
};
return f;
};
convert.d – Number conversion functions (hex to dec, dec to bin, dec to hex, ...)
/*
* convert.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717007
*
* Number conversion functions
*
* - Requires Ikarus>=1.2.2, strings.d
* - Compatible with Gothic 1 and Gothic 2
*
*
* func string dec2hex(int dec)
* func int hex2dec(string hex)
*
* func string dec2bin(int dec)
* func int bin2dec(string bin)
*
* func string hex2bin(string hex)
* func string bin2hex(string bin)
*
* func int h(string hex) Shorter alias for hex2dec
* func int b(string bin) Shorter alias for bin2dec
*/
/*
* Fix function from Ikarus: Based on MEMINT_ByteToKeyHex
*/
func string byte2hex(var int byte) {
const int ASCII_0 = 48;
const int ASCII_A = 65;
byte = byte & 255;
// Fix ASCII characters (A to F)
var int c1; c1 = (byte >> 4);
if (c1 >= 10) {
c1 += ASCII_A-ASCII_0-10;
};
var int c2; c2 = (byte & 15);
if (c2 >= 10) {
c2 += ASCII_A-ASCII_0-10;
};
const int mem = 0;
if (!mem) { mem = MEM_Alloc(3); };
MEM_WriteByte(mem , c1 + ASCII_0);
MEM_WriteByte(mem + 1, c2 + ASCII_0);
return STR_FromChar(mem);
};
/*
* Convert decimal to hexadecimal (big endian)
*/
func string dec2hex(var int dec) {
var string hex; hex = "";
hex = ConcatStrings(hex, byte2hex(dec >> 24));
hex = ConcatStrings(hex, byte2hex(dec >> 16));
hex = ConcatStrings(hex, byte2hex(dec >> 8));
hex = ConcatStrings(hex, byte2hex(dec));
return hex;
};
/*
* Convert hexadecimal to decimal (big endian)
*/
func int hex2dec(var string hex) {
var zString zStr; zStr = _^(_@s(hex));
if (!zStr.len) {
return 0;
};
// Remove 0x prefix and h postfix
hex = STR_Lower(hex);
if (Hlp_StrCmp(STR_Prefix(hex, 2), "0x")) {
hex = STR_SubStr(hex, 2, zStr.len-2);
} else if (MEM_ReadByte(zStr.ptr+zStr.len-1) == /*h*/ 104) {
hex = STR_Prefix(hex, zStr.len-1);
};
// Remove any spaces
hex = STR_ReplaceAll(hex, " ", "");
// Check length
if (zStr.len > 8) {
MEM_Error("hex2dec: Hexadecimal number to big. Considering the last 4 bytes only.");
hex = STR_Postfix(hex, zStr.len-8);
};
// Iterate over all characters (from back to front)
var int dec; dec = 0;
repeat(i, zStr.len); var int i;
dec += MEMINT_HexCharToInt(MEM_ReadByte(zStr.ptr+(zStr.len-1)-i)) << 4*i;
end;
return dec;
};
func int h(var string hex) {
return hex2dec(hex);
};
/*
* Convert decimal to binary
*/
func string dec2bin(var int dec) {
var string bin; bin = "";
repeat(i, 32); var int i;
if (dec & (1 << i)) {
bin = ConcatStrings("1", bin);
} else {
bin = ConcatStrings("0", bin);
};
end;
return bin;
};
/*
* Convert hexadecimal to binary
*/
func string hex2bin(var string hex) {
return dec2bin(hex2dec(hex));
};
/*
* Convert binary to decimal
*/
func int bin2dec(var string bin) {
var zString zStr; zStr = _^(_@s(bin));
if (!zStr.len) {
return 0;
};
// Remove any spaces
bin = STR_ReplaceAll(bin, " ", "");
// Check length
if (zStr.len > 32) {
MEM_Error("bin2dec: Binary number to big. Considering the last 32 bits only.");
bin = STR_Postfix(bin, zStr.len-32);
};
// Iterate over all characters (from back to front)
const int ASCII_0 = 48;
const int ASCII_1 = 49;
var int dec; dec = 0;
repeat(i, zStr.len); var int i;
var int c; c = MEM_ReadByte(zStr.ptr+(zStr.len-1)-i);
if (c < ASCII_0) || (c > ASCII_1) {
MEM_Error(ConcatStrings("bin2dec: Invalid binary char: ", STR_FromChar(_@(c))));
return 0;
};
dec += (c - ASCII_0) << i;
end;
return dec;
};
func int b(var string bin) {
return bin2dec(bin);
};
/*
* Convert binary to hexadecimal
*/
func string bin2hex(var string bin) {
return dec2hex(bin2dec(bin));
};
searchVobs.d – Search vobs by different criteria
/*
* searchVobs.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717007
*
* Search vobs by different criteria
*
* - Requires Ikarus, objCheckInheritance.d, insertAnything.d
* - Compatible with Gothic 1 and Gothic 2
*
* The function argument vobListPtr may always be zero, or the pointer to a previously created array.
* The return value is the number of found vobs.
*
*
* func int SearchVobsByClass (string className, int vobListPtr)
* func int SearchVobsByVisual (string visual, int vobListPtr)
* func int SearchVobsByProximity (int posPtr, int maxDist, int vobListPtr)
* func int SearchVobsByRemoteness(int posPtr, int minDist, int vobListPtr)
*/
/*
* Search all or given vobs by base class
*/
func int SearchVobsByClass(var string className, var int vobListPtr) {
const int zCWorld__SearchVobListByBaseClass_G1 = 6250016; //0x5F5E20
const int zCWorld__SearchVobListByBaseClass_G2 = 6439712; //0x624320
var zCArray vobList; vobList = _^(vobListPtr);
if (!vobList.numInArray) {
var int vobTreePtr; vobTreePtr = _@(MEM_Vobtree);
var int worldPtr; worldPtr = _@(MEM_World);
var int classDef; classDef = GetClassDefByString(className);
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(vobTreePtr));
CALL_PtrParam(_@(vobListPtr));
CALL_PtrParam(_@(classDef));
CALL__thiscall(_@(worldPtr), MEMINT_SwitchG1G2(zCWorld__SearchVobListByBaseClass_G1,
zCWorld__SearchVobListByBaseClass_G2));
call = CALL_End();
};
} else {
// Iterate over all vobs and remove the ones not matching the criteria
var int i; i = 0;
while(i < vobList.numInArray);
var int vobPtr; vobPtr = MEM_ArrayRead(vobListPtr, i);
if (objCheckInheritance(vobPtr, classDef)) {
// Keep vob
i += 1;
} else {
// Otherwise remove vob from array
MEM_ArrayRemoveIndex(vobListPtr, i);
};
end;
};
return vobList.numInArray;
};
/*
* Search all or given vobs by visual
*/
func int SearchVobsByVisual(var string visual, var int vobListPtr) {
// Create vob list if empty
var zCArray vobList; vobList = _^(vobListPtr);
if (!vobList.numInArray) {
if (!SearchVobsByClass("zCVob", vobListPtr)) {
return 0;
};
};
// Iterate over all vobs and remove the ones not matching the criteria
var int i; i = 0;
while(i < vobList.numInArray);
var int vobPtr; vobPtr = MEM_ArrayRead(vobListPtr, i);
if (vobPtr) {
// Check for visual
var zCVob vob; vob = _^(vobPtr);
if (vob.visual) {
// Compare visual
var zCObject visualObj; visualObj = _^(vob.visual);
if (Hlp_StrCmp(visualObj.objectname, visual)) {
// Keep vob
i += 1;
continue;
};
};
};
// Otherwise remove vob from array
MEM_ArrayRemoveIndex(vobListPtr, i);
end;
return vobList.numInArray;
};
/*
* Search all or given vobs by proximity
*/
func int SearchVobsByProximity(var int posPtr, var int maxDist, var int vobListPtr) {
// Create vob list if empty
var zCArray vobList; vobList = _^(vobListPtr);
if (!vobList.numInArray) {
if (!SearchVobsByClass("zCVob", vobListPtr)) {
return 0;
};
};
var int pos[3];
MEM_CopyWords(posPtr, _@(pos), 3);
// Iterate over all vobs and remove the ones not matching the criteria
var int i; i = 0;
while(i < vobList.numInArray);
var int vobPtr; vobPtr = MEM_ArrayRead(vobListPtr, i);
if (vobPtr) {
// Check distance
var zCVob vob; vob = _^(vobPtr);
// Compute distance between vob and position
var int dist[3];
dist[0] = subf(vob.trafoObjToWorld[ 3], pos[0]);
dist[1] = subf(vob.trafoObjToWorld[ 7], pos[1]);
dist[2] = subf(vob.trafoObjToWorld[11], pos[2]);
var int distance;
distance = sqrtf(addf(addf(sqrf(dist[0]), sqrf(dist[1])), sqrf(dist[2])));
// Check if distance is with in maxDist
if (lef(distance, maxDist)) {
// Keep vob
i += 1;
continue;
};
};
// Otherwise remove vob from array
MEM_ArrayRemoveIndex(vobListPtr, i);
end;
return vobList.numInArray;
};
/*
* Search all or given vobs by remoteness
*/
func int SearchVobsByRemoteness(var int posPtr, var int minDist, var int vobListPtr) {
// Create vob list if empty
var zCArray vobList; vobList = _^(vobListPtr);
if (!vobList.numInArray) {
if (!SearchVobsByClass("zCVob", vobListPtr)) {
return 0;
};
};
var int pos[3];
MEM_CopyWords(posPtr, _@(pos), 3);
// Iterate over all vobs and remove the ones not matching the criteria
var int i; i = 0;
while(i < vobList.numInArray);
var int vobPtr; vobPtr = MEM_ArrayRead(vobListPtr, i);
if (vobPtr) {
// Check distance
var zCVob vob; vob = _^(vobPtr);
// Compute distance between vob and position
var int dist[3];
dist[0] = subf(vob.trafoObjToWorld[ 3], pos[0]);
dist[1] = subf(vob.trafoObjToWorld[ 7], pos[1]);
dist[2] = subf(vob.trafoObjToWorld[11], pos[2]);
var int distance;
distance = sqrtf(addf(addf(sqrf(dist[0]), sqrf(dist[1])), sqrf(dist[2])));
// Check if distance is beyond minDist
if (gf(distance, minDist)) {
// Keep vob
i += 1;
continue;
};
};
// Otherwise remove vob from array
MEM_ArrayRemoveIndex(vobListPtr, i);
end;
return vobList.numInArray;
};
mud-freak
24.01.2018, 17:23
sound.d – Sound and music functions
Hier ist die Funktion stopAllSounds, die bereits im ScriptBin ist, enthalten.
/*
* sound.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717008
*
* Sound and music functions
*
* - Requires Ikarus, LeGo (Anim8), searchVobs.d, insertAnything.d
* - Compatible with Gothic 1 and Gothic 2
* - Initialization with restoreMasterVolumeSoundsToDefault() and/or restoreVolumeMusicToDefault() recommended
*
* When using the setVolumne-functions or fadeOut-functions, make sure to call
* restoreMasterVolumeSoundsToDefault() and/or restoreVolumeMusicToDefault()
* in Init_Global to restore the volume on loading/new game!
*
* The volumes will always be reset to their defaults after loading a game. Thus, this script does not take care of
* maintaining the volume over saving and loading a game.
*
* Keep in mind, that you cannot call the fadeOut-functions from startup. LeGo has to be initialized first. The LeGo
* package GameState can compensate for that.
*
*
* func void stopAllSounds()
* func void fadeOutAllSounds(int fadeOutMS, int waitMS, int fadeInMS, int killSounds)
* func int getMasterVolumeSounds()
* func void setMasterVolumeSounds(int volume)
* func void restoreMasterVolumeSoundsToDefault()
*
* func void stopMusic()
* func void fadeOutMusic(int fadeOutMS, int waitMS, int fadeInMS, int killMusic)
* func void playMusic(string music, int transition)
* func int getVolumeMusic()
* func void setVolumeMusic(int volume)
* func void restoreVolumeMusicToDefault()
*
* func void setMusicZoneTheme(string vobName, string musicTheme)
* func void setCurrentMusicZoneTheme(string musicTheme)
* func string getCurrentMusicZoneTheme()
* func void setAllMusicZoneThemes(string musicTheme)
*/
/*
* Stop all effect and speech sounds of the game instantly
*/
func void stopAllSounds() {
MEM_InitAll();
const int zsound_G1 = 9236044; //0x8CEE4C
const int zsound_G2 = 10072124; //0x99B03C
const int zCSndSys_MSS__RemoveAllActiveSounds_G1 = 5112224; //0x4E01A0
const int zCSndSys_MSS__RemoveAllActiveSounds_G2 = 5167008; //0x4ED7A0
var int zsoundPtr; zsoundPtr = MEM_ReadInt(MEMINT_SwitchG1G2(zsound_G1, zsound_G2));
const int call = 0;
if (CALL_Begin(call)) {
CALL__thiscall(_@(zsoundPtr), MEMINT_SwitchG1G2(zCSndSys_MSS__RemoveAllActiveSounds_G1,
zCSndSys_MSS__RemoveAllActiveSounds_G2));
call = CALL_End();
};
};
/*
* Get master volume sound
*/
func int getMasterVolumeSounds() {
const int zCSndSys_MSS__prefs_G1 = 8837192; //0x86D848
const int zCSndSys_MSS__prefs_G2 = 9249512; //0x8D22E8
return MEM_ReadInt(MEMINT_SwitchG1G2(zCSndSys_MSS__prefs_G1, zCSndSys_MSS__prefs_G2));
};
/*
* Backup default master volume sound
*/
func int getMasterVolumeSoundsDefault() {
const int soundDefVol = 0;
if (!soundDefVol) {
soundDefVol = getMasterVolumeSounds();
};
return soundDefVol;
};
/*
* Set master volume of sounds
* Caution: This control differs from the volume settings in the menu, be sure to always reset it!
*/
func void setMasterVolumeSounds(var int volume) {
const int zsound_G1 = 9236044; //0x8CEE4C
const int zsound_G2 = 10072124; //0x99B03C
const int zCSndSys_MSS__SetMasterVolume_G1 = 5112544; //0x4E02E0
const int zCSndSys_MSS__SetMasterVolume_G2 = 5167328; //0x4ED8E0
const int mss32_AILptr_G1 = 8838412; //0x86DD0C
const int mss32_AILptr_G2 = 9251444; //0x8D2A74
// Prevent crash by checking if sound was initialized (if sound is disabled)
if (!MEMINT_SwitchG1G2(MEM_ReadInt(mss32_AILptr_G1), MEM_ReadInt(mss32_AILptr_G2))) {
return;
};
// Backup default volume before changing it
var int i; i = getMasterVolumeSoundsDefault();
var int zsoundPtr; zsoundPtr = MEM_ReadInt(MEMINT_SwitchG1G2(zsound_G1, zsound_G2));
const int call = 0;
if (CALL_Begin(call)) {
CALL_FloatParam(_@(volume));
CALL__thiscall(_@(zsoundPtr), MEMINT_SwitchG1G2(zCSndSys_MSS__SetMasterVolume_G1,
zCSndSys_MSS__SetMasterVolume_G2));
call = CALL_End();
};
};
/*
* Restore default sound master volume
* Call this function from Init_Global to ensure working sound after loading
*/
func void restoreMasterVolumeSoundsToDefault() {
setMasterVolumeSounds(getMasterVolumeSoundsDefault());
};
/*
* Fade out all sounds
* Leave sound muted indefinitely by setting fadeInMS to -1
*/
func void fadeOutAllSounds(var int fadeOutMS, var int waitMS, var int fadeInMS, var int killSounds) {
// Retrieve and backup master volume
var int sVol; sVol = getMasterVolumeSounds();
// Start fade out
var int a8; a8 = Anim8_NewExt(sVol, soundFadeHandler, killSounds, TRUE);
Anim8_RemoveIfEmpty(a8, TRUE);
Anim8(a8, FLOATNULL, fadeOutMS, A8_SlowEnd);
Anim8q(a8, FLOATNULL, waitMS, A8_Wait);
if (fadeInMS != -1) {
Anim8q(a8, sVol, fadeInMS, A8_SlowStart);
};
};
func void soundFadeHandler(var int killSounds, var int value) {
// Once quiet: Kill all sounds if desired
if (!value) && (killSounds) {
stopAllSounds();
};
setMasterVolumeSounds(value);
};
/*
* Stop the music instantly
*/
func void stopMusic() {
MEM_InitAll();
const int zmusic_G1 = 8836220; //0x86D47C
const int zmusic_G2 = 9248532; //0x8D1F14
const int zCMusicSys_DirectMusic__Stop_G1 = 5098480; //0x4DCBF0
const int zCMusicSys_DirectMusic__Stop_G2 = 5152544; //0x4E9F20
const int zCMusicSystem__s_musicSystemDisabled_G1 = 8836224; //0x86D480
const int zCMusicSystem__s_musicSystemDisabled_G2 = 9248536; //0x8D1F18
// Prevent crash by checking if music system was initialized (if music is disabled)
if (MEMINT_SwitchG1G2(MEM_ReadInt(zCMusicSystem__s_musicSystemDisabled_G1),
MEM_ReadInt(zCMusicSystem__s_musicSystemDisabled_G2))) {
return;
};
var int zmusicPtr; zmusicPtr = MEMINT_SwitchG1G2(MEM_ReadInt(zmusic_G1), MEM_ReadInt(zmusic_G2));
const int call = 0;
if (CALL_Begin(call)) {
CALL__thiscall(_@(zmusicPtr), MEMINT_SwitchG1G2(zCMusicSys_DirectMusic__Stop_G1,
zCMusicSys_DirectMusic__Stop_G2));
call = CALL_End();
};
};
/*
* Get music volume
*/
func int getVolumeMusic() {
const int zmusic_G1 = 8836220; //0x86D47C
const int zmusic_G2 = 9248532; //0x8D1F14
const int zCMusicSys_DirectMusic_volume_offset = 12; //0Ch same for G1 and G2
var int zmusicPtr; zmusicPtr = MEMINT_SwitchG1G2(MEM_ReadInt(zmusic_G1), MEM_ReadInt(zmusic_G2));
return MEM_ReadInt(zmusicPtr+zCMusicSys_DirectMusic_volume_offset);
};
/*
* Backup default music volume
*/
func int getVolumeMusicDefault() {
const int musicDefVol = 0;
if (!musicDefVol) {
musicDefVol = getVolumeMusic();
};
return musicDefVol;
};
/*
* Set volume of music
* Caution: This control differs from the volume settings in the menu, be sure to always reset it!
*/
func void setVolumeMusic(var int volume) {
const int zmusic_G1 = 8836220; //0x86D47C
const int zmusic_G2 = 9248532; //0x8D1F14
const int zCMusicSys_DirectMusic__SetVolume_G1 = 5098624; //0x4DCC80
const int zCMusicSys_DirectMusic__SetVolume_G2 = 5152720; //0x4E9FD0
const int zCMusicSystem__s_musicSystemDisabled_G1 = 8836224; //0x86D480
const int zCMusicSystem__s_musicSystemDisabled_G2 = 9248536; //0x8D1F18
// Prevent crash by checking if music system was initialized (if music is disabled)
if (MEMINT_SwitchG1G2(MEM_ReadInt(zCMusicSystem__s_musicSystemDisabled_G1),
MEM_ReadInt(zCMusicSystem__s_musicSystemDisabled_G2))) {
return;
};
// Backup default volume before changing it
var int i; i = getVolumeMusicDefault();
var int zmusicPtr; zmusicPtr = MEMINT_SwitchG1G2(MEM_ReadInt(zmusic_G1), MEM_ReadInt(zmusic_G2));
const int call = 0;
if (CALL_Begin(call)) {
CALL_FloatParam(_@(volume));
CALL__thiscall(_@(zmusicPtr), MEMINT_SwitchG1G2(zCMusicSys_DirectMusic__SetVolume_G1,
zCMusicSys_DirectMusic__SetVolume_G2));
call = CALL_End();
};
};
/*
* Restore default music volume
* Call this function from Init_Global to ensure working music after loading
*/
func void restoreVolumeMusicToDefault() {
setVolumeMusic(getVolumeMusicDefault());
};
/*
* Fade out the music
* Leave music muted indefinitely by setting fadeInMS to -1
*/
func void fadeOutMusic(var int fadeOutMS, var int waitMS, var int fadeInMS, var int killMusic) {
// Retrieve and backup music volume
var int mVol; mVol = getVolumeMusic();
// Start fade out
var int a8; a8 = Anim8_NewExt(mVol, musicFadeHandler, killMusic, TRUE);
Anim8_RemoveIfEmpty(a8, TRUE);
Anim8(a8, FLOATNULL, fadeOutMS, A8_SlowEnd);
Anim8q(a8, FLOATNULL, waitMS, A8_Wait);
if (fadeInMS != -1) {
Anim8q(a8, mVol, fadeInMS, A8_SlowStart);
};
};
func void musicFadeHandler(var int killMusic, var int value) {
// Once quiet: Kill music if desired
if (!value) && (killMusic) {
stopMusic();
};
setVolumeMusic(value);
};
/*
* Play music by script instance, e.g. "XAR_Day_Std"
* The second argument may be either be 0 (musical transition?), 1 (?) or 2 (some thing like fade out/fade in?)
*/
func void playMusic(var string music, var int transition) {
const int zmusic_G1 = 8836220; //0x86D47C
const int zmusic_G2 = 9248532; //0x8D1F14
const int zCMusicSys_DirectMusic__PlayThemeByScript_G1 = 5093456; //0x4DB850
const int zCMusicSys_DirectMusic__PlayThemeByScript_G2 = 5147312; //0x4E8AB0
var int zmusicPtr; zmusicPtr = MEMINT_SwitchG1G2(MEM_ReadInt(zmusic_G1), MEM_ReadInt(zmusic_G2));
var int musicPtr; musicPtr = _@s(music);
var int zero;
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(zero)); // Some pointer
CALL_IntParam(_@(transition)); // zTMus_TransType: either 0, 1 or 2
CALL_PtrParam(_@(musicPtr));
CALL__thiscall(_@(zmusicPtr), MEMINT_SwitchG1G2(zCMusicSys_DirectMusic__PlayThemeByScript_G1,
zCMusicSys_DirectMusic__PlayThemeByScript_G2));
call = CALL_End();
};
};
/*
* Overwrite the music theme of a music zone
*
* This function will permanently change the music theme for a certain music zone. This change is preserved over
* saving/loading, but not across saves.
* The first parameter is the object name of the oCZoneMusic vob.
* The second parameter is the new post fix identifying the music theme, e.g. "XAR"
*
* The transition in music will be instant (given that the player is in the affected zone).
*/
func void setMusicZoneTheme(var string vobName, var string musicTheme) {
// Find all vobs of this zone, there are multiple!
var int arrPtr; arrPtr = MEM_SearchAllVobsByName(vobName);
// Build new vob name
var string newName; newName = ConcatStrings(STR_Prefix(vobName, STR_Len(vobName)-3), musicTheme);
// Iterate over all music zones and exchange their identifier
var zCArray arr; arr = _^(arrPtr);
repeat(j, arr.numInArray); var int j;
var int vobPtr; vobPtr = MEM_ArrayRead(arrPtr, j);
MEM_RenameVob(vobPtr, newName);
end;
// Free array
MEM_ArrayFree(arrPtr);
};
/*
* Overwrite the music theme of the current music zone
*/
func void setCurrentMusicZoneTheme(var string musicTheme) {
if (!MEM_ReadInt(oCZoneMusic__s_musiczone_Address)) {
MEM_Warn("No music zone active. No changes performed.");
return;
};
// Not only change the current oCZoneMusic, but all matching ones in the area
var zCObject vob; vob = _^(MEM_ReadInt(oCZoneMusic__s_musiczone_Address));
setMusicZoneTheme(vob.objectName, musicTheme);
};
/*
* Get music theme of current music zone (as a prior backup for setCurrentMusicZoneTheme above)
*/
func string getCurrentMusicZoneTheme() {
if (!MEM_ReadInt(oCZoneMusic__s_musiczone_Address)) {
MEM_Warn("No music zone active.");
return "";
};
var zCObject vob; vob = _^(MEM_ReadInt(oCZoneMusic__s_musiczone_Address));
return STR_SubStr(vob.objectName, STR_Len(vob.objectName)-3, 3);
};
/*
* Overwrite the music theme of all music zones
*/
func void setAllMusicZoneThemes(var string musicTheme) {
var int arrPtr; arrPtr = MEM_ArrayCreate();
if (SearchVobsByClass("oCZoneMusic", arrPtr)) {
// Iterate over all oCZoneMusic vobs
var zCArray arr; arr = _^(arrPtr);
repeat(i, arr.numInArray); var int i;
var int vobPtr; vobPtr = MEM_ArrayRead(arrPtr, i);
var zCObject vob; vob = _^(vobPtr);
// Build new vob name
var string vobName; vobName = vob.objectName;
var string newName; newName = ConcatStrings(STR_Prefix(vobName, STR_Len(vobName)-3), musicTheme);
// Exchange music theme identifier
MEM_RenameVob(vobPtr, newName);
end;
};
// Free array
MEM_ArrayFree(arrPtr);
};
hidePlayerStatus.d – Simple script to remove all player info on-screen continuously until showing it again
Dies ist eine Alternative zu hideBars, die im Gegensatz dazu alle on-screen Informationen dauerhaft ausblendet (ausgenommen von Views). Daher ist sie eine bessere Grundlage für ScreenBlend.d
/*
* hidePlayerStatus.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717008
*
* Simple script to remove all player info on-screen continuously until showing it again.
*
* - Requires Ikarus, LeGo (FrameFunctions)
* - Compatible with Gothic 1 and Gothic 2
*
* The info includes health bar, mana bar, swim bar, focus bar and focus names. This change is preserved over saving and
* loading, but not across saves/starting a new game.
*
*
* hidePlayerStatus() once to hide all information and keep it hidden.
* showPlayerStatus() once to show all again.
*/
/*
* Internal wrapper for oCGame::SetShowPlayerStatus
* This engine function merely sets ogame->showPlayerStatus to zero and removes all bar-view-items from screen.
*/
func void setShowPlayerStatus(var int on) {
const int oCGame__SetShowPlayerStatus_G1 = 6523872; //0x638BE0
const int oCGame__SetShowPlayerStatus_G2 = 7089552; //0x6C2D90
var int gamePtr; gamePtr = _@(MEM_Game);
const int call = 0;
if (CALL_Begin(call)) {
CALL_IntParam(_@(on));
CALL__thiscall(_@(gamePtr), MEMINT_SwitchG1G2(oCGame__SetShowPlayerStatus_G1, oCGame__SetShowPlayerStatus_G2));
call = CALL_End();
};
};
func void hidePlayerStatus() {
setShowPlayerStatus(0); // Remove all on screen info
FF_ApplyOnce(_hidePlayerStatus); // And then never draw it again
};
func void showPlayerStatus() {
if (InfoManager_hasFinished()) {
setShowPlayerStatus(1);
};
FF_Remove(_hidePlayerStatus);
};
func void _hidePlayerStatus() {
if (MEM_Game.showPlayerStatus) {
// Will be called sparingly; only after exiting the main menu and after dialogs
setShowPlayerStatus(0);
};
};
/*
All of the above could be done more elegantly and performant without FrameFunctions by modifying the setter function.
However, this would not allow to maintain the visibility state across saving and loading. For completeness, that
approach is shown below.
func void forceHidePlayerStatus(var int on) {
const int set = 0;
if (set == on) {
return;
};
const int oCGame__SetShowPlayerStatus_G1 = 6523872; //0x638BE0
const int oCGame__SetShowPlayerStatus_G2 = 7089552; //0x6C2D90
var int addr; addr = MEMINT_SwitchG1G2(oCGame__SetShowPlayerStatus_G1, oCGame__SetShowPlayerStatus_G2)+2;
if (on) {
// Overwrite to hide always: replace argument with zero
MemoryProtectionOverride(addr, 4);
MEM_WriteInt(addr, -1869545677); //33 FF 90 90 xor edi, edi
} else {
// Reset to default
MEM_WriteInt(addr, 203717771); //8B 7C 24 0C mov edi, [esp+8+4]
};
set = on;
}; */
blockAllPlayerInput.d – Block all player input with or without blocking the main menu as well
/*
* blockAllPlayerInput.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717008
*
* Block all player input with or without blocking the main menu as well.
*
* - Requires Ikarus, LeGo>=2.5.0 (HookEngine, [optional] Cursor)
* - Compatible with Gothic 1 and Gothic 2
* - Resetting with unblockAllPlayerInput() in Init_Global recommended to restore player input on loading/new game!
*
* Blocking the main menu is not recommended and should only be done in highly controlled cases!
*
*
* Main functions to use:
* blockAllPlayerInput(int blockGameMenu) once to block all player input and keep it blocked.
* unblockAllPlayerInput() once to unblock all again.
*
* Sub-functions (called from the functions above):
* blockInGameMenus(int on)
* blockMainMenu(int on)
* blockHotkeys(int on)
* blockControls(int on)
* blockMouse(int on)
*/
/*
* Block all in-game menus (status screen, log screen, map)
*/
func void blockInGameMenus(var int on) {
const int set = 0;
if (set == on) {
return;
};
const int oCGame__HandleEvent_G1 = 6680288; //0x65EEE0
const int oCGame__HandleEvent_G2 = 7324016; //0x6FC170
var int addr; addr = MEMINT_SwitchG1G2(oCGame__HandleEvent_G1, oCGame__HandleEvent_G2);
if (on) {
ReplaceEngineFuncF(addr, 1, Hook_ReturnFalse);
} else {
RemoveHookF(addr, 5, Hook_ReturnFalse);
if (!IsHooked(addr)) {
MEM_WriteInt(addr, MEMINT_SwitchG1G2(/*0xA164*/ 41316, /*0x8568FF6A*/ 2238250858));
};
};
set = on;
};
/*
* Block game menu (ESC key). This actually also disables the hot keys
*/
func void blockMainMenu(var int on) {
const int set = 0;
if (set == on) {
return;
};
const int cGameManager__HandleEvent_G1 = 4363200; //0x4293C0
const int cGameManager__HandleEvent_G2 = 4369744; //0x42AD50
var int addr; addr = MEMINT_SwitchG1G2(cGameManager__HandleEvent_G1, cGameManager__HandleEvent_G2);
if (on) {
ReplaceEngineFuncF(addr, 1, Hook_ReturnFalse);
} else {
RemoveHookF(addr, 5, Hook_ReturnFalse);
if (!IsHooked(addr)) {
MEM_WriteInt(addr, MEMINT_SwitchG1G2(/*0xD98B5351*/ 3649786705, /*0xA164*/ 41316));
};
};
set = on;
};
/*
* Disable hot keys
*/
func void blockHotkeys(var int on) {
const int set = 0;
if (set == on) {
return;
};
const int cGameManager__HandleEvent_quickload_G1 = 4363453; //0x4294BD
const int cGameManager__HandleEvent_quicksave_G1 = 4363312; //0x429430
const int oCGame__s_bUsePotionKeys_G2 = 9118156; //0x8B21CC
const int oCGame__s_bUseQuickSave_G2 = 9118160; //0x8B21D0
const int cGameManager__HandleEvent_F9keyJZ_G2 = 4369832; //0x42ADA8
const int enabled_G2 = 0;
if (on) {
if (GOTHIC_BASE_VERSION == 1) {
// Disable quick saving
MemoryProtectionOverride(cGameManager__HandleEvent_quicksave_G1, 1);
MEM_WriteByte(cGameManager__HandleEvent_quicksave_G1, /*EB short jmp*/ 235);
// Disable quick loading
MemoryProtectionOverride(cGameManager__HandleEvent_quickload_G1, 1);
MEM_WriteByte(cGameManager__HandleEvent_quickload_G1, /*EB short jmp*/ 235);
} else {
// Back up if they were enabled beforehand
enabled_G2 = enabled_G2 | (MEM_ReadInt(oCGame__s_bUsePotionKeys_G2) << 0);
enabled_G2 = enabled_G2 | (MEM_ReadInt(oCGame__s_bUseQuickSave_G2) << 1);
// Disabled them
MEM_WriteInt(oCGame__s_bUsePotionKeys_G2, 0);
MEM_WriteInt(oCGame__s_bUseQuickSave_G2, 0);
// Quick loading is always possible due to a logic mistake in the engine
MemoryProtectionOverride(cGameManager__HandleEvent_F9keyJZ_G2, 4);
MEM_WriteInt(cGameManager__HandleEvent_F9keyJZ_G2, 995); // jump beyond broken logic: 4370831-(4369830+6)
};
} else {
if (GOTHIC_BASE_VERSION == 1) {
MEM_WriteByte(cGameManager__HandleEvent_quicksave_G1, /*74 short jz*/ 116);
MEM_WriteByte(cGameManager__HandleEvent_quickload_G1, /*74 short jz*/ 116);
} else {
// Re-enabled if they were enabled beforehand
MEM_WriteInt(oCGame__s_bUsePotionKeys_G2, (enabled_G2 & 1));
MEM_WriteInt(oCGame__s_bUseQuickSave_G2, (enabled_G2 >> 1));
// Re-instate original, broken logic
MEM_WriteInt(cGameManager__HandleEvent_F9keyJZ_G2, 237); //ED 00 00 00
};
};
set = on;
};
/*
* Remove player control (essentially turns the hero AI into an NPC AI)
*/
func void blockControls(var int on) {
const int set = 0;
if (set == on) {
return;
};
const int oCAIHuman__DoAI_player_G1 = 6381143; //0x615E57
const int oCAIHuman__DoAI_player_G2 = 6930571; //0x69C08B
const int oCNpc__CanDrawWeapon_G1 = 7647728; //0x74B1F0
const int oCNpc__CanDrawWeapon_G2 = 6817216; //0x6805C0
var int doAIplayerAddr; doAIplayerAddr = MEMINT_SwitchG1G2(oCAIHuman__DoAI_player_G1, oCAIHuman__DoAI_player_G2);
var int canDrawWeaponAddr; canDrawWeaponAddr = MEMINT_SwitchG1G2(oCNpc__CanDrawWeapon_G1, oCNpc__CanDrawWeapon_G2);
if (on) {
// Detach player AI
MemoryProtectionOverride(doAIplayerAddr, 5);
MEM_WriteByte(doAIplayerAddr, MEMINT_SwitchG1G2(/*EB short jmp*/ 235, /*long jmp*/ ASMINT_OP_jmp));
if (GOTHIC_BASE_VERSION == 2) {
MEM_WriteInt(doAIplayerAddr+1, 432); // Jump to 0x69C240: 6931008-6930571-5
};
// Block combat keys (1-0)
ReplaceEngineFuncF(canDrawWeaponAddr, 0, Hook_ReturnFalse);
} else {
// Restore player AI
MEM_WriteByte(doAIplayerAddr, MEMINT_SwitchG1G2(/*75 jnz*/ 117, /*0F jne*/ 15));
if (GOTHIC_BASE_VERSION == 2) {
MEM_WriteInt(doAIplayerAddr+1, 110469); //0x01AF85
};
// Re-instate combat keys (1-0)
RemoveHookF(canDrawWeaponAddr, 5, Hook_ReturnFalse);
if (!IsHooked(canDrawWeaponAddr)) {
MEM_WriteInt(canDrawWeaponAddr, /*0xE8F18B56*/ -386823338);
};
};
set = on;
};
/*
* Backup mouse enable state and then disable it (requires LeGo_Cursor)
*/
func void blockMouse(var int on) {
Cursor_NoEngine = on;
};
/*
* Block all player input with or without blocking the game menu (not recommended)
*/
func void blockAllPlayerInput(var int blockGameMenu) {
MEM_SendToSpy(zERR_TYPE_WARN, "Blocking all player input");
blockInGameMenus(1);
blockHotkeys(1);
blockControls(1);
blockMouse(1);
if (blockGameMenu) {
// Kenny Loggins was here in 1986
blockMainMenu(1);
};
};
/*
* Re-enable all player input
*/
func void unblockAllPlayerInput() {
MEM_SendToSpy(zERR_TYPE_WARN, "Unblocking all player input");
blockInGameMenus(0);
blockHotkeys(0);
blockControls(0);
blockMouse(0);
blockMainMenu(0);
};
mud-freak
24.01.2018, 17:23
screenFade.d – This script is a modified version of ScreenFade.d from ScriptBin written by Lehona (et al.)
Das Skript habe ich etwas geändert um u.A. das neue hidePlayerStatus einzubauen, um auch Fokusnamen zu verstecken.
/*
* screenFade.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717013
*
* This script is a modified version of ScreenFade.d from ScriptBin written by Lehona (et al.)
*
* - Requires Ikarus, LeGo>=2.5.1 (Anim8), hidePlayerStatus.d
* - Compatible with Gothic 1 and Gothic 2
*
* Changes include:
* - Substitution of hideManaBar with hidePlayerStatus
* - Usage of OnRemove-Callback available in Anim8 after LeGo 2.5.0
* - Possibility to pass an existing zCView handle to the function (may be 0)
* - Possibility to keep screen faded indefinitely (if fadeOutMS == -1), if so, returns zCView handle, 0 otherwise
*
*
* func void ScreenFade ( int fadeInMS, int waitInMS, int fadeOutMS) // Usage as before
* func int ScreenFadeExt(int view, int fadeInMS, int waitInMS, int fadeOutMS) // Extended usage
*/
func int ScreenFadeExt(var int view, var int fadeInMS, var int waitInMS, var int fadeOutMS) {
hidePlayerStatus();
// Allow existing view to be used
if (!Hlp_IsValidHandle(view)) {
view = View_Create(0, 0, PS_VMAX, PS_VMAX);
View_SetTexture(view, "default.tga");
View_SetColor(view, 0);
View_Open(view);
};
var int a8; a8 = Anim8_NewExt(0, ScreenFadeHandler, view, false);
Anim8_RemoveIfEmpty(a8, true);
if (fadeOutMS != -1) {
Anim8_RemoveDataIfEmpty(a8, true);
Anim8_CallOnRemove(a8, TurnScreenBackOn);
};
Anim8(a8, 255, fadeInMS, A8_Constant);
Anim8q(a8, 255, waitInMS, A8_Wait);
if (fadeOutMS != -1) {
Anim8q(a8, 0, fadeOutMS, A8_Constant);
};
// Return view handle
if (fadeOutMS == -1) {
return view;
} else {
return -1;
};
};
func void ScreenFade(var int fadeInMS, var int waitInMS, var int fadeOutMS) {
var int i; i = ScreenFadeExt(0, fadeInMS, waitInMS, fadeOutMS);
};
func void ScreenFadeHandler(var int view, var int alpha) {
View_SetColor(view, RGBA(0, 0, 0, alpha));
};
func void TurnScreenBackOn() {
showPlayerStatus();
};
rollCredits.d – Roll credits, including screen blend/fade, music and blocking of user input with optional subsequent function call
Mit dieses Skript erübrigt sich das lästige Credits-Video schneiden und neu rendern, wenn doch noch ein Schreibfehler auftaucht. Mit Screenblende, Hintergrundmusik, ... Dazu hier ein Beispielvideo (https://youtu.be/7OxAyEbobgw).
/*
* rollCredits.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25717013
*
* Roll credits, including screen blend/fade, music and blocking of user input with optional subsequent function call.
*
* - Requires Ikarus, LeGo>=2.5.1 (View, Anim8), screenFade.d, blockAllPlayerInput.d, sound.d
* - Compatible with Gothic 1 (untested) and Gothic 2
*
* Caution: The game will continue in the background. Also any FrameFunction and Anim8 instances. You have to stop them
* individually! Do not stop the Timer or set MEM_Game.singleStep, this will also stop the credits from rolling.
* It's best to teleport the player to a secluded location (a few seconds after starting the credits), to avoid any
* game sounds during the credits rolling.
*
* For an example see rollCreditsExample() at the bottom of this file.
*
*
* rollCredits (int linesPtr, int numLines, int fadeInMS, int durationMS, int fadeOutMS)
* rollCreditsF (int linesPtr, int numLines, int fadeInMS, int durationMS, int fadeOutMS, func postCreditFunc)
* rollCreditsM (int linesPtr, int numLines, int fadeInMS, int durationMS, int fadeOutMS, string music)
* rollCreditsFM(int linesPtr, int numLines, int fadeInMS, int durationMS, int fadeOutMS, func postCreditFunc, string music)
*/
/*
* Variables and constants
*/
const string ROLLCREDITS_SKIP_MESSAGE = "[ESC] to skip";
var int rollCreditsTextHeight;
var int rollCreditsPostFunc;
var string rollCreditsMusic;
var string rollCreditsCurrentMusicTheme;
/*
* [Internal] Roll credits machinery
*/
func void rollCreditsExt(var int linesPtr, var int numLines, var int fadeInMS, var int durationMS, var int fadeOutMS,
var int postCreditFunc, var string music) {
if (numLines < 1) || (!linesPtr) {
return;
};
// Build textlines
var string text; text = "";
repeat(i, numLines); var int i;
text = ConcatStrings(text, MEM_ReadStatStringArr(MEM_ReadString(linesPtr), i));
if (i < numLines) {
text = ConcatStrings(text, Print_LineSeperator);
};
end;
// Call screen fade before, because of view layering
ScreenFade(fadeInMS, durationMS+2000, fadeOutMS);
fadeOutAllSounds(fadeInMS, 0, 0, TRUE);
fadeOutMusic(fadeInMS, 0, 0, TRUE);
rollCreditsCurrentMusicTheme = getCurrentMusicZoneTheme();
setCurrentMusicZoneTheme("XXX"); // Has to be three characters long
// Create TextField
var int y; y = PS_VMAX;
var int hi; hi = Print_GetFontHeight(PF_Font);
rollCreditsTextHeight = Print_ToVirtual(hi, (hi*numLines+2));
var int txt; txt = Print_TextField(0, y, text, PF_Font, rollCreditsTextHeight);
// Create view to hold the text
var int viewHndl; viewHndl = View_Create(0, 0, PS_VMAX, PS_VMAX);
var zCView view; view = get(viewHndl);
view.textLines_next = txt;
ViewPtr_AlignText(getPtr(viewHndl), 0);
View_Open(viewHndl);
// Animate text
var int a8; a8 = Anim8_NewExt(y, rollCreditsHandler, viewHndl, FALSE);
Anim8_RemoveIfEmpty(a8, TRUE);
Anim8_RemoveDataIfEmpty(a8, TRUE);
Anim8_CallOnRemove(a8, rollCreditsPost);
rollCreditsPostFunc = postCreditFunc;
Anim8(a8, y, fadeInMS+1000, A8_Wait);
Anim8q(a8, -(rollCreditsTextHeight*(numLines+1)), durationMS, A8_Constant);
Anim8q(a8, -(rollCreditsTextHeight*(numLines+1)), 500, A8_Wait);
// Block all player input (after fade in)
FF_ApplyExtData(rollCreditsPre1, fadeInMS, 1, viewHndl);
if (!Hlp_StrCmp(music, "")) {
rollCreditsMusic = music;
FF_ApplyExt(rollCreditsPre2, fadeInMS+500, 1);
};
};
/*
* Callable functions
*/
func void rollCredits(var int linesPtr, var int numLines, var int fadeInMS, var int durationMS, var int fadeOutMS) {
rollCreditsExt(linesPtr, numLines, fadeInMS, durationMS, fadeOutMS, /*NOFUNC*/ -1, "");
};
func void rollCreditsF(var int linesPtr, var int numLines, var int fadeInMS, var int durationMS, var int fadeOutMS,
var func postCreditFunc) {
rollCreditsExt(linesPtr, numLines, fadeInMS, durationMS, fadeOutMS, MEM_GetFuncID(postCreditFunc), "");
};
func void rollCreditsM(var int linesPtr, var int numLines, var int fadeInMS, var int durationMS, var int fadeOutMS,
var string music) {
rollCreditsExt(linesPtr, numLines, fadeInMS, durationMS, fadeOutMS, /*NOFUNC*/ -1, music);
};
func void rollCreditsFM(var int linesPtr, var int numLines, var int fadeInMS, var int durationMS, var int fadeOutMS,
var func postCreditFunc, var string music) {
rollCreditsExt(linesPtr, numLines, fadeInMS, durationMS, fadeOutMS, MEM_GetFuncID(postCreditFunc), music);
};
/*
* Anim8 handler to scroll the text
*/
func void rollCreditsHandler(var int viewHndl, var int yPos) {
var zCView view; view = get(viewHndl);
var int lp; lp = view.textLines_next;
var int i; i = 1;
while(lp);
var zCList l; l = _^(lp);
var zCViewText vt; vt = _^(l.data);
// Exclude skip message
if (!Hlp_StrCmp(vt.text, ROLLCREDITS_SKIP_MESSAGE)) {
vt.posy = yPos+(rollCreditsTextHeight*i);
i += 1;
};
lp = l.next;
end;
};
/*
* Functions called pre and post credits roll
*/
func void rollCreditsPre1(var int viewHndl) {
blockAllPlayerInput(1);
// Allow skipping
FF_ApplyData(rollCreditsCheckEsc, viewHndl);
};
func void rollCreditsPre2() {
playMusic(rollCreditsMusic, 2);
};
func void rollCreditsPost() {
unblockAllPlayerInput();
setCurrentMusicZoneTheme(rollCreditsCurrentMusicTheme);
if (!Hlp_StrCmp(rollCreditsCurrentMusicTheme, "")) {
playMusic(ConcatStrings(rollCreditsCurrentMusicTheme, "_DAY_STD"), 2);
};
FF_Remove(rollCreditsCheckEsc);
if (rollCreditsPostFunc > 0) {
MEM_CallByID(rollCreditsPostFunc);
rollCreditsPostFunc = 0;
};
};
/*
* For-each function to identify Anim8 and View to increase their velocity/timing
*/
func int rollCreditsSkip(var int hndl) {
// Quite ugly approach, but I don't have the handles
var A8Head h; h = get(hndl);
if (h.fnc != MEM_GetFuncPtr(rollCreditsHandler))
&& (h.fnc != MEM_GetFuncPtr(ScreenFadeHandler)) {
return rContinue;
};
var int ldata; ldata = List_Get(h.queue, 2);
var A8Command c; c = get(ldata);
c.startVal = h.value;
c.startTime = Timer();
if (h.fnc == MEM_GetFuncPtr(rollCreditsHandler)) {
c.timeSpan = mkf(1000);
} else {
c.timeSpan = mkf(2500);
};
_Anim8_SetVelo(h, c);
};
/*
* Frame function to check for escape key presses
*/
func void rollCreditsCheckEsc(var int viewHndl) {
// Watch the escape key
if (MEM_KeyState(KEY_ESCAPE) == KEY_PRESSED) {
var int lastKeyPress;
var int cTime; cTime = Timer();
// Reset lastKeyPress from previous credits
if (lastKeyPress >= cTime) {
lastKeyPress = 0;
};
var zCView view; view = get(viewHndl);
// Check if pressed twice within 2.0 seconds
if (lastKeyPress+2000 >= cTime) {
// Increase credits roll and fade speed
foreachHndl(A8Head@, rollCreditsSkip);
// Remove this very FF
FF_Remove(rollCreditsCheckEsc);
} else {
// Otherwise create skip message
var int ptr; ptr = Print_CreateTextPtrColored(ROLLCREDITS_SKIP_MESSAGE, PF_Font, -1);
var zCViewText txt; txt = _^(ptr);
var int width;
width = Print_ToVirtual(Print_GetStringWidthPtr(txt.text, txt.font), PS_X) * PS_VMAX / view.vsizex;
txt.posx = PS_VMAX - width - 200;
var int height;
height = Print_ToVirtual(Print_GetFontHeight(PF_Font), PS_Y) * PS_VMAX / view.vsizey;
txt.posy = PS_VMAX - height - 200;
txt.timed = TRUE;
txt.timer = mkf(2000);
// Add to view
List_Add(view.textLines_next, ptr);
};
// Update last key press
lastKeyPress = cTime;
};
};
/*
* Usage example function
*/
func void rollCreditsExample() {
const int lineNum = 16;
const string lines[lineNum] = {
"CREDITS",
"",
"",
"Scripter Person Person",
"Tester Person Person",
"Player Person Person",
"",
"ENGLSIH TRANSLATION",
"Translator Person Person",
"Tester Person Person",
"Player Person Person",
"",
"SPECIAL THANKS",
"Player for playing",
"",
"The Name of the Game"
};
// End game session after credits rolled
rollCreditsFM(_@s(lines), lineNum, 1500, 20000, 0, rollCreditsExample_after, "PIE_DAY_STD");
// OR: Continue game afterwards
//rollCreditsM(_@s(lines), lineNum, 1500, 20000, 1000, "PIE_DAY_STD");
};
func void rollCreditsExample_after() {
ExitSession();
};
Fisk2033
25.01.2018, 08:05
Wow, wirklich tolle Sachen dabei! Vielen Dank fürs teilen. :)
mud-freak
25.01.2018, 09:13
Die Skripte hier einfach abzuladen bringt sicher niemandem was. Für Leute die bisher auch gut ohne mögliche Hilfestellungen ausgekommen sind, möchte ich noch erklären, wo die nützlich sein können.
strings.d
Keine direkten Einsatzmöglichkeiten, ausser String-Manipulationen.
convert.d
Das Skript ist vorwiegend gut fürs Debuggen und Arbeiten mit Ikarus. Hier drei Beispiele:
Speicheradressen lassen sich direkt als Hex-Zahl in Skripte schreiben.
HookEngineF(h("0x68B840"), 6, hookingFunc); // Nur zum Testen so!
Das hat allerdings den massiven Nachteil der Unleserlichkeit. Damit entsteht ein Haufen von "Magicnumbers" die man nachher nicht mehr nachvollziehen kann. Für schnelle Tests, ob man die richtige Adresse erwischt hat, ist das aber genau das richtige. Anschliessend sollte man die Zahl als Konstante mit beschreibenden Namen speichern.
Folgendes Beispiel ist da besser:
MemoryProtectionOverride(addr, 1);
MEM_WriteByte(addr, h("6A"));
Hier interessiert niemanden, wie das Byte in Dezimaldarstellung aussieht und die Funktion erspart das manuelle "Umrechnen".
Die wohl sinnvollste Anwendung ist es, sich Bitfelder besser anzeigen zu lassen:
var zCVob oth; oth = _^(her.focus_vob);
oth.bitfield[0]; // 493 -> Welche bits sind das jetzt genau?
dec2bin(oth.bitfield[0]); // 00000000000000000000000111101101 -> Einfacher abzulesen und mit zCVob_bitfield0_* abzugleichen
searchVobs.d
MEM_SeachVobByName/MEM_SearchAllVobsByName aus Ikarus benutzt sicher jeder mal irgendwann. Die Funktionen hier geben noch mehr Suchkriterien um Vobs zu finden: Nach Klasse (z.B: "oCMobContainer"), nach Visual (z.B. "OW_LOB_TREE_V6.3DS"), in einem bestimmten Umkreis oder ausserhalb eines bestimmten Umkreises. Diese Funktionen lassen sich hintereinander verketten, um die Vobsuche noch weiter einzugrenzen (z.B. Container mit bestimmten Visual, in bestimmtem Radius von einer Koordinate). Zusammen mit dem insertAnything-Skript lassen sich damit viel Storybasierende Änderungen in der Welt vornehmen oder sogar einfach die Spacerarbeit minimieren.
var int vobArray; vobArray = MEM_ArrayCreate();
const float pos[3] = { 12525.603516, 1198.112549, -3076.614258 };
if (SearchVobsByClass("oCMobContainer", vobArray)) // Finde alle Container in der Welt
&& (SearchVobsByVisual("CHESTSMALL_NW_POOR_OPEN.MDS", vobArray)) // Grenze die Suche ein auf ein Visual
&& (SearchVobsByProximity(_@f(pos), mkf(1000), vobArray)) { // Grenze die Suche ein auf die Position
// Im vobArray sind nun nur noch alle Vobs, die mit den Kriterien übereinstimmen
// (Vielleicht wäre es mal Zeit für eine ForEach-Funktion für Arrays)
} else {
// Keine Vobs mit den Kriterien gefunden
};
MEM_ArrayFree(vobArray);
sound.d
Dieses Skript ist ein besonders gutes Instrument für Atmosphäre. Damit lassen sich alle Sounds als auch die Spielmusik aus-"faden", um z.B. ein Intro-Video oder andere Cutscene schöner heranzuführen. Ausserdem lassen sich auch Musikstücke gezielt abspielen oder ganze Musikzonen ändern (für einschneidende Storyereignisse). Ein gutes Beispiel ist das Aus-"faden" der Menu-Musik (wird als Sound abgespielt) beim Ende vom Laden für eine bessere Überleitung in das Spiel:
// In der Startup.d
func void Init_Global() {
// ...
// Menu-Musik (Sound) am Ende des Ladebildschirms aus-"faden"
fadeOutAllSounds(2500, 0, 1100, TRUE);
};
Wichtig zu Bedenken ist, dass die fadeOut-Funktionen nicht in der Startup benutzt werden können, da sie LeGo benötigen, was erst anschliessend in der Init_Global initialisiert wird. Um also den Menü-Sound vor einem Intro-Video verstummen zulassen, sollte man auf stopAllSounds() zurückgreifen.
Was sounds.d noch so alles hergibt, kann man sich im Skript rollCredits.d anschauen.
hidePlayerStatus.d
Dazu ist nicht viel zu sagen. In einigen Hinsichten nützlicher als das hideBars-Skript, in anderen nicht (wenn es z.B. darum geht einzelne Bars zu verstecken oder anzuzeigen).
hidePlayerStatus() muss nur einmal aufgerufen werden und alle Bars und Fokus-Informationen verschwinden und bleiben versteckt bis man showPlayerStatus() aufruft. Dieser "Zustand" wandert mit in den Speicherstand, was auch gut ist.
blockAllPlayerInput.d
Auch ziemlich selbsterklärend. Mit dem Aufruf von blockAllPlayerInput(0) werden alle Eingaben vom Spieler blockiert/ignoriert, bis unblockAllPlayerInput aufgerufen wird. Das Argument in blockAllPlayerInput(0) entscheidet, ob auch das Spielmenü (also Escape) blockiert werden soll. Darauf sollte möglichst verzichtet werden, dann wenn was schief läuft, kann der Spieler gar nichts mehr machen.
Der "Zustand" wandert nicht mit in den Speicherstand und bleibt über die Session hinweg bestehen, man sollte also in der Init_Global immer unblockAllPlayerInput() aufrufen.
Das Skript bietet auch einzelne Sub-Funktionen an, um bspw. nur alle InGame-Menüs oder nur die Spielersteuerung zu blockieren.
rollCredits.d
Dieses Skript bedient sich vieler der obigen Funktionen. Man kann damit Credits abspielen lassen. Dabei wird automatisch jeglicher Spieler-Input blockiert und alle Sounds und Musik ausge-"fadet". Am besten visualisiert das dieses Video (https://youtu.be/7OxAyEbobgw) (der Code dazu ist im Skript unten als Beispiel-Code enthalten).
Im Video sieht man beim zweiten Durchlauf der Credits, was passiert, wenn man Escape drückt: Anstatt dem Spielmenü, erscheint unten rechts für einige Sekunden die Information, dass erneutes Escape-Drücken die Credits überspringt. Geschieht das, werden die Credits innerhalb einer Sekunde zu Ende gespielt.
Das Skript ermöglicht es auch eine Funktion anzugeben, die anschliessend aufgerufen wird. In dem Video ruft diese Funktion ExitSession() auf, dort kann aber auch beliebig anderes oder gar nichts passieren. Auch bestimmte Musik kann optional abgespielt werden und erhöht das "Credits-Feeling".
I have a little probelm - Why sometimes, i have crash when i'm using GetCurrentZoneActive?
[start of stacktrace]
[f] 01:37 Fault: 0 Q: MEMINT_HANDLEERROR(2, 'No music zone active.') + 62 bytes
[f] 01:37 Fault: 0 Q: MEM_WARN('No music zone active.') + 21 bytes
[f] 01:37 Fault: 0 Q: GETCURRENTMUSICZONETHEME() + 26 bytes
[f] 01:37 Fault: 0 Q: CMUSIC_GETTHEME() + 5 bytes
[f] 01:37 Fault: 0 Q: CMUSIC_CALLBACK() + 5 bytes
[f] 01:37 Fault: 0 Q: FRAMEFUNCTIONS(13) + 104 bytes
[f] 01:37 Fault: 0 Q: FOREACHHNDL(9104, FRAMEFUNCTIONS) + 263 bytes
[f] 01:37 Fault: 0 Q: [UNKNOWN] +301338389 bytes
[f] 01:37 Fault: 0 Q: _FF_HOOK() + 32 bytes
[f] 01:37 Fault: 0 Q: MEM_CALLBYID(43239) + 224 bytes
[f] 01:37 Fault: 0 Q: _HOOK(529031632, 808577952, 0, 505315136, 20313484, 5376, 449594656, 689782792, 5376) + 498 bytes
[f] 01:37 Fault: 0 Q: [UNKNOWN] +305836739 bytes
[f] 01:37 Fault: 0 Q: [end of stacktrace]
[w] 01:37 Warn: 0 Q: No music zone active.
In Access Violation screen, I have zSTRING::zSTRING operator problem? It's mistake in my scripts?
mud-freak
30.01.2018, 15:16
I have a little probelm - Why sometimes, i have crash when i'm using GetCurrentZoneActive?
GetCurrentZoneActive is not part of the scripts here, nobody can help you with that. You would have to post the code of that function.
The stack trace you posted is not the cause of the crash. It is only a warning, stating that there is no active music zone (at the moment or in that area). So getCurrentMusicZoneTheme returns an empty string ("").
The crash might be, because you do operations with that empty string afterwards. You should check first if the returned string is empty.
Can you look (Full code of my music system)
const int oCZoneMusic_HeroStatus = 10111520;
//-------- Library -------- //
var int CMusic_Inited;
var int CMusic_Library;
var int Lib_PlayMusic;
var int Lib_SetVolume;
var int Lib_Tidy;
//-------- MusicSystem -------- //
var int CMusic_CurrentZone;
var int CMusic_VolumeInited;
var int CMusic_MusicVolume;
var int CMusic_HeroStatus_Last;
var int CMusic_LastZone;
var string CMusic_OldTheme;
func void CMusic_Init()
{
if(CMusic_Inited){ return; };
CMusic_Library = LoadLibrary("CMusic.dll");
Lib_PlayMusic = GetProcAddress(CMusic_Library, "PlayMusic");
Lib_SetVolume = GetProcAddress(CMusic_Library, "SetVolume");
Lib_Tidy = GetProcAddress(CMusic_Library, "Tidy");
CMusic_Inited = true;
};
func void CMusic_Play(var string file, var int vol, var int IsInstant)
{
CMusic_Init();
CALL_IntParam(IsInstant);
CALL_PtrParam(vol);
CALL_cStringPtrParam(file);
CALL__cdecl(Lib_PlayMusic);
};
func void CMusic_SetVolume(var int vol)
{
CMusic_Init();
CALL_PtrParam(vol);
CALL__cdecl(Lib_SetVolume);
};
func void CMusic_Tidy()
{
CMusic_Init();
CALL__cdecl(Lib_Tidy);
};
func int CMusic_GetVolume(var string vol)
{
var int firstChar; firstChar = STR_GetCharAt(vol, 0);
var int result; result = mkf(1);
//ToDo: Maybe use zSTRING::ToFloat Method...
if(firstChar != 49) //'1' in ASCII
{
var string AfterCom; AfterCom = STR_SubStr(vol, 2, STR_Len(vol) - 2);
var int min; min = 4;
if(min > STR_Len(AfterCom)){
min = STR_Len(AfterCom);
};
AfterCom = STR_SubStr(vol, 2, min);
var int ret; ret = STR_ToInt(AfterCom);
var int mret; mret = Math_Power(10, min);
result = divf(mkf(ret), mkf(mret));
};
return result;
};
func void CMusic_VolumeUpdate()
{
var string MusicVol; MusicVol = MEM_GetGothOpt ("SOUND", "musicVolume");
var string MusicEnable; MusicEnable = MEM_GetGothOpt ("SOUND", "musicEnabled");
if(STR_ToInt(MusicEnable) == 0)
{
CMusic_MusicVolume = FLOATNULL;
}
else
{
CMusic_MusicVolume = CMusic_GetVolume(MusicVol);
};
CMusic_SetVolume(CMusic_MusicVolume);
};
func string CMusic_GetTheme()
{
return getCurrentMusicZoneTheme();
};
func string CMusic_GetWorldTheme()
{
var string zone; zone = CMusic_GetTheme();
return CMusic_DEF_Soundtrack_NW(zone);
};
func string CMusic_GetFightTheme()
{
var string zone; zone = CMusic_GetTheme();
return CMusic_DEF_Soundtrack_NW(zone);
};
func void CMusic_SetTheme()
{
var int IsInstant; IsInstant = 0;
var string filename;
var int HeroStatus; HeroStatus = MEM_ReadInt(oCZoneMusic_HeroStatus);
filename = CMusic_GetWorldTheme();
if(Hlp_StrCmp(filename, CMusic_OldTheme) && herostatus == CMusic_HeroStatus_Last)
{
return;
}
else
{
CMusic_OldTheme = filename;
};
if(HeroStatus <= 1)
{
filename = CMusic_GetWorldTheme();
}
else
{
filename = CMusic_GetFightTheme();
IsInstant = true;
};
//Debug: ToDO:
PrintS("New!");
PrintS(filename);
CMusic_Play(filename, CMusic_MusicVolume, IsInstant);
CMusic_HeroStatus_Last = HeroStatus;
};
func void CMusic_Callback()
{
Print(CMusic_GetTheme());
//return;
if(MEM_Game.singleStep){
return;
};
if(!CMusic_VolumeInited)
{
CMusic_VolumeUpdate();
CMusic_VolumeInited = true;
};
var int zone;
var int herostatus;
if(CMusic_MusicVolume == FLOATNULL){
return;
};
//ToDO: BossFights
zone = MEM_ReadInt(10111524);
if(zone)
{
if(zone != CMusic_LastZone)
{
CMusic_SetTheme();
}
else
{
herostatus = MEM_ReadInt(oCZoneMusic_HeroStatus);
if(herostatus != CMusic_HeroStatus_Last)
{
PrintS("New oHeroStatus!");
var string file; file = CMusic_GetWorldTheme();
CMusic_SetTheme();
CMusic_OldTheme = file;
};
};
CMusic_LastZone = zone;
};
CMusic_Tidy();
};
func void CMusic_ReInit()
{
CMusic_Inited = false;
CMusic_VolumeInited = false;
};
You should really start to debug your own code. At least find out where the crash actually occurs! You can use MEM_InfoBox() or MEM_Debug() (if you have debug infos turned on) to pinpoint which lines are being executed when the crash occurs. You don't even supply the definition for getCurrentMusicZoneTheme(), which is the only thing the warning points to. Unless you find out what's going wrong, we can't really figure out why it's doing that.
mud-freak
14.02.2018, 16:36
Ich habe oben in den Skripten noch zwei String-Funktionen hinzugefügt.
STR_TrimChar ist wie STR_Trim nur mit einem Char anstatt einem String und STR_ToFloat erlaubt die Konvertierung von Strings zu Floats (Rückgabe ist aber ein Integerfloat).
Ausserdem habe ich, auf Hinweis von Cryp18Struct, die Funktion showPlayerStatus aktualisiert, sodass sie die Bars (usw.) nicht während eines Dialogs wieder anzeigt, sondern erst anschliessend. Danke dafür!
Hi mud-freak,
Ich würde gerne dein Roll-Credits-Skript in G1 einsetzen, scheitere aber an zwei Dingen:
1) in screenfade.d rufst du Anim8_CallOnRemove() auf, welches ich aber nicht in Anims8.d von LeGo finden kann. Ich habe Version 2.5.0, du verlangst >2.5.0 Allerdings kann ich diese Funktionalität auch im dev-Branch des LeGo-Repos nicht finden (https://app.assembla.com/spaces/lego2/subversion/source) Woher hast du eine aktuellere LeGo-Version?
2) In deinem Beispielvideo rufst du die Credit-Funktion mittels eines "call" Commands auf. Gibt es den nur in G2 oder hast du den selbst implementiert? Wenn selbst geschrieben, gibt es dafür irgendwo eine Anleitung? So etwas wäre super praktisch zum Debuggen.
Würde mich freuen, wenn du mir weiterhelfen könntest. :)
mud-freak
18.02.2018, 20:58
Hi mud-freak,
Ich würde gerne dein Roll-Credits-Skript in G1 einsetzen, scheitere aber an zwei Dingen:
1) in screenfade.d rufst du Anim8_CallOnRemove() auf, welches ich aber nicht in Anims8.d von LeGo finden kann. Ich habe Version 2.5.0, du verlangst >2.5.0 Allerdings kann ich diese Funktionalität auch im dev-Branch des LeGo-Repos nicht finden (https://app.assembla.com/spaces/lego2/subversion/source) Woher hast du eine aktuellere LeGo-Version?
2) In deinem Beispielvideo rufst du die Credit-Funktion mittels eines "call" Commands auf. Gibt es den nur in G2 oder hast du den selbst implementiert? Wenn selbst geschrieben, gibt es dafür irgendwo eine Anleitung? So etwas wäre super praktisch zum Debuggen.
Würde mich freuen, wenn du mir weiterhelfen könntest. :)
Die Funktion Anim8_CallOnRemove ist neulich erst zu LeGo hinzugefügt worden (daher das >2.5.0). Bis eine nächste Version von LeGo herauskommt, kannst du sie hier (https://app.assembla.com/spaces/lego2/subversion/source/HEAD/dev/Anim8.d) finden (am besten ersetzt du deine Anim8.d im LeGo-Verzeichnis mit der verlinkten). Alternativ kannst du aber auch die "alte" ScreenFade.d (https://app.assembla.com/spaces/LM/subversion-2/source/HEAD/LeGo_Scripts/ScreenFade.d) aus dem ScriptBin nehmen, bei der das anderes geregelt ist. Dabei müsstest du dann die Aufrufe (Argumente und erwarteter Rückgabewert) in der rollCredits.d anpassen.
Das Skript für den Konsolenbefehl "call" (jegliche Daealus und externe Funktionen von der Konsole aufrufen) hier.
mud-freak
24.02.2018, 09:21
Hier eine Funktion, mit dem man Symbole (Variablen, Konstanten, Funktionen, Klassen, Prototypen, Instanzen) aus anderen Skripten-Typen (z.B. Menü-Skripte, PFX-Skripte, Kamera-Skripte, ...) auslesen kann. Für ein Anwendungsbeispiel siehe hier.
/*
* Get symbol by name from any parser
*
* - Requires Ikarus
* - Compatible with Gothic 1 and Gothic 2
*/
func int GetAnyParserSymbol(var int parserAddr, var string symbolName) {
var int symTab; symTab = parserAddr+16; //0x10 zCParser.symtab
var int namePtr; namePtr = _@s(symbolName);
const int zCPar_SymbolTable__GetSymbol_G1 = 7316336; //0x6FA370
const int zCPar_SymbolTable__GetSymbol_G2 = 8011328; //0x7A3E40
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(namePtr));
CALL_PutRetValTo(_@(ret));
CALL__thiscall(_@(symTab), MEMINT_SwitchG1G2(zCPar_SymbolTable__GetSymbol_G1, zCPar_SymbolTable__GetSymbol_G2));
call = CALL_End();
};
var int ret;
return +ret;
};
FIFO List
Small enlargement for zCList, by this three functions you can easy make FIFO List [First in, first out List].
func void CList_Insert(var int list, var int obj)
{
var zCList l; l = _^(list);
var zCList it; it = get(new(zCList@));
it.data = obj;
it.next = l.next;
l.next = _@(it);
};
func int CList_GetAt(var int list, var int nr)
{
var zCList l; l = _^(list);
var zCList it; it = _^(l.next);
var int c; c = 0;
while(_@(it));
if(c == nr){
return it.data;
};
it = _^(it.next);
c+=1;
end;
return l.data;
};
func void CList_Remove(var int list, var int obj)
{
var zCList l; l = _^(list);
while(_@(l.next) != 0);
if(MEM_ReadInt(l.next) == obj) /*zCList::data*/
{
var zCList tmp; tmp = _^(l.next);
l.next = MEM_ReadInt(l.next + 4); /*zCList::next*/
tmp.next = 0;
MEM_Free(_@(tmp));
return;
};
l = _^(l.next);
end;
};
Cinemascope + update dialogbox.
Include to game CinemaScopes for dialogs. (Don't work with D3D11)
func void CinemaScopes()
{
// if(!COpt_CinemaScopes)
// {
// return;
// };
if(!_@(MEM_Game))
{
MEM_InitGlobalInst();
};
var int Alpha;
MEM_Game.array_view_visible[3] = true;
MEM_Game.array_view_enabled[3] = true;
MEM_Camera.cinemaScopeEnabled = true;
MEM_Camera.cinemaScopeColor = RGBA (0, 0, 0, Alpha);
if(InfoManager_HasFinished())
{
if (Alpha >= 5)
{
Alpha -= 10;
};
if (Alpha == 5)
{
Alpha = 0;
};
}
else
{
if (Alpha <= 250)
{
Alpha += 10;
};
if (Alpha == 250)
{
Alpha = 255;
};
};
//Update ChoiceBox
var int ptr; ptr = MEM_ReadInt(MEMINT_oCInformationManager_Address + 28); //zCViewDialogChoice*
var int ySize; ySize = Print_ToPixel(970, PS_Y);
MEM_WriteInt(ptr + 56, 0); //xPos
MEM_WriteInt(ptr + 60, Print_Screen[PS_Y] - ySize); //yPos
MEM_WriteInt(ptr + 64, STR_ToInt (MEM_GetGothOpt ("VIDEO", "zVidResFullscreenX")));
MEM_WriteInt(ptr + 68, ySize);
MEM_WriteInt(ptr + 96, zCTexture_Load("CA_Alpha"));
//Update DialogBox
var zCView v; v = _^(MEM_Game.array_view[1]);
v.backTex = zCTexture_Load("CA_Alpha");
MEM_Call(ChangeSelectionPosition);
};
func void ChangeSelectionPosition()
{
var int i;
var zCViewText2 pText;
var zCArray arr; arr = _^ (MEM_InformationMan.DlgChoice+172); //zCViewText2*
if(!InfoManager_HasFinished() && arr.array)
{
//Zmiana położenia
i = 0;
MEM_Label(0);
if(i < arr.NumInArray)
{
if(MEM_ReadIntArray(arr.array, i))
{
pText = _^(MEM_ReadIntArray(arr.array, i));
var int fontWidth; fontWidth = Print_GetStringWidthPtr(pText.text, pText.font);
var int NewX; NewX = Print_ToPixel(4096 - Print_ToVirtual(fontWidth, PS_X) / 2, PS_X);
pText.posx = NewX;
pText.color = RGBA(255, 255, 255, 255);
i+=1;
MEM_Goto(0);
};
};
//Zmiana koloru zaznaczonej opcji
const int null = 0;
CALL__fastcall (MEM_InformationMan.DlgChoice, _@ (null), MEMINT_SwitchG1G2 (7705536, 6878528));
var zCViewText2 pColor; pColor = _^(CALL_RetValAsPtr());
pColor.color = RGBA(255, 255, 0, 255);
};
};
Now it's all from me.
Orc Hunter UA
28.05.2018, 18:39
Hallo mud-freak. Ich mache einen Mod für den ersten Teil von Gothic, und ich würde gerne in meinem Modemanagement als in Gothic 2 arbeiten. Kannst du mir dabei helfen?
mud-freak
07.09.2018, 09:59
Ich habe hier zwei Skripte, die ich teilen möchte. Das erste ist ein Hilfsskript, um die Zeit nur über Aufrufe von bestimmten Funktionen zu zählen und sich Trigger oder Pulse in bestimmter Frequenz zu holen. Das zweite ermöglicht bequemes und performantes Auswechseln von Texturen aller Standardbars (HP, Mana, Schwimmen, Fokus). Im Falle der Schwimm-Bar werden einem auch Informationen über den verbleibenden Atem in Prozent bereitgestellt. In einem enthaltenen Beispel wird das benutzt, um die Bar blinken zu lassen, wenn man unter 40% kommt.
freqTimer.d – This script offers triggers and pulses at certain frequency tracked for individual functions.
/*
* freqTimer.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25919315
*
* Get triggers or pulses at certain frequencies, see https://forum.worldofplayers.de/forum/threads/?p=25919441
*
* - Requires Ikarus>=1.2.1
* - Compatible with Gothic 1 and Gothic 2
*/
/*
* Return one of two pulse states (on or off) at certain frequency.
*
* Call this function in the following fashion:
*
* var int timerVar;
* var int state; state = freqPulse(_@(timerVar), X);
*
* where X is an integer specifying the frequency in Hz, e.g. 2 for twice a second.
* The variable "state" will then be either 1 or 0, depending on time.
*/
func int freqPulse(var int timerPtr, var int frequencyHz) {
var int timer; timer = MEM_ReadInt(timerPtr) + MEM_Timer.frameTime;
var int interval; interval = 1000 / frequencyHz;
timer = timer % (interval * 2);
MEM_WriteInt(timerPtr, timer);
return timer < interval;
};
/*
* Return a trigger at certain frequency.
*
* Call this function in the following fashion:
*
* var int timerVar;
* var int nTriggered; nTriggered = freqPulse(_@(timerVar), X);
*
* where X is an integer specifying the frequency in Hz, e.g. 2 for twice a second.
* The variable "nTriggered" will contain the number of times the frequency matched since the last call.
*/
func int freqTrigger(var int timerPtr, var int frequencyHz) {
var int timer; timer = MEM_ReadInt(timerPtr) + MEM_Timer.frameTime;
var int interval; interval = 1000 / frequencyHz;
MEM_WriteInt(timerPtr, timer % interval);
return timer / interval;
};
overrideBars.d – Conviniently override the texture of the standard bars (hp, mana, swim, focus).
An example for flashing the swim bar when running out of breath is included that requires the above script.
/*
* overrideBars.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=25919315
*
* Change the texture of the bars, e.g. for visualizing a poisoned state.
*
* - Requires Ikarus, LeGo (HookEngine)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Add additional bar textures (if desired).
* - Adjust the customizable functions that determine the bar textures, see example for red-flashing swim bar.
* - Initialize from Init_Global with
* overrideBars_Init(BAR_ALL);
* where BAR_ALL == BAR_HP | BAR_MANA | BAR_SWIM | BAR_FOCUS
*/
const int BAR_NONE = 0; // Internal, do not use/change
const int BAR_RED = 1;
const int BAR_BLUE = 2;
const int BAR_YELLOW = 3;
const int BAR_MAX = 4;
const string BAR_TEX[BAR_MAX] = {
"", // BAR_NONE
"BAR_HEALTH", // BAR_RED
"BAR_MANA", // BAR_BLUE
"BAR_MISC" // BAR_YELLOW
};
/*
* Customizable functions that determine the bar texture for NPCs
*/
func int getNPCHealthState(var C_Npc npc) {
// Change the texture by condition, e.g. (N)PC is poisoned
if (FALSE) {
return BAR_YELLOW;
};
};
func int getPCManaState() {
// Change the texture by condition, e.g. magic is disabled
if (FALSE) {
return BAR_YELLOW;
};
};
func int getPCSwimState(var int remainingBreathPercent) {
// Change the texture by condition, e.g. low on breath
if (FALSE) {
return BAR_RED;
};
/*
// EXAMPLE: Flash the swim bar red (8 Hz) when there is less than 40% breath left (requires freqTimer script)
var int flashTimer;
return (remainingBreathPercent < 40) * freqPulse(_@(flashTimer), 8) * BAR_RED;
*/
};
/*
* Main update function
*/
func void overrideBar(var int barPtr, var int texID) {
var oCViewStatusBar bar; bar = _^(barPtr);
ViewPtr_SetTexture(bar.value_bar, MEM_ReadStatStringArr(BAR_TEX, texID));
};
/*
* Hooking function when drawing the health bar
*/
func void _updateHealthBar() {
const int SET = BAR_RED;
var int now; now = getNPCHealthState(hero);
if (!now) {
now = BAR_RED;
};
if (now != SET) {
overrideBar(MEMINT_SwitchG1G2(ECX, EAX), now);
SET = now;
};
};
/*
* Hooking function when drawing the swim bar
*/
func void _updateSwimBar() {
const int SET = BAR_YELLOW;
// Make sure guild values are assigned (lost after level change)
const int TGilValues_G1 = 9276496; //0x8D8C50
const int TGilValues_G2 = 11197352; //0xAADBA8
var C_GILVALUES gilVal; gilVal = _^(MEMINT_SwitchG1G2(TGilValues_G1, TGilValues_G2));
// Get remaining breath in percent
var int divectr; divectr = MEM_ReadInt(ESP+28); // esp+0x80-0x64
var int gil; gil = hero.guild;
if (gil < GIL_SEPERATOR_HUM) {
gil = 1;
};
var int divetime; divetime = MEM_ReadStatArr(gilVal.DIVE_TIME, gil);
var int remainingBreath; remainingBreath = roundf(divectr) / 10 / divetime;
var int now; now = getPCSwimState(remainingBreath);
if (!now) {
now = BAR_YELLOW;
};
if (now != SET) {
overrideBar(EDX, now);
SET = now;
};
};
/*
* Hooking function when drawing the mana bar
*/
func void _updateManaBar() {
const int SET = BAR_BLUE;
var int now; now = getPCManaState();
if (!now) {
now = BAR_BLUE;
};
if (now != SET) {
overrideBar(EAX, now);
SET = now;
};
};
/*
* Hooking function when drawing the focus bar
*/
func void _updateFocusBar() {
const int SET = BAR_RED;
var C_Npc npc; npc = _^(MEMINT_SwitchG1G2(EBP, EDI));
var int now; now = getNPCHealthState(npc);
if (!now) {
now = BAR_RED;
};
if (now != SET) {
overrideBar(MEMINT_SwitchG1G2(ECX, EDX), now);
SET = now;
};
};
const int BAR_HP = 1<<0;
const int BAR_MANA = 1<<1;
const int BAR_SWIM = 1<<2;
const int BAR_FOCUS = 1<<3;
const int BAR_ALL = (1<<4) - 1;
/*
* Initialization function
*/
func void overrideBars_Init(var int flags) {
MEM_InitAll();
const int oCGame__UpdatePlayerStatus_hpbar_G1 = 6524982; //0x639036
const int oCGame__UpdatePlayerStatus_hpbar_G2 = 7090787; //0x6C3263
const int oCGame__UpdatePlayerStatus_swimbar_G1 = 6525204; //0x639114
const int oCGame__UpdatePlayerStatus_swimbar_G2 = 7091050; //0x6C336A
const int oCGame__UpdatePlayerStatus_manabar_G1 = 6525379; //0x6391C3
const int oCGame__UpdatePlayerStatus_manabar_G2 = 7091233; //0x6C3421
const int oCGame__UpdatePlayerStatus_focusbar_G1 = 6525725; //0x63931D
const int oCGame__UpdatePlayerStatus_focusbar_G2 = 7091981; //0x6C370D
if (flags & BAR_HP) {
HookEngineF(MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_hpbar_G1,
oCGame__UpdatePlayerStatus_hpbar_G2), 6, _updateHealthBar);
};
if (flags & BAR_SWIM) {
HookEngineF(MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_swimbar_G1,
oCGame__UpdatePlayerStatus_swimbar_G2), 6, _updateSwimBar);
};
if (flags & BAR_MANA) {
HookEngineF(MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_manabar_G1,
oCGame__UpdatePlayerStatus_manabar_G2), 6, _updateManaBar);
};
if (flags & BAR_FOCUS) {
HookEngineF(+MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_focusbar_G1,
oCGame__UpdatePlayerStatus_focusbar_G2), +MEMINT_SwitchG1G2(9,
6), _updateFocusBar);
};
};
Fisk2033
07.09.2018, 10:48
Ich habe hier zwei Skripte, die ich teilen möchte. Das erste ist ein Hilfsskript, um die Zeit nur über Aufrufe von bestimmten Funktionen zu zählen und sich Trigger oder Pulse in bestimmter Frequenz zu holen. Das zweite ermöglicht bequemes und performantes Auswechseln von Texturen aller Standardbars (HP, Mana, Schwimmen, Fokus). Im Falle der Schwimm-Bar werden einem auch Informationen über den verbleibenden Atem in Prozent bereitgestellt. In einem enthaltenen Beispel wird das benutzt, um die Bar blinken zu lassen, wenn man unter 40% kommt.
freqTimer.d – This script offers triggers and pulses at certain frequency tracked for individual functions.
/*
* freqTimer.d
*
* Get triggers or pulses at certain frequencies.
*
* - Requires Ikarus
* - Compatible with Gothic 1 and Gothic 2
*
*
* When using Gothic 1, initialize from Init_Global with
* freqTimer_Init();
*/
/*
* Return one of two pulse states (on or off) at certain frequency.
*
* Call this function in the following fashion:
*
* var int timerVar;
* var int state; state = freqPulse(_@(timerVar), X);
*
* where X is an integer specifying the frequency in Hz, e.g. 2 for twice a second.
* The variable "state" will then be either 1 or 0, depending on time.
*/
func int freqPulse(var int timerPtr, var int frequencyHz) {
var int timer; timer = MEM_ReadInt(timerPtr);
var int interval; interval = 1000 / frequencyHz;
MEM_WriteInt(timerPtr, timer + MEM_Timer.frameTime);
if (timer >= interval * 2) {
MEM_WriteInt(timerPtr, timer - interval * 2);
};
if (timer < interval) {
return TRUE;
} else {
return FALSE;
};
};
/*
* Return a trigger at certain frequency.
*
* Call this function in the following fashion:
*
* var int timerVar;
* var int trigger; trigger = freqPulse(_@(timerVar), X);
*
* where X is an integer specifying the frequency in Hz, e.g. 2 for twice a second.
* The variable "trigger" will then be 1, only at the times where the frequency is matched, e.g. twice a second.
*/
func int freqTrigger(var int timerPtr, var int frequencyHz) {
var int timer; timer = MEM_ReadInt(timerPtr);
var int interval; interval = 1000 / frequencyHz;
MEM_WriteInt(timerPtr, timer + MEM_Timer.frameTime);
if (timer >= interval) {
MEM_WriteInt(timerPtr, timer - interval);
return TRUE;
} else {
return FALSE;
};
};
/*
* Initialization only necessary for Gothic 1
*/
func void freqTimer_Init() {
const int once = 0;
if (!once) {
// Fix zTimer for Gothic 1 (the address in Ikarus is wrong)
if (GOTHIC_BASE_VERSION == 1) {
MEMINT_zTimer_Address = 9236968; //0x8CF1E8
MEM_Timer = _^(MEMINT_zTimer_Address);
};
once = 1;
};
};
overrideBars.d – Conviniently override the texture of the standard bars (hp, mana, swim, focus).
An example for flashing the swim bar when running out of breath is included that requires the above script.
/*
* overrideBars.d
*
* Change the texture of the bars, e.g. for visualizing a poisoned state.
*
* - Requires Ikarus, LeGo (HookEngine), freqTimer (optional)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Add additional bar textures (if desired).
* - Adjust the customizable functions that determine the bar textures, see example for red-flashing swim bar.
* - Initialize from Init_Global with
* overrideBars_Init(BAR_ALL);
* where BAR_ALL == BAR_HP | BAR_MANA | BAR_SWIM | BAR_FOCUS
*/
const int BAR_NONE = 0; // Internal, do not use/change
const int BAR_RED = 1;
const int BAR_BLUE = 2;
const int BAR_YELLOW = 3;
const int BAR_MAX = 4;
const string BAR_TEX[BAR_MAX] = {
"", // BAR_NONE
"BAR_HEALTH", // BAR_RED
"BAR_MANA", // BAR_BLUE
"BAR_MISC" // BAR_YELLOW
};
/*
* Customizable functions that determine the bar texture for NPCs
*/
func int getNPCHealthState(var C_Npc npc) {
// Change the texture by condition, e.g. (N)PC is poisoned
if (FALSE) {
return BAR_YELLOW;
};
};
func int getPCManaState() {
// Change the texture by condition, e.g. magic is disabled
if (FALSE) {
return BAR_YELLOW;
};
};
func int getPCSwimState(var int remainingBreathPercent) {
// EXAMPLE: Flash the swim bar red (8 Hz) when there is less than 40% breath left
var int flashTimer;
return (remainingBreathPercent < 40) * freqPulse(_@(flashTimer), 8) * BAR_RED;
};
/*
* Main update function
*/
func void overrideBar(var int barPtr, var int texID) {
var oCViewStatusBar bar; bar = _^(barPtr);
ViewPtr_SetTexture(bar.value_bar, MEM_ReadStatStringArr(BAR_TEX, texID));
};
/*
* Hooking function when drawing the health bar
*/
func void _updateHealthBar() {
const int SET = BAR_RED;
var int now; now = getNPCHealthState(hero);
if (!now) {
now = BAR_RED;
};
if (now != SET) {
overrideBar(MEMINT_SwitchG1G2(ECX, EAX), now);
SET = now;
};
};
/*
* Hooking function when drawing the swim bar
*/
func void _updateSwimBar() {
const int SET = BAR_YELLOW;
// Get remaining breath in percent
var int divectr; divectr = MEM_ReadInt(ESP+28); // esp+0x80-0x64
var int gil; gil = hero.guild;
if (gil < GIL_SEPERATOR_HUM) {
gil = 1;
};
var int divetime; divetime = MEM_ReadStatArr(Gil_Values.DIVE_TIME, gil);
var int remainingBreath; remainingBreath = roundf(divectr) / 10 / divetime;
var int now; now = getPCSwimState(remainingBreath);
if (!now) {
now = BAR_YELLOW;
};
if (now != SET) {
overrideBar(EDX, now);
SET = now;
};
};
/*
* Hooking function when drawing the mana bar
*/
func void _updateManaBar() {
const int SET = BAR_BLUE;
var int now; now = getPCManaState();
if (!now) {
now = BAR_BLUE;
};
if (now != SET) {
overrideBar(EAX, now);
SET = now;
};
};
/*
* Hooking function when drawing the focus bar
*/
func void _updateFocusBar() {
const int SET = BAR_RED;
var C_Npc npc; npc = _^(MEMINT_SwitchG1G2(EBP, EDI));
var int now; now = getNPCHealthState(npc);
if (!now) {
now = BAR_RED;
};
if (now != SET) {
overrideBar(MEMINT_SwitchG1G2(ECX, EDX), now);
SET = now;
};
};
const int BAR_HP = 1<<0;
const int BAR_MANA = 1<<1;
const int BAR_SWIM = 1<<2;
const int BAR_FOCUS = 1<<3;
const int BAR_ALL = (1<<4) - 1;
/*
* Initialization function
*/
func void overrideBars_Init(var int flags) {
MEM_InitAll();
const int oCGame__UpdatePlayerStatus_hpbar_G1 = 6524982; //0x639036
const int oCGame__UpdatePlayerStatus_hpbar_G2 = 7090787; //0x6C3263
const int oCGame__UpdatePlayerStatus_swimbar_G1 = 6525204; //0x639114
const int oCGame__UpdatePlayerStatus_swimbar_G2 = 7091050; //0x6C336A
const int oCGame__UpdatePlayerStatus_manabar_G1 = 6525379; //0x6391C3
const int oCGame__UpdatePlayerStatus_manabar_G2 = 7091233; //0x6C3421
const int oCGame__UpdatePlayerStatus_focusbar_G1 = 6525725; //0x63931D
const int oCGame__UpdatePlayerStatus_focusbar_G2 = 7091981; //0x6C370D
if (flags & BAR_HP) {
HookEngineF(+MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_hpbar_G1,
oCGame__UpdatePlayerStatus_hpbar_G2), 6, _updateHealthBar);
};
if (flags & BAR_SWIM) {
HookEngineF(+MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_swimbar_G1,
oCGame__UpdatePlayerStatus_swimbar_G2), 6, _updateSwimBar);
};
if (flags & BAR_MANA) {
HookEngineF(+MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_manabar_G1,
oCGame__UpdatePlayerStatus_manabar_G2), 6, _updateManaBar);
};
if (flags & BAR_FOCUS) {
HookEngineF(+MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_focusbar_G1,
oCGame__UpdatePlayerStatus_focusbar_G2), MEMINT_SwitchG1G2(9,
6), _updateFocusBar);
};
};
Vielen Dank! Vor allem für die ausführliche Doku mit Beispiel. :gratz
Gute Arbeit, aber ich verstehe den Unterschied zwischen freqPulse und freqTrigger noch nicht so richtig (und in deiner Dokumentation hast du ausversehen das Snippet von freqPulse für freqTrigger angegeben).
mud-freak
07.09.2018, 12:41
Gute Arbeit, aber ich verstehe den Unterschied zwischen freqPulse und freqTrigger noch nicht so richtig (und in deiner Dokumentation hast du ausversehen das Snippet von freqPulse für freqTrigger angegeben).
Das Frequenz-Skript habe ich nur hinzugefügt, weil ich es in dem Beispiel für die Schwimm-Bar benutzt habe. Da ist mir dann die Lust am Dokumentieren vergangen (vor allem, weil die Funktionen sehr speziell sind und ich nicht glaube, dass sie häufig verwendet werden). Ich gebe zu, dass die beiden Funktionen nicht gut bis gar nicht erklärt sind.
Zur generellen Idee:
Normalerweise ist man ja mit dem Timer von LeGo gut bedient, um Zeiten abzufragen. Wenn man allerdings ein Intervall benötigt (mache X alle Y Millisekunden) wird es problematisch, wenn man das Spiel pausiert (weil die Zeit weiterläuftt). Neuerdings/bald gibt es dafür den Game-Timer in LeGo, der nur läuft, wenn das Spiel nicht pausiert ist. Ich habe das Problem noch einen Schritt weiter genommen. Ich wollte, dass die Zeit nur weiter gezählt wird, in den Frames in denen meine Funktion aufgerufen wird.
Mit den Frequenz-Funktionen kann ich mir jetzt individuell für eine Funktion Trigger oder Pulse geben lasssen, wobei ich Trigger und Pulse hier für mich so definiere:
https://upload.worldofplayers.de/files11/4iIIqTfreqTimer.png
Im Beispiel für die blinkende Schwimm-Bar werden Pulse verwendet, um sie für die gleiche Dauer rot und gelb anzuzeigen.
Milky-Way
09.01.2019, 04:58
Wäre es sinnvoll, ScriptBin ebenfalls zu GitHub zu transferieren, und bei der Gelegenheit die weiteren hier im Thema genannten und aktualisierten Skripte einzubauen?
mud-freak
09.01.2019, 09:07
Wäre es sinnvoll, ScriptBin ebenfalls zu GitHub zu transferieren, und bei der Gelegenheit die weiteren hier im Thema genannten und aktualisierten Skripte einzubauen?
Vielleicht sogar nicht als ein Repository, sondern angelegt als "Organisation" (das ist eine Kollektion von Repositories). Dann kann jedes Skript(-paket) in einem eigenen Repository bleiben und es ist einfacher sich ein bestimmtes Skript-Feature heraus zu picken (wenn man an all den anderen Skripten nicht interessiert ist). Die bisherige Struktur vom SkriptBin Repo kann schnell unübersichtlich werden und man kann teilweise nur schwer zuordnen welche Skripte oder Funktionen jetzt zu dem gewünschten Skript dazu gehören. Verlinken von einem Skript macht das auch einfacher. Auch das hinzufügen von neuen Skripten bedurfte bisher eher aufwändiges eingliedern in die Ordnerstruktur und in die SRC-Datei.
Die "Gefahr" ist aber, dass es zu viele Repos werden.
Das sind so meine Gedanken dazu.
Eine Frage: Wie rufe ich stopAllSounds beim Spiel Laden auf? Beim Spiel Start hab wird meine (lange) Gamestart.wav schon schön abgebrochen, aber beim Spiel Laden noch nicht.
Naja wo rufst du es denn momentan auf? Generell ist die INIT_Global (Startup.d) die Funktion, die sowohl beim Start eines neuen Spiels als auch beim Laden eines Speicherstandes aufgerufen wird.
Naja wo rufst du es denn momentan auf? Generell ist die INIT_Global (Startup.d) die Funktion, die sowohl beim Start eines neuen Spiels als auch beim Laden eines Speicherstandes aufgerufen wird.
Okay, danke. Ich hatte sie nicht in INIT_Global eingetragen. Jetzt funktioniert's.
Few days ago I wrote a script that creates in inventory every existing in the game item. Maybe someone will make use of it :gratz
func void CreateAllItems(var c_npc slf) {
var int i;
repeat(i, MEM_Parser.symtab_table_numInArray);
var zCPar_Symbol symb; symb = _^(MEM_ReadIntArray(MEM_Parser.symtab_table_array, i));
if ((symb.bitfield & zCPar_Symbol_bitfield_type) != zPAR_TYPE_INSTANCE || STR_IndexOf(symb.name, ".") > 0 || !symb.parent) {
continue;
};
symb = _^(symb.parent);
if (Hlp_StrCmp(symb.name, "C_ITEM")) {
CreateInvItems(slf, i, 1);
}
else if (symb.parent && (symb.bitfield & zCPar_Symbol_bitfield_type) == zPAR_TYPE_PROTOTYPE) {
symb = _^(symb.parent);
if (Hlp_StrCmp(symb.name, "C_ITEM")) {
CreateInvItems(slf, i, 1);
};
};
end;
};
The game may freeze for a while because table of parser symbols is very huge.
mud-freak
04.03.2019, 17:03
Nicely written code, thanks for sharing!
F a w k e s
06.03.2019, 18:18
I will also add something I believe can be useful: :gratz
NPCVobList.d – functions will showcase better control over what NPCs can 'detect' in specified range.
//Tested with G1 Ikarus 1.2.1
//Maybe also compatible with G2A - I never tested :)
/***
NPC_CreateVobList (var int slfInstance, var int range)
Pretty much same as NPC_PerceiveAll - however allows you to control range in which Vobs will be collected.
When used all collected vobs will be available in slf.vobList_array.
***/
FUNC VOID NPC_CreateVobList (var int slfInstance, var int range)
{
//006B7110 .text Debug data ?CreateVobList@oCNpc@@QAEXM@Z
const int oCNpc__CreateVobList_G1 = 7041296;
//0x0075DA40 public: void __thiscall oCNpc::CreateVobList(float)
const int oCNpc__CreateVobList_G2 = 7723584;
var C_NPC slf;
slf = Hlp_GetNPC (slfInstance);
CALL_IntParam (mkf (range));
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNpc__CreateVobList_G1, oCNpc__CreateVobList_G2));
};
/***
NPC_ClearVobList (var int slfInstance)
Clears slf.vobList_array - not sure if this useful - I never used it :)
***/
FUNC VOID NPC_ClearVobList (var int slfInstance)
{
//006B6EB0 .text Debug data ?ClearVobList@oCNpc@@QAEXXZ
const int oCNpc__ClearVobList_G1 = 7040688;
//0x0075D7F0 public: void __thiscall oCNpc::ClearVobList(void)
const int oCNpc__ClearVobList_G2 = 7722992;
var C_NPC slf;
slf = Hlp_GetNPC (slfInstance);
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNpc__ClearVobList_G1, oCNpc__ClearVobList_G2));
};
/***
NPC_RemoveFromVobList (var int slfInstance, var int vobPtr)
Removes specific vobPtr from slf.vobList_array
***/
FUNC VOID NPC_RemoveFromVobList (var int slfInstance, var int vobPtr)
{
//006B7080 .text Debug data ?RemoveFromVobList@oCNpc@@QAEXPAVzCVob@@@Z
const int oCNpc__RemoveFromVobList_G1 = 7041152;
//0x0075D9B0 public: void __thiscall oCNpc::RemoveFromVobList(class zCVob *)
const int oCNpc__RemoveFromVobList_G2 = 7723440;
if (!vobPtr) { return; };
var C_NPC slf;
slf = Hlp_GetNPC (slfInstance);
var zCVob Vob;
Vob = _^ (vobPtr);
CALL_PtrParam (MEM_InstToPtr (Vob));
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNpc__RemoveFromVobList_G1, oCNpc__RemoveFromVobList_G2));
};
/***
NPC_VobList_RemoveDeadNPC (var int slfInstance)
Will remove dead NPC from slf.vobList_array.
***/
FUNC VOID NPC_VobList_RemoveDeadNPC (var int slfInstance)
{
var oCNPC slf;
slf = Hlp_GetNPC (slfInstance);
var int i; i = 0;
var int loop;
MEM_InitLabels ();
loop = MEM_StackPos.position;
if (slf.vobList_numInArray > 0)
{
var int vobPtr; vobPtr = MEM_ReadIntArray (slf.vobList_array, i);
if (Hlp_Is_oCNpc (vobPtr))
{
var C_NPC npc;
npc = MEM_PtrToInst (vobPtr);
if (NPC_IsDead (npc))
{
//remove npc from vobList_array
NPC_RemoveFromVobList (slfInstance, vobPtr);
//restart loop
i = 0;
MEM_StackPos.position = loop;
};
};
i += 1;
if (i < slf.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
};
/***
NPC_VobList_RemovePlayer (var int slfInstance)
Will remove player from slf.vobList_array.
***/
FUNC VOID NPC_VobList_RemovePlayer (var int slfInstance)
{
var oCNPC slf;
slf = Hlp_GetNPC (slfInstance);
var int i; i = 0;
var int loop;
MEM_InitLabels ();
loop = MEM_StackPos.position;
if (slf.vobList_numInArray > 0)
{
var int vobPtr; vobPtr = MEM_ReadIntArray (slf.vobList_array, i);
if (Hlp_Is_oCNpc (vobPtr))
{
var C_NPC npc;
npc = MEM_PtrToInst (vobPtr);
if (NPC_IsPlayer (npc))
{
//remove npc from vobList_array
NPC_RemoveFromVobList (slfInstance, vobPtr);
//restart loop
i = 0;
MEM_StackPos.position = loop;
};
};
i += 1;
if (i < slf.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
};
/***
NPC_VobList_FindMobInter (var int slfInstance, var string scemeName)
Function removes from slf.vobList_array everything that is not oCMobInter and does not have sceme equal to scemeName.
//Example will detect VWHEEL object:
var int ptr;
NPC_CreateVobList (self, 800);
ptr = NPC_VobList_FindMobInter (self, "VWHEEL");
//Example will detect chest (CHESTBIG or CHESTMEDIUM or CHESTSMALL) object:
var int ptr;
NPC_CreateVobList (self, 800);
ptr = NPC_VobList_FindMobInter (self, "CHESTBIG");
if (ptr == 0) {
NPC_CreateVobList (self, 800);
ptr = NPC_VobList_FindMobInter (self, "CHESTMEDIUM");
};
if (ptr == 0) {
NPC_CreateVobList (self, 800);
ptr = NPC_VobList_FindMobInter (self, "CHESTSMALL");
};
***/
FUNC INT NPC_VobList_FindMobInter (var int slfInstance, var string scemeName)
{
var oCNPC slf;
slf = Hlp_GetNPC (slfInstance);
scemeName = STR_Upper (scemeName);
var int i; i = 0;
var int loop;
var int vobPtr;
var int flgRemove;
MEM_InitLabels ();
loop = MEM_StackPos.position;
if (slf.vobList_numInArray > 0)
{
flgRemove = TRUE;
vobPtr = MEM_ReadIntArray (slf.vobList_array, i);
if (Hlp_Is_oCMobInter (vobPtr)) {
var oCMobInter mob;
mob = MEM_PtrToInst (vobPtr);
if (Hlp_StrCmp (STR_Upper (mob.sceme), scemeName)) {
flgRemove = FALSE;
};
};
if (flgRemove) {
//remove npc from vobList_array
NPC_RemoveFromVobList (slfInstance, vobPtr);
//restart loop
i = 0;
MEM_StackPos.position = loop;
};
i += 1;
if (i < slf.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
i = 0;
loop = MEM_StackPos.position;
if (slf.vobList_numInArray > 0)
{
vobPtr = MEM_ReadIntArray (slf.vobList_array, i);
return vobPtr;
};
return 0;
};
/***
NPC_FreeLineOfSight (var int slfInstance, var int vobPtr)
Checks if NPC can see vobPtr. Used further below by function NPC_VobList_DetectObject.
***/
FUNC INT NPC_FreeLineOfSight (var int slfInstance, var int vobPtr)
{
//0069DE50 .text Debug data ?FreeLineOfSight@oCNpc@@QAEHPAVzCVob@@@Z
const int oCNpc__FreeLineOfSight_G1 = 6938192;
//0x007418E0 public: int __thiscall oCNpc::FreeLineOfSight(class zCVob *)
const int oCNpc__FreeLineOfSight_G2 = 7608544;
if (!vobPtr) { return 0; };
var C_NPC slf;
slf = Hlp_GetNPC (slfInstance);
var zCVob Vob;
Vob = _^ (vobPtr);
CALL_PtrParam (MEM_InstToPtr (Vob));
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNpc__FreeLineOfSight_G1, oCNpc__FreeLineOfSight_G2));
return CALL_RetValAsInt();
};
/***
NPC_VobList_DetectObject (var int slfInstance, var int freeLOS, var int vtbl, var int objInstance)
Allows you to detect specific objects. Returns object pointer.
freeLOS: is object in Free Line of Sight? If TRUE - will return object pointer only if in Free Line of Sight, if FALSE - will return object regardless of whether it is in Free Line of sight or not.
object vtbl: oCMobFire_vtbl, zCMover_vtbl, oCMob_vtbl, oCMobInter_vtbl, oCMobContainer_vtbl, oCMobDoor_vtbl, oCMobContainer_vtbl, oCNpc_vtbl, oCItem_vtbl, zCVobLight_vtbl
objInstance: detection of specific instance. (use 0 if you don't want to detect instances)
//Example will detect npc, Bloodfly, which is in Free Line of Sight
NPC_CreateVobList (self, 3000);
if (NPC_VobList_DetectObject (self, TRUE, oCNpc_vtbl, Bloodfly)) {
...
};
//Example will detect item, ItMi_Plants_Swampherb_01, which is in Free Line of Sight
NPC_CreateVobList (self, 1500);
if (NPC_VobList_DetectObject (self, TRUE, oCItem_vtbl, ItMi_Plants_Swampherb_01)) {
...
};
//Example will detect chest, which is in Free Line of Sight
var int ptr;
NPC_CreateVobList (self, 1000);
ptr = NPC_VobList_DetectObject (self, TRUE, oCMobContainer_vtbl, 0);
if (ptr) {
var oCMobLockable chestLock;
chestLock = MEM_PtrToInst (ptr);
//Is chest locked?
if (chestLock.bitfield & oCMobLockable_bitfield_locked) {
var oCMobContainer chest;
chest = MEM_PtrToInst (ptr);
PrintScreen (chest._oCMobLockable_pickLockStr, -1, _YPOS_MESSAGE_LOGENTRY, "font_old_10_white.tga", _TIME_MESSAGE_LOGENTRY);
};
};
//Example will detect door, which is in Free Line of Sight
var int ptr;
NPC_CreateVobList (self, 1000);
ptr = NPC_VobList_DetectObject (self, TRUE, oCMobDoor_vtbl, 0);
if (ptr) {
var oCMobLockable doorLock;
doorLock = MEM_PtrToInst (ptr);
//Is door locked?
if (doorLock.bitfield & oCMobLockable_bitfield_locked) {
var oCMobDoor door;
door = MEM_PtrToInst (ptr);
PrintScreen (door._oCMobLockable_pickLockStr, -1, _YPOS_MESSAGE_LOGENTRY, "font_old_10_white.tga", _TIME_MESSAGE_LOGENTRY);
};
};
***/
FUNC INT NPC_VobList_DetectObject (var int slfInstance, var int freeLOS, var int vtbl, var int objInstance)
{
var oCNPC slf;
slf = Hlp_GetNPC (slfInstance);
var int i; i = 0;
var int loop;
//flags
var int fFreeLOS;
var int fVtbl;
var int fObjInstance;
//loop through objects
MEM_InitLabels ();
loop = MEM_StackPos.position;
fVtbl = FALSE;
fFreeLOS = FALSE;
fObjInstance = FALSE;
if (!objInstance) { fObjInstance = TRUE; };
if (slf.vobList_numInArray > 0)
{
var int vobPtr; vobPtr = MEM_ReadIntArray (slf.vobList_array, i);
var int vobVtbl; vobVtbl = MEM_ReadInt (vobPtr);
//oCMobFire_vtbl
if (vtbl == oCMobFire_vtbl) {
if (vobVtbl == oCMobFire_vtbl) {
fVtbl = TRUE;
};
} else
//zCMover_vtbl
if (vtbl == zCMover_vtbl) {
if (vobVtbl == zCMover_vtbl) {
fVtbl = TRUE;
};
} else
//oCMob_vtbl
if (vtbl == oCMob_vtbl) {
//
if (vobVtbl == oCMob_vtbl)
|| (vobVtbl == oCMobInter_vtbl)
|| (vobVtbl == oCMobContainer_vtbl)
|| (vobVtbl == oCMobDoor_vtbl) {
fVtbl = TRUE;
};
} else
//oCMobInter_vtbl
if (vtbl == oCMobInter_vtbl) {
//
if (vobVtbl == oCMobInter_vtbl)
|| (vobVtbl == oCMobContainer_vtbl)
|| (vobVtbl == oCMobDoor_vtbl) {
fVtbl = TRUE;
};
} else
//oCMobLockable (does not exist in G1 Ikarus as a constant
//if (vtbl == oCMobLockable_vtbl) {
if (vtbl == oCMobContainer_vtbl) {
//
if (vobVtbl == oCMobContainer_vtbl) {
fVtbl = TRUE;
};
} else
if (vtbl == oCMobDoor_vtbl) {
if (vobVtbl == oCMobDoor_vtbl) {
fVtbl = TRUE;
};
} else
//oCNpc_vtbl
if (vtbl == oCNpc_vtbl) {
//
if (vobVtbl == oCNpc_vtbl) {
//Check NPC instance if required
if (objInstance) {
var C_NPC npc;
npc = _^ (vobPtr);
if (Hlp_GetInstanceID (npc) == objInstance) {
fObjInstance = TRUE;
};
};
fVtbl = TRUE;
};
} else
//oCItem_vtbl
if (vtbl == oCItem_vtbl) {
//
if (vobVtbl == oCItem_vtbl) {
//Check item instance if required
if (objInstance) {
var C_Item itm;
itm = _^ (vobPtr);
if (Hlp_GetInstanceID (itm) == objInstance) {
fObjInstance = TRUE;
};
};
fVtbl = TRUE;
};
} else
//zCVobLight_vtbl
if (vtbl == zCVobLight_vtbl) {
//
if (vobVtbl == zCVobLight_vtbl) {
fVtbl = TRUE;
};
};
//Free Line of Sight
if (freeLOS) {
if (NPC_FreeLineOfSight (slf, vobPtr)) {
fFreeLOS = TRUE;
};
} else {
fFreeLOS = TRUE;
};
if (fVtbl) && (fFreeLOS) && (fObjInstance) {
return vobPtr;
};
i += 1;
if (i < slf.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
return 0;
};
F a w k e s
10.03.2019, 22:24
Hello folks,
Today I want to demonstrate how powerful npc.vobList_array can actually be - even I did not fully realize it till today!
I always wanted to move around in-game objects while playing the game, but was not really able to - as I was not able to detect what kind of object is in front of hero. (in case it would not be focus-able)
I know you can use trace rays somehow, but that is just .. out of my league! But with NPC_CreateVobList I can detect all objects which are nearby hero, and then I can just check, whether hero can see an object or not.
If he can see object, then it means object is in front of hero - and we can assume that he can move it around:
https://www.youtube.com/watch?v=5INZCzTqGcg
Here is complete code in case you would like to try it yourself. :gratz
Required:
Ikarus 1.2.1
Function NPC_CanSeeVob from:
https://forum.worldofplayers.de/forum/threads/1299679-Skriptpaket-Ikarus-4/page24?p=26055633&viewfull=1#post26055633
Function SetVobToFloor from Scriptbin\InsertAnything:
(@mud-freak thank you!)
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page2?p=25712257&viewfull=1#post25712257
Functions from Scriptbin\NPCVobList.d:
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page4?p=26052199&viewfull=1#post26052199
+ Functions Vob_Move & moveVobInFront:
(@Lehona thank you!)
const int zCVob__Move_G1 = 6217184; //0x5EDDE0
const int zCVob__Move_G2 = 6402784; //0x61B2E0
func void Vob_Move (var int ptr, var int x, var int y, var int z)
{
CALL_FloatParam (x);
CALL_FloatParam (y);
CALL_FloatParam (z);
CALL__thiscall (ptr, MEMINT_SwitchG1G2 (zCVob__Move_G1, zCVob__Move_G2));
};
/***
Function moveVobInFront move vob in front of NPC
***/
func void moveVobInFront (var int slfInstance, var int vobPtr)
{
var zCVob slf; slf = Hlp_GetNPC (slfInstance);
var zCVob vob; vob = _^ (vobPtr);
// trafo kopieren
MEM_CopyBytes(_@(slf) + 60, vobPtr + 60, 64);
// Vob vor den Helden setzen
var int delta; delta = mkf(150);
Vob_Move (vobPtr, mulf (slf.trafoObjToWorld[10], delta), mulf (slf.trafoObjToWorld[6], delta), mulf (slf.trafoObjToWorld[2], delta));
};
I have hooked function oCGame__HandleEvent - within this function I am checking if player pressed '[' key to start/stop object moving mode, or key ']' to enable/disable floor aligning mode:
//Global variables
var int vobTransportPtr;
var int vobTransportMode;
const int cvobTransportMode_Disabled = 0;
const int cvobTransportMode_Init = 1;
const int cvobTransportMode_Moving = 2;
const int cvobTransportMode_Done = 3;
var int vobTransportModeFloor;
var int vobTransportCollBits;
const int oCGame__HandleEvent_G1 = 6680288; //0x65EEE0
const int oCGame__HandleEvent_G2 = 7324016; //0x6FC170
FUNC VOID _HOOK_GAME_HANDLEEVENT ()
{
var int inputKey;
inputKey = MEM_KeyState (KEY_LBRACKET);
//Vob moving on/off
if (inputKey == KEY_PRESSED) || (inputKey == KEY_HOLD)
{
if (vobTransportMode == cvobTransportMode_Disabled)
{
vobTransportMode = cvobTransportMode_Init;
};
if (vobTransportMode == cvobTransportMode_Moving)
{
vobTransportMode = cvobTransportMode_Done;
};
};
//Vob surface align mode on/off
inputKey = MEM_KeyState (KEY_RBRACKET);
if (inputKey == KEY_PRESSED) || (inputKey == KEY_HOLD)
{
vobTransportModeFloor = !vobTransportModeFloor;
};
};
//Hook this
HookEngine (MEMINT_SwitchG1G2(oCGame__HandleEvent_G1, oCGame__HandleEvent_G2), 6, "_HOOK_GAME_HANDLEEVENT");
And finally FrameHandler_VobTransport function - is the one responsible for object detection and moving (should be called every frame):
/***
VobGetCollBits will return collision bitfield values
***/
FUNC INT VobGetCollBits (var int vobPtr)
{
if (!vobPtr) { return; };
var int collBits; collBits = (zCVob_bitfield0_collDetectionStatic + zCVob_bitfield0_collDetectionDynamic);
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
return (vob.bitfield[0] & (collBits));
};
/***
VobRemoveCollBits - will remove collisions based on input bitfield values in collBits
***/
FUNC VOID VobRemoveCollBits (var int vobPtr, var int collBits)
{
if (!vobPtr) { return; };
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
vob.bitfield[0] = vob.bitfield[0] & ~ (collBits);
};
/***
VobRestoreCollBits - will apply collisions based on input bitfield values in collBits
***/
FUNC VOID VobRestoreCollBits (var int vobPtr, var int collBits)
{
if (!vobPtr) { return; };
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
vob.bitfield[0] = vob.bitfield[0] | (collBits);
};
/***
Hlp_Is_zcVob - validates whether ptr object is zcVob
***/
FUNC INT Hlp_Is_zcVob (var int ptr) {
if (!ptr) { return 0; };
var int vtbl;
vtbl = MEM_ReadInt (ptr);
return (vtbl == zCVob_vtbl);
};
/***
Hlp_Is_oCMobLadder - validates whether ptr object is oCMobLadder
***/
FUNC INT Hlp_Is_oCMobLadder (var int ptr) {
if (!ptr) { return 0; };
var int vtbl;
vtbl = MEM_ReadInt (ptr);
return (vtbl == oCMobLadder_vtbl);
};
/*** Why are these 2 above not in Ikarus by default :-O ***/
/***
FrameHandler_VobTransport
Call this function every frame, in your frame trigger function for example.
Code within this function identifies object which should be moved around.
***/
FUNC VOID FrameHandler_VobTransport ()
{
//Identification of object which should be moved around
if (vobTransportMode == cvobTransportMode_Init)
{
//Safety-check
if (Hlp_IsValidNPC (hero))
{
var oCNPC her;
her = Hlp_GetNPC (hero);
var zCVob vobHero;
var zCVob vob;
//Is there anything in hero's focus ?
if (her.focus_vob)
{
//Move around following objects
if (Hlp_Is_oCMob (her.focus_vob))
|| (Hlp_Is_oCMobInter (her.focus_vob))
|| (Hlp_Is_oCMobFire (her.focus_vob))
|| (Hlp_Is_oCMobLockable (her.focus_vob))
// || (Hlp_Is_zCMover (her.focus_vob))
|| (Hlp_Is_oCMobContainer (her.focus_vob))
|| (Hlp_Is_oCMobDoor (her.focus_vob))
|| (Hlp_Is_oCMobLadder (her.focus_vob))
{
//Get pointer of moved object - it's in our focus
vobTransportPtr = her.focus_vob;
//Backup collision bitfields
vobTransportCollBits = VobGetCollBits (vobTransportPtr);
//Remove active collisions
VobRemoveCollBits (vobTransportPtr, vobTransportCollBits);
//Change vobTransportMode
vobTransportMode = cvobTransportMode_Moving;
};
} else
//Detect vob, which cannot be in her.focus_vob (e.g. zcVobs)
{
//Detect all nearby vobs
NPC_ClearVobList (hero);
NPC_CreateVobList (hero, 120);
//Loop through all nearby vobs
var int i; i = 0;
var int loop;
var int vobPtr;
MEM_InitLabels ();
loop = MEM_StackPos.position;
if (her.vobList_numInArray > 0)
{
vobPtr = MEM_ReadIntArray (her.vobList_array, i);
//Is this object zcVob ?
if (Hlp_Is_zcVob (vobPtr))
{
//Is this vob in front of player - can player see it?
if (NPC_CanSeeVob (hero, vobPtr))
{
//Get pointer of moved object
vobTransportPtr = vobPtr;
//Backup collision bitfields
vobTransportCollBits = VobGetCollBits (vobTransportPtr);
//Remove active collisions
VobRemoveCollBits (vobTransportPtr, vobTransportCollBits);
//Change vobTransportMode
vobTransportMode = cvobTransportMode_Moving;
};
};
//If vobTransportMode was not changed ... continue in loop (or exit)
if (vobTransportMode == cvobTransportMode_Init)
{
i += 1;
if (i < her.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
};
};
};
//If vobTransportMode was not changed ... then there was no object detected - disable vobTransportMode
if (vobTransportMode == cvobTransportMode_Init)
{
vobTransportMode = cvobTransportMode_Disabled;
};
};
//Moving mode
if (vobTransportMode == cvobTransportMode_Moving)
{
//Move object in front of hero
MoveVobInFront (hero, vobTransportPtr);
//Align vob to floor
if (vobTransportModeFloor == TRUE) {
SetVobToFloor (vobTransportPtr);
};
};
//Do we want to stop moving object around?
if (vobTransportMode == cvobTransportMode_Done)
{
//Restore collision bitfields
VobRestoreCollBits (vobTransportPtr, vobTransportCollBits);
//Disable vobTransportMode
vobTransportMode = cvobTransportMode_Disabled;
};
};
mud-freak
11.03.2019, 08:25
Thanks for sharing. A couple of suggestions:
Why are you hooking oCGame::HandleEvent, but then use MEM_KeyState? HandleEvent already provides the pressed key. Using MEM_KeyState is unreliable, because if called with the same key at another place, KEY_PRESSED might never be detected. To add new key detection directly to oCGame::HandleEvent, see this post.
/*** Why are these 2 above not in Ikarus by default :-O ***/
Hlp_Is_oCMobLadder could be added. But Hlp_Is_zcVob - the way you have it - does something different from Ikarus. The Hlp_Is_* functions in Ikarus are designed to include all inherited classes. Your function explicitly checks for zCVob only. This is of course not "illegal" but the name of the function is misleading compared to the Ikarus functions.
Because of this inheritance checking, you will only need to check Hlp_Is_oCMob and Hlp_Is_zCMover. All other checks are redundant.
Are you sure you want to allow moving movers? This probably won't move their key frames causing it to "jump" around when triggered.
You are traversing the voblist, why the need to check if a respective vob is a zCVob?
F a w k e s
11.03.2019, 11:08
Thanks for sharing. A couple of suggestions:
Why are you hooking oCGame::HandleEvent, but then use MEM_KeyState? HandleEvent already provides the pressed key. Using MEM_KeyState is unreliable, because if called with the same key at another place, KEY_PRESSED might never be detected. To add new key detection directly to oCGame::HandleEvent, see this post.
I actually saw your post - and that's why I decided to hook this one specifically. In Gothic 1 however ESI does not return pressed key. I was printing on screen all registers - and seems like EAX is kind of returning pressed key (if you hold it long enough) - however if you just quickly tap a key - this was not working. Then I tested MEM_KeyState - and this worked reliably for both tapping and pressing-holding a key. Maybe it's because I was not directly hooking this specific address oCGame__HandleEvent_dfltCase - here I have no idea how to find this address in G1.
Hlp_Is_oCMobLadder could be added. But Hlp_Is_zcVob - the way you have it - does something different from Ikarus. The Hlp_Is_* functions in Ikarus are designed to include all inherited classes. Your function explicitly checks for zCVob only. This is of course not "illegal" but the name of the function is misleading compared to the Ikarus functions.
Ok, I see your point here - Hlp_Is_zcVob might be misleading then as all inherited classes belong to zCVob. What would be a better name then? §medi
Because of this inheritance checking, you will only need to check Hlp_Is_oCMob and Hlp_Is_zCMover. All other checks are redundant.
Good point - thank you :)
Are you sure you want to allow moving movers? This probably won't move their key frames causing it to "jump" around when triggered.
Another good one! I actually do not want to move movers!
You are traversing the voblist, why the need to check if a respective vob is a zCVob?
Sooo this npc.vobList_array actually does not contain only zCVobs - when you call function NPC_CreateVobList (npc, range) - function collects all objects - NPCs, Items, Vobs, mobs - everything in specified range.
If I would not be verifying that object in a list and in front of hero is zCVob only - I would be able to move unintentionally around mobs (which I don't want to, if I want to move mobs, I will have them in a focus), Items and even NPCs! :)
https://www.youtube.com/watch?v=uQy0XP5m66A
mud-freak
11.03.2019, 12:05
I actually saw your post - and that's why I decided to hook this one specifically. In Gothic 1 however ESI does not return pressed key. I was printing on screen all registers - and seems like EAX is kind of returning pressed key (if you hold it long enough) - however if you just quickly tap a key - this was not working. Then I tested MEM_KeyState - and this worked reliably for both tapping and pressing-holding a key. Maybe it's because I was not directly hooking this specific address oCGame__HandleEvent_dfltCase - here I have no idea how to find this address in G1.
Yes, I forgot you are talking about Gothic 1. When I find the time, I will look up the respective address in Gothic 1. Essentially, this address is reached only when no other game key is pressed - the perfect address to "inject" new key strokes for a running game, without having to perform all the checks that are already passed.
Ok, I see your point here - Hlp_Is_zcVob might be misleading then as all inherited classes belong to zCVob. What would be a better name then? §medi
Because you are using this check explicitly and only once, I suppose you could just use the code inline.
//Is this object zcVob ?
if (MEM_ReadInt(vobPtr) == zCVob_vtbl)
{
Sooo this npc.vobList_array actually does not contain only zCVobs - when you call function NPC_CreateVobList (npc, range) - function collects all objects - NPCs, Items, Vobs, mobs - everything in specified range.
If I would not be verifying that object in a list and in front of hero is zCVob only - I would be able to move unintentionally around mobs (which I don't want to, if I want to move mobs, I will have them in a focus), Items and even NPCs! :)
Right! Seems I already got confused by Hlp_Is_zcVob.
You might also want to exclude doors (the player could just move locked doors to places that they are not supposed to reach yet) and ladders (the player could reach places that are not intended to be accessible - same problem as with the elevation and blink spell btw).
You could simplify your condition to:
if ((Hlp_Is_oCMob(her.focus_vob))
|| (Hlp_Is_oCMobFire (her.focus_vob)))
&& (!Hlp_Is_oCMobDoor(her.focus_vob))
&& (!Hlp_Is_oCMobLadder (her.focus_vob))
{You should also keep in mind that oCMobInters might be in use by other NPCs at the moment. Furthermore, once you moved a particular oCMobInter (e.g. a bench) NPCs whose daily routine depends on it might not find it anymore.
Fisk2033
11.03.2019, 12:55
Thanks for sharing Fawkes!
mud-freak
11.03.2019, 17:06
I looked up the address within oCGame::HandleEvent. I made into a universally usable script for Gothic 1 and Gothic 2.
One could argue now that this is not much different from a FrameFunction with MEM_KeyState. The difference is that this approach saves the extra work of checking if any menu is open, whether the player is in a dialog, whether the player may move, etc. Also this function is "event driven", meaning it is really only called when a key is pressed/held instead of every frame in vain. So it's arguably more performant.
gameKeyEvents.d – Adding new key events to oCGame::HandleEvent.
/*
* gameKeyEvents.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=26055992
*
* Handle key events from the player the way Gothic is doing it
*
* - Requires Ikarus, LeGo (HookEngine)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Initialize from Init_Global with
* Game_KeyEventInit();
* - Add your key event detection in Game_KeyEvent
*/
/*
* Customizable function to handle key events (pressed is FALSE: key is held, pressed is TRUE: key press onset)
* This function has to return TRUE if the given key was handled, and FALSE otherwise
*/
func int Game_KeyEvent(var int key, var int pressed) {
if (key == KEY_LBRACKET) && (pressed) {
// Entertain me!
return TRUE;
};
return FALSE;
};
/*
* This function is called during the running game when a key is pressed/held that is not already handled by Gothic
*/
func void Game_KeyEvent_() {
if (Game_KeyEvent(ESI, MEM_ReadByte(MEMINT_KeyToggle_Offset + ESI))) {
MEM_WriteByte(MEMINT_KeyToggle_Offset + ESI, 0); // Change toggle state only if key event was handled
};
};
/*
* Initialization function for custom key events
*/
func void Game_KeyEventInit() {
const int oCGame__HandleEvent_dfltCase_G1 = 6684404; //0x65FEF4
const int oCGame__HandleEvent_dfltCase_G2 = 7328820; //0x6FD434
HookEngineF(+MEMINT_SwitchG1G2(oCGame__HandleEvent_dfltCase_G1,
oCGame__HandleEvent_dfltCase_G2), 6, Game_KeyEvent_);
};
F a w k e s
11.03.2019, 17:27
Thank you !! Works like a charm :gratz
Why is there a + in front of MEMINT_Switch (works with & without it, so was wondering if this is just a typo)?
HookEngineF(+MEMINT_SwitchG1G2(oCGame__HandleEvent_dfltCase_G1,
oCGame__HandleEvent_dfltCase_G2), 6, Game_KeyEvent_);
Milky-Way
11.03.2019, 17:38
Few days ago I wrote a script that creates in inventory every existing in the game item. Maybe someone will make use of it :gratz
func void CreateAllItems(var c_npc slf) {
var int i;
repeat(i, MEM_Parser.symtab_table_numInArray);
var zCPar_Symbol symb; symb = _^(MEM_ReadIntArray(MEM_Parser.symtab_table_array, i));
if ((symb.bitfield & zCPar_Symbol_bitfield_type) != zPAR_TYPE_INSTANCE || STR_IndexOf(symb.name, ".") > 0 || !symb.parent) {
continue;
};
symb = _^(symb.parent);
if (Hlp_StrCmp(symb.name, "C_ITEM")) {
CreateInvItems(slf, i, 1);
}
else if (symb.parent && (symb.bitfield & zCPar_Symbol_bitfield_type) == zPAR_TYPE_PROTOTYPE) {
symb = _^(symb.parent);
if (Hlp_StrCmp(symb.name, "C_ITEM")) {
CreateInvItems(slf, i, 1);
};
};
end;
};
The game may freeze for a while because table of parser symbols is very huge.
What's the idea here that the variable i is the item to be created and not something using symb? Looking at the code, I would have thought that i is just an array index rather than the actual address of the object.
What's the idea here that the variable i is the item to be created and not something using symb? Looking at the code, I would have thought that i is just an array index rather than the actual address of the object.
An instance, like you would normally pass to CreateInvItem, is internally just an id (integer). The script iterates over all possible ids and if they are C_ITEMs, it creates them via the External.
mud-freak
11.03.2019, 18:09
Thank you !! Works like a charm :gratz
Why is there a + in front of MEMINT_Switch (works with & without it, so was wondering if this is just a typo)?
You could say, it is used to make the parser resolve a variable (lvalue) into its actual value (rvalue) before pushing it. You can find it a lot in Ikarus. At some point I had similar(?) code that would only work like that. I cannot remember the exact circumstances but I've grown accustomed to using it, when using MEMINT_SwitchG1G2 inside HookEngineF. Thanks for letting me know that it works regardless. I must have remembered it wrong.
For a bug to occur without the '+', the following conditions are necessary:
A function f that returns a variable directly, not a computation (return x; vs return x+1)
And a caller has to use f in a way such that it is called twice before either return value is used in a computation. Essentially:
func int thingy(var int x) {
return x;
};
func int bug() {
// Prints 8, not 7
print(IntToString(thingy(3)+thingy(4)));
};
Changing thingy to 'return +x;' instead fixes the bug.
Milky-Way
11.03.2019, 22:44
An instance, like you would normally pass to CreateInvItem, is internally just an id (integer). The script iterates over all possible ids and if they are C_ITEMs, it creates them via the External.
Thanks for the explanation, that's exactly what I needed :)
mud-freak
12.03.2019, 16:10
For a bug to occur without the '+', the following conditions are necessary:
A function f that returns a variable directly, not a computation (return x; vs return x+1)
And a caller has to use f in a way such that it is called twice before either return value is used in a computation. Essentially:
func int thingy(var int x) {
return x;
};
func int bug() {
// Prints 8, not 7
print(IntToString(thingy(3)+thingy(4)));
};
Changing thingy to 'return +x;' instead fixes the bug.
Danke für das Beispiel. Jetzt ist mir auch wieder eingefallen, wo das Problem im Zusammenhang mit HookEngineF und MEMINT_SwitchG1G2 auftrat:
HookEngineF(MEMINT_SwitchG1G2(addrG1, addrG2), MEMINT_SwitchG1G2(5, 7), someFunction);
Fisk2033
13.03.2019, 06:47
I want to use Fawkes "MoveVobs"-Script in Gothic 2. How I can define "oCMobLadder_vtbl"? Maybe its my mistake but I cant find it anywhere (except Ikarus_Const_G1.d).
F a w k e s
13.03.2019, 11:43
I want to use Fawkes "MoveVobs"-Script in Gothic 2. How I can define "oCMobLadder_vtbl"? Maybe its my mistake but I cant find it anywhere (except Ikarus_Const_G1.d).
This should be constant for G2A:
//0x0083CF2C const oCMobLadder::`vftable'
const int oCMobLadder_vtbl = 8638252;
F a w k e s
14.03.2019, 16:10
Hello folks,
Slightly improved version:
Press '[' to activate select mode.
Select mode:
Use left/right arrows to select objects you want to move/rotate.
Press '[' to confirm selection of the object - this will activate Transform mode.
Press ']' to cancel select mode.
Transform mode:
Move around hero to move around object.
Press 'E' to adjust elevation: use up/down arrows to adjust vertical position of object
Press 'X', 'Y', or 'Z' to activate rotation mode.
Press '[' to stop interaction.
Press ']' to align to surface.
Rotation mode:
Use arrow keys to adjust rotation of respective axis.
https://www.youtube.com/watch?v=E-0-CvXJiQw
Here is code if you would be interested:
Required:
Ikarus 1.2.1
Function NPC_CanSee & AI_TurnToVobPtr from:
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page7?p=26640112&viewfull=1#post26640112
Function SetVobToFloor from Scriptbin\InsertAnything:
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page2?p=25712257&viewfull=1#post25712257
Functions from Scriptbin\NPCVobList.d:
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page4?p=26052199&viewfull=1#post26052199
Functions from Scriptbin\gameKeyEvents.d
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page4?p=26055992&viewfull=1#post26055992
Other variables and functions which are required:
//--- vob Transport global variables & constants
const int vobTRange = 300;
var int vobTPtr;
var int vobTVobList;
var int vobTMode;
const int cvobTMode_Disabled = 0;
const int cvobTMode_Init = 1;
const int cvobTMode_Select = 2;
const int cvobTMode_Select_Next = 3;
const int cvobTMode_Select_Previous = 4;
const int cvobTMode_Select_Confirm = 5;
const int cvobTMode_Moving = 6;
const int cvobTMode_Done = 7;
var int vobTModeAlignToFloor;
var int vobTCollBits;
var int vobTMovementSpeed;
var int vobTModeXYZE;
const int cvobTRotationMode_0 = 0;
const int cvobTRotationMode_X = 1;
const int cvobTRotationMode_Y = 2;
const int cvobTRotationMode_Z = 3;
const int cvobTRotationMode_E = 4; //Elevation
var int vobTRotX;
var int vobTRotY;
var int vobTRotZ;
var int vobTElevation;
/***
Hero_Lock will freeze player in place (he will not move)
***/
FUNC VOID Hero_Lock () {
NPC_ClearAIQueue (hero);
var oCNpc her;
her = Hlp_GetNpc (hero);
her._zCVob_bitfield[2] = (her._zCVob_bitfield[2] & ~ zCVob_bitfield2_sleepingMode) | 0;
};
/***
Hero_UnLock will unfreeze player
***/
FUNC VOID Hero_UnLock () {
NPC_ClearAIQueue (hero);
var oCNpc her;
her = Hlp_GetNpc (hero);
her._zCVob_bitfield[2] = (her._zCVob_bitfield[2] & ~ zCVob_bitfield2_sleepingMode) | 1;
};
/***
Will draw BBox3D on Vob
***/
FUNC VOID Vob_SetDrawBBox3D (var int vobPtr, var int onOff) {
//00645030 .text Debug data ?SetDrawBBox3D@zCVob@@QAEXH@Z
const int zCVob__SetDrawBBox3D_G1 = 6574128;
//0x006CFFE0 public: void __thiscall zCVob::SetDrawBBox3D(int)
const int zCVob__SetDrawBBox3D_G2 = 7143392;
if (!vobPtr) { return; };
CALL_IntParam (onOff);
CALL__thiscall (vobPtr, MEMINT_SwitchG1G2 (zCVob__SetDrawBBox3D_G1, zCVob__SetDrawBBox3D_G2));
};
/***
VobGetCollBits will return collision bitfield values
***/
FUNC INT VobGetCollBits (var int vobPtr) {
if (!vobPtr) { return 0; };
var int collBits; collBits = (zCVob_bitfield0_collDetectionStatic + zCVob_bitfield0_collDetectionDynamic);
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
return (vob.bitfield[0] & (collBits));
};
/***
VobRemoveCollBits - will remove collisions based on input bitfield values in collBits
***/
FUNC VOID VobRemoveCollBits (var int vobPtr, var int collBits) {
if (!vobPtr) { return; };
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
vob.bitfield[0] = vob.bitfield[0] & ~ (collBits);
};
/***
VobRestoreCollBits - will apply collisions based on input bitfield values in collBits
***/
FUNC VOID VobRestoreCollBits (var int vobPtr, var int collBits) {
if (!vobPtr) { return; };
var zCVob vob; vob = MEM_PtrToInst (vobPtr);
vob.bitfield[0] = vob.bitfield[0] | (collBits);
};
/***
Rotates vob on its X axis
***/
func void TRF_RotateLocalX(var zCVob obj, var int x) {
//005EE1A0 .text Debug data ?RotateLocalX@zCVob@@QAEXM@Z
const int zCVob__RotateLocalX_G1 = 6218144;
//0x0061B6B0 public: void __thiscall zCVob::RotateLocalX(float)
const int zCVob__RotateLocalX_G2 = 6403760;
CALL_FloatParam(x);
CALL__thiscall(MEM_InstToPtr(obj), MEMINT_SwitchG1G2 (zCVob__RotateLocalX_G1, zCVob__RotateLocalX_G2));
};
/***
Rotates vob on its Y axis
***/
func void TRF_RotateLocalY(var zCVob obj, var int x) {
//005EE210 .text Debug data ?RotateLocalY@zCVob@@QAEXM@Z
const int zCVob__RotateLocalY_G1 = 6218256;
//0x0061B720 public: void __thiscall zCVob::RotateLocalY(float)
const int zCVob__RotateLocalY_G2 = 6403872;
CALL_FloatParam(x);
CALL__thiscall(MEM_InstToPtr(obj), MEMINT_SwitchG1G2 (zCVob__RotateLocalY_G1, zCVob__RotateLocalY_G2));
};
/***
Rotates vob on its Z axis
***/
func void TRF_RotateLocalZ(var zCVob obj, var int x) {
//005EE280 .text Debug data ?RotateLocalZ@zCVob@@QAEXM@Z
const int zCVob__RotateLocalZ_G1 = 6218368;
//0x0061B790 public: void __thiscall zCVob::RotateLocalZ(float)
const int zCVob__RotateLocalZ_G2 = 6403984;
CALL_FloatParam(x);
CALL__thiscall(MEM_InstToPtr(obj), MEMINT_SwitchG1G2 (zCVob__RotateLocalZ_G1, zCVob__RotateLocalZ_G2));
};
/***
Rotates vob on its X Y Z axes
***/
func void TRF_RotateLocal(var zCVob obj, var int x, var int y, var int z) {
TRF_RotateLocalX(obj, x);
TRF_RotateLocalY(obj, y);
TRF_RotateLocalZ(obj, z);
};
//http://themodders.org/index.php?topic=10383.msg1256794#msg1256794
func void Vob_Move(var int ptr, var int x, var int y, var int z) {
const int zCVob__Move_G1 = 6217184; //0x5EDDE0
const int zCVob__Move_G2 = 6402784; //0x61B2E0
CALL_FloatParam (x);
CALL_FloatParam (y);
CALL_FloatParam (z);
CALL__thiscall (ptr, MEMINT_SwitchG1G2 (zCVob__Move_G1, zCVob__Move_G2));
};
func void MoveVobInFront(var int slfInstance, var int vobPtr) {
var zCVob slf; slf = Hlp_GetNPC (slfInstance);
var zCVob vob; vob = _^ (vobPtr);
//Move vob to NPC X Y Z coordinates
Vob.trafoObjToWorld [03] = slf.trafoObjToWorld [03];
Vob.trafoObjToWorld [07] = slf.trafoObjToWorld [07];
Vob.trafoObjToWorld [11] = slf.trafoObjToWorld [11];
//Adjust 'elevation'
Vob.trafoObjToWorld [07] = addf (Vob.trafoObjToWorld [07], mkf (vobTElevation));
//Keep vob data (rotation)
MEM_CopyBytes(_@(vob) + 60, vobPtr + 60, 64);
// Vob vor den Helden setzen
var int delta;
delta = mkf(150);
Vob_Move (vobPtr, mulf (slf.trafoObjToWorld[10], delta), mulf (slf.trafoObjToWorld[6], delta), mulf (slf.trafoObjToWorld[2], delta));
};
FrameHandler_VobTransport function - has to be called every frame:
FUNC VOID FrameHandler_vobTransport ()
{
//Loop through all nearby vobs
var int i;
var int loop;
var int vobPtr;
//Identification of object which should be moved around
if (vobTMode == cvobTMode_Init) {
//Safety-check
if (Hlp_IsValidNPC (hero)) {
var oCNPC her;
her = Hlp_GetNPC (hero);
var zCVob vob;
//Reset
vobTPtr = 0;
//Is there anything in hero's focus ?
if (her.focus_vob) {
//Move around following objects
if (Hlp_Is_oCMob (her.focus_vob))
|| (Hlp_Is_oCMobInter (her.focus_vob))
|| (Hlp_Is_oCMobFire (her.focus_vob))
|| (Hlp_Is_oCMobLockable (her.focus_vob))
|| (Hlp_Is_oCMobContainer (her.focus_vob))
|| (Hlp_Is_oCMobDoor (her.focus_vob))
|| (Hlp_Is_oCMobLadder (her.focus_vob))
{
//Get pointer of focus_vob
vobTPtr = her.focus_vob;
//Change vobTMode
vobTMode = cvobTMode_Select;
};
};
//Detect all nearby objects
NPC_ClearVobList (hero);
NPC_CreateVobList (hero, vobTRange);
vobTVobList = her.vobList_array;
//If there was nothing in focus - find an object
if (!vobTPtr) {
MEM_InitLabels ();
i = 0;
loop = MEM_StackPos.position;
if (her.vobList_numInArray > 0) {
vobPtr = MEM_ReadIntArray (her.vobList_array, i);
if (Hlp_Is_oCMob (vobPtr))
|| (Hlp_Is_oCMobInter (vobPtr))
|| (Hlp_Is_oCMobFire (vobPtr))
|| (Hlp_Is_oCMobLockable (vobPtr))
|| (Hlp_Is_oCMobContainer (vobPtr))
|| (Hlp_Is_oCMobDoor (vobPtr))
|| (Hlp_Is_oCMobLadder (vobPtr))
//zCVob
|| (MEM_ReadInt (vobPtr) == zCVob_vtbl)
{
//Is this vob in front of player - can player see it?
if (NPC_CanSee (hero, vobPtr, 0)) {
//Get pointer of moved object
vobTPtr = vobPtr;
//Change vobTMode
vobTMode = cvobTMode_Select;
};
};
//If vobTMode was not changed ... continue in loop (or exit)
if (vobTMode == cvobTMode_Init) {
i += 1;
if (i < her.vobList_numInArray) {
MEM_StackPos.position = loop;
};
};
};
};
};
//If vobTMode was not changed ... then there was no object detected - disable vobTMode
if (vobTMode == cvobTMode_Init) {
vobTMode = cvobTMode_Disabled;
} else {
//Select mode - draw BBox and lock hero
Vob_SetDrawBBox3D (vobTPtr, 1);
Hero_Lock ();
};
};
if (vobTMode == cvobTMode_Select_Next) {
//loop through vob list - select next in the list
var int flg_Next; flg_Next = FALSE;
/*
NPC_ClearVobList (hero);
NPC_CreateVobList (hero, vobTRange);
*/
her.vobList_array = vobTVobList;
MEM_InitLabels ();
i = 0;
loop = MEM_StackPos.position;
if (her.vobList_numInArray > 0) {
vobPtr = MEM_ReadIntArray (her.vobList_array, i);
if (Hlp_Is_oCMob (vobPtr))
|| (Hlp_Is_oCMobInter (vobPtr))
|| (Hlp_Is_oCMobFire (vobPtr))
|| (Hlp_Is_oCMobLockable (vobPtr))
|| (Hlp_Is_oCMobContainer (vobPtr))
|| (Hlp_Is_oCMobDoor (vobPtr))
|| (Hlp_Is_oCMobLadder (vobPtr))
//zCVob
|| (MEM_ReadInt (vobPtr) == zCVob_vtbl)
{
if (flg_Next == FALSE) {
if (vobTPtr == vobPtr) {
flg_Next = TRUE;
};
} else {
//Remove BBox from last vobPtr
Vob_SetDrawBBox3D (vobTPtr, 0);
//Get pointer of moved object
vobTPtr = vobPtr;
//Add Bbox to next vobPtr
Vob_SetDrawBBox3D (vobTPtr, 1);
NPC_ClearAIQueue (hero);
AI_TurnToVobPtr (hero, vobTPtr);
//Change vobTMode
vobTMode = cvobTMode_Select;
};
};
//If vobTMode was not changed ... continue in loop (or exit)
if (vobTMode == cvobTMode_Select_Next) {
i += 1;
if (i < her.vobList_numInArray) {
MEM_StackPos.position = loop;
};
vobTMode = cvobTMode_Select;
};
};
};
if (vobTMode == cvobTMode_Select_Previous) {
//loop through vob list - select previous in the list
var int flg_Previous; flg_Previous = FALSE;
//NPC_ClearVobList (hero);
//NPC_CreateVobList (hero, vobTRange);
her.vobList_array = vobTVobList;
MEM_InitLabels ();
i = 0;
loop = MEM_StackPos.position;
if (her.vobList_numInArray > 0) {
vobPtr = MEM_ReadIntArray (her.vobList_array, i);
if (Hlp_Is_oCMob (vobPtr))
|| (Hlp_Is_oCMobInter (vobPtr))
|| (Hlp_Is_oCMobFire (vobPtr))
|| (Hlp_Is_oCMobLockable (vobPtr))
|| (Hlp_Is_oCMobContainer (vobPtr))
|| (Hlp_Is_oCMobDoor (vobPtr))
|| (Hlp_Is_oCMobLadder (vobPtr))
//zCVob
|| (MEM_ReadInt (vobPtr) == zCVob_vtbl)
{
if (flg_Previous == FALSE) {
if (vobTPtr == vobPtr) {
flg_Previous = TRUE;
};
} else {
//Remove Bbox from last vobPtr
Vob_SetDrawBBox3D (vobTPtr, 0);
//Get pointer of moved object
vobTPtr = vobPtr;
//Add Bbox to previous vobPtr
Vob_SetDrawBBox3D (vobTPtr, 1);
NPC_ClearAIQueue (hero);
AI_TurnToVobPtr (hero, vobTPtr);
//Change vobTMode
vobTMode = cvobTMode_Select;
};
};
//If vobTMode was not changed ... continue in loop (or exit)
if (vobTMode == cvobTMode_Select_Previous) {
if (flg_Previous == FALSE) {
i += 1;
if (i < her.vobList_numInArray) {
MEM_StackPos.position = loop;
};
} else {
if (i > 0) {
i -= 1;
};
MEM_StackPos.position = loop;
};
vobTMode = cvobTMode_Select;
};
};
};
if (vobTMode == cvobTMode_Select_Confirm) {
//Backup collision bitfields
vobTCollBits = VobGetCollBits (vobTPtr);
//Remove active collisions
VobRemoveCollBits (vobTPtr, vobTCollBits);
vobTMode = cvobTMode_Moving;
Hero_UnLock ();
};
//Moving mode
if (vobTMode == cvobTMode_Moving) {
if (vobTPtr) {
//Move object in front of hero (X, Y, Z)
MoveVobInFront (hero, vobTPtr);
Vob = _^ (vobTPtr);
//Rotate
TRF_RotateLocal (Vob, mkf (vobTRotX), mkf (vobTRotY), mkf (vobTRotZ));
vobTRotX = 0; vobTRotY = 0; vobTRotZ = 0;
//Align vob to floor
if (vobTModeXYZE != cvobTRotationMode_E) {
if (vobTModeAlignToFloor == TRUE) {
SetVobToFloor (vobTPtr);
};
};
};
};
//Stop transport mode
if (vobTMode == cvobTMode_Done) {
//Restore collision bitfields
VobRestoreCollBits (vobTPtr, vobTCollBits);
//Disable vobTMode
vobTMode = cvobTMode_Disabled;
};
};
This one will be triggered from gameKeyEvents.d
/*
* Customizable function to handle key events (pressed is FALSE: key is held, pressed is TRUE: key press onset)
*/
func void Game_KeyEvent(var int key, var int pressed) {
//--- Activate Vob Transport mode
if (key == KEY_LBRACKET) && (pressed)
{
//Start vob/focus selection
if (vobTMode == cvobTMode_Disabled) {
vobTMode = cvobTMode_Init;
vobTMovementSpeed = 1;
};
//Confirm selection
if (vobTMode == cvobTMode_Select) {
vobTMode = cvobTMode_Select_Confirm;
};
//End interaction
if (vobTMode == cvobTMode_Moving) {
Vob_SetDrawBBox3D (vobTPtr, 0);
Hero_UnLock ();
vobTModeXYZE = cvobTRotationMode_0;
vobTMode = cvobTMode_Done;
};
};
if (vobTMode == cvobTMode_Select) {
//Select previous Vob
if (key == KEY_LEFTARROW) && (pressed) {
if (vobTMode == cvobTMode_Select) {
vobTMode = cvobTMode_Select_Previous;
};
};
//Select next Vob
if (key == KEY_RIGHTARROW) && (pressed) {
if (vobTMode == cvobTMode_Select) {
vobTMode = cvobTMode_Select_Next;
};
};
//Cancel selection (escape is bringing main menu :()
if (key == KEY_ESCAPE) && (pressed)
|| (key == KEY_RBRACKET) && (pressed) {
Vob_SetDrawBBox3D (vobTPtr, 0);
Hero_UnLock ();
vobTModeXYZE = cvobTRotationMode_0;
vobTMode = cvobTMode_Disabled;
};
};
if (vobTMode == cvobTMode_Moving) {
//Align to surface on/off
if (vobTModeXYZE != cvobTRotationMode_E) {
if (key == KEY_RBRACKET) && (pressed) {
vobTModeAlignToFloor = !vobTModeAlignToFloor;
};
};
//Space - increase movement speed 1 - 10 - 20
if (key == KEY_SPACE) && (pressed) {
if (vobTMovementSpeed == 1) {
vobTMovementSpeed = 10;
} else
if (vobTMovementSpeed == 10) {
vobTMovementSpeed = 20;
} else {
vobTMovementSpeed = 1;
};
};
//X - rotate X axis
if (key == KEY_X) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_X) {
vobTModeXYZE = cvobTRotationMode_0;
Hero_UnLock ();
} else {
vobTModeXYZE = cvobTRotationMode_X;
Hero_Lock ();
};
};
//Y - rotate Y axis
if (key == KEY_Y) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_Y) {
vobTModeXYZE = cvobTRotationMode_0;
Hero_UnLock ();
} else {
vobTModeXYZE = cvobTRotationMode_Y;
Hero_Lock ();
};
};
//Z - rotate Z axis
if (key == KEY_Z) && (pressed)
{
if (vobTModeXYZE == cvobTRotationMode_Z) {
vobTModeXYZE = cvobTRotationMode_0;
Hero_UnLock ();
} else {
vobTModeXYZE = cvobTRotationMode_Z;
Hero_Lock ();
};
};
//E - elevate
if (key == KEY_E) && (pressed)
{
if (vobTModeXYZE == cvobTRotationMode_E) {
vobTModeXYZE = cvobTRotationMode_0;
Hero_UnLock ();
} else {
vobTModeXYZE = cvobTRotationMode_E;
Hero_Lock ();
};
};
//--- Rotation - left / down key
if (key == KEY_LEFTARROW) && (pressed)
|| (key == KEY_DOWNARROW) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_X) {
vobTRotX -= vobTMovementSpeed;
};
if (vobTModeXYZE == cvobTRotationMode_Y) {
vobTRotY -= vobTMovementSpeed;
};
if (vobTModeXYZE == cvobTRotationMode_Z) {
vobTRotZ -= vobTMovementSpeed;
};
};
// right / up key
if (key == KEY_RIGHTARROW) && (pressed)
|| (key == KEY_UPARROW) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_X) {
vobTRotX += vobTMovementSpeed;
};
if (vobTModeXYZE == cvobTRotationMode_Y) {
vobTRotY += vobTMovementSpeed;
};
if (vobTModeXYZE == cvobTRotationMode_Z) {
vobTRotZ += vobTMovementSpeed;
};
};
//--- Elevation - up - down key
if (key == KEY_UPARROW) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_E) {
vobTElevation += vobTMovementSpeed;
};
};
if (key == KEY_DOWNARROW) && (pressed) {
if (vobTModeXYZE == cvobTRotationMode_E) {
vobTElevation -= vobTMovementSpeed;
};
};
//--- Safety checks for rotation
if (vobTRotX < 0) { vobTRotX = 360 - (0 - vobTRotX); };
if (vobTRotY < 0) { vobTRotY = 360 - (0 - vobTRotY); };
if (vobTRotZ < 0) { vobTRotZ = 360 - (0 - vobTRotZ); };
if (vobTRotX > 360) { vobTRotX = vobTRotX - 360; };
if (vobTRotY > 360) { vobTRotY = vobTRotY - 360; };
if (vobTRotZ > 360) { vobTRotZ = vobTRotZ - 360; };
};
};
Fisk2033
15.03.2019, 08:36
I want to use this system in Gothic 2... I paste your code into a new file and added the missing G2 constats (oCMobLadder_vtbl and cZVob_vtbl). After that I added "Game_KeyEventInit()" and "FF_ApplyOnce(FrameHandler_VobTransport)" to my init_global... But I can't activate the transport mode... Did I miss something?
Fisk2033
17.03.2019, 06:47
Ok it was my fault... works like a charm in g2! :)
Fawkes, this is amazing!:gratz
It would be nice to add some spell effect. will looks more in Gothic style.
Say, since when do you work at this moving vob feature? I got it in my mod since about 2016 and it was one of my best features... until you released this script for everyone. I don't wanna say that I'm pissed. I'm just slightly... no theres no better word. I AM pissed. All the hard work and now everyone has it.
F a w k e s
24.03.2019, 12:48
Say, since when do you work at this moving vob feature? I got it in my mod since about 2016 and it was one of my best features... until you released this script for everyone. I don't wanna say that I'm pissed. I'm just slightly... no theres no better word. I AM pissed. All the hard work and now everyone has it.
Hi Bisasam,
I started on 10th of March and finished what I wanted to have in it on 14th of March. I believe in open-source, that's why I shared my code, hoping it can be helpful to others.
Say, since when do you work at this moving vob feature? I got it in my mod since about 2016 and it was one of my best features... until you released this script for everyone. I don't wanna say that I'm pissed. I'm just slightly... no theres no better word. I AM pissed. All the hard work and now everyone has it.
Ich kann ja verstehen, dass dich das ein bisschen ärgert, aber du kannst da F a w k e s nicht wirklich einen Vorwurf machen, meine ich. Er hat das Feature ja wohl völlig unabhängig von dir entwickelt. Ich weiß jetzt gar nicht, ob du das mal öffentlich außerhalb von deinen Tests präsentiert hast. Wie hätte F a w k es denn dieses Feature „stehlen“ sollen?
Say, since when do you work at this moving vob feature? I got it in my mod since about 2016 and it was one of my best features... until you released this script for everyone. I don't wanna say that I'm pissed. I'm just slightly... no theres no better word. I AM pissed. All the hard work and now everyone has it.
Modding is not a competition. It should be a way to express your ideas and create a wonderful and interesting experience for others. Feeling upset is okay, but expressing this anger openly (and feeling justified to do that!) just because someone decided to share his work with the community is despicable. How would you feel if Sektenspinner or Gottfried and me* never shared our work? Your feature relies on the spirit of open source as well, don't forget that.
*Increasingly often I get the feeling I should simply license LeGo under the GPL to be done with this high school drama.
Das hier ist ein Forum, oder? Warum kann ich nicht schreiben, wie ich mich fühle und warum? Ich habe ihn nicht beleidigt. Es ist nur so, dass das (in meinen Augen) häufiger vorkommt. Letztens hat Mud-Freak ja meinen kompletten Taschendiebstahlscode neu geschrieben. Ich sage nicht, dass ich die Arbeit daran schlecht finde (zumal er ja auch noch gesagt hat, dass er lieber meinen Namen drunter stehen hätte weil er weiß, wie viel Arbeit da reingeflossen ist), es geht eher darum, dass mir oft das Gefühl gegeben wird, dass jeder alles besser kann als ich. Und das frustriert. Wieso werde ich denn gleich so angefahren, wenn ich das mal anspreche? Ich bin keine Heilige, ich habe Gefühle und die würde ich gern an- und aussprechen statt sie in mich reinzufressen.
Im Moment komme ich mir so vor, als würde ich eine Wissenschaftliche Arbeit verfassen und jedes Mal, wenn ich etwas beinahe releasefertig habe, bringt jemand anderes es früher raus, wodurch ich wie der Abkupferer aussehe. Dabei soll das hier doch kein Wettbewerb sein. Warum fühlt es sich nur so an? :(
Das Feature wurde übrigens schon in diesem Video öffentlich vorgestellt: https://www.youtube.com/watch?v=lve0nNTqFPs (letzte 5 Minuten) Soweit ich weiß gabs da auch ne News zu.
Hey Siemekk, thanks for the ani scripts!
Because i finished work in Ikarus and now i'm using AST(Agama Script Tools) i decided to share my scripts :)
Some Engine scripts:
func void SetAsPlayer (var C_NPC slf)
{
const int oCNpc__SetAsPlayer = 7612064;
CALL__thiscall (MEM_InstToPtr (slf), oCNpc__SetAsPlayer);
};
func int Menu_ReadInt(var string category, var string name) //int
{
var string value; value = Mem_GetGothOpt(category, name);
return STR_ToInt(value);
};
func void Menu_WriteInt(var string category, var string name, var int value)
{
MEM_SetGothOpt(category,name, IntToString(value));
};
func int oCNpc_GetModel(var int npc) //zCModel*
{
const int oCNpc__GetModel = 7571232;
CALL__thiscall(MEM_InstToPtr(npc), oCNpc__GetModel);
return CALL_RetValAsInt();
};
func int AniIsActive(var c_npc slf, var string aniname) //BOOL
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel_AniIsActive = 5727888;//0x00576690
CALL_zStringPtrParam(Str_Upper(aniname));
CALL__thiscall(ptr,zCModel_AniIsActive);
return CALL_RetValAsInt();
};
func int zCVisual_LoadVisual(var string vis) //zCVisual*
{
const int zCVisual__LoadVisual = 6318800; //0x00606AD0
CALL_zStringPtrParam(STR_Upper(vis));
CALL__cdecl(zCVisual__LoadVisual);
return CALL_RetValAsInt();
};
func int StringIsEmpty(var string str) //BOOL
{
if(STR_Len(str)>=1)
{
return true;
};
return false;
};
func void Set_ItemVisual(var int itm, var string vis)
{
const int oCItem__SetVisual = 7411984; //0x00711910
CALL_PtrParam(zCVisual_LoadVisual(vis));
CALL__thiscall(MEM_InstToPtr(itm),oCItem__SetVisual);
};
func int zCModel_SearchNode(var int model, var string node) //zCModelNodeInst*
{
CALL_zStringPtrParam(Str_Upper(node));
CALL__thiscall(model, zCModel__SearchNode);
return CALL_RetValAsInt();
};
func void Ext_PutInSlot(var int pnpc, var string node, var string visual)
{
const int zCModel__SetNodeVisual = 5739168;
var int model; model = oCNpc_GetModel(pnpc);
var int vis; vis = zCVisual_LoadVisual(visual);
var int nde; nde = zCModel_SearchNode(model,node);
CALL_IntParam(1); //3 param
CALL_PtrParam(vis); //2 param - vis
CALL_PtrParam(nde); //1 param get node
CALL__thiscall(model, zCModel__SetNodeVisual);
};
func void Ext_PutInSlot2(var int pnpc, var string node, var int vob) //Used to mounts system (hero, "NODE", her.focus_vob)
{
var int model; model = oCNpc_GetModel(pnpc);
var int nde; nde = zCModel_SearchNode(model,node);
CALL_PtrParam(1); //1 param get node
var int vb; vb = MEM_InstGetOffset(vob);
CALL_PtrParam(MEM_ReadInt(vb+200)); //2 param - vis
CALL_PtrParam(nde); //1 param get node
CALL__thiscall(model, zCModel__SetNodeVisual);
};
func int Get_AniIDFromAniName(var int slf, var string aniName) //int
{
const int zCModel__AniIDFromAniName = 6365296; //0x00612070
var int model; model = oCNpc_GetModel(slf);
CALL_zStringPtrParam(Str_Upper(aniName));
CALL__thiscall(model,zCModel__AniIDFromAniName);
return CALL_RetValAsInt();
};
func int GetAniFromAniID(var c_npc slf, var string aniName) //zCModelAni*
{
const int zCModel__GetAniFromAniID = 4665168; //0x00472F50
var int model; model = oCNpc_GetModel(slf);
var int ani; ani = Get_AniIDFromAniName(slf,aniName);
CALL_PtrParam(ani);
CALL__thiscall(model,zCModel__GetAniFromAniID);
return CALL_RetValAsInt();
};
func void Set_AniFPS(var c_npc slf, var string aniName, var int FPS)
{
var int ptr;
ptr = GetAniFromAniID(slf, aniName);
MEM_WriteInt(ptr+176, mkf(FPS));
};
func int GetNodeBBox3D(var int slf, var string bone) //Get Node BBox - Hitboxes or Check collision
{
var int model; model = oCNpc_GetModel(slf);
var int node; node = zCModel_SearchNode(model,bone);
CALL_PtrParam(node);
CALL_RetValIsStruct(24); //6*4
CALL__thiscall(model,zCModel__GetBBox3DNodeWorld);
return CALL_RetValAsPtr();
};
func oCItem Hlp_GetItem(var int inst) //oCItem*
{
var zCPar_Symbol symb; symb = _^(MEM_GetSymbolByIndex(inst));
_^(symb.offset);
};
I changed your ani functions a bit and made some more.
Your scripts:
/**
* get pointer to model object
* @param npc pointer to npc object
*/
func int oCNpc_GetModel(var int npc) //zCModel*
{
const int oCNpc__GetModel = 7571232;
CALL__thiscall(MEM_InstToPtr(npc), oCNpc__GetModel);
return CALL_RetValAsInt();
};
/**
* checks if ani with given name is currently active
* @param slf npc
* @param aniname name of ani
*/
func int zCModel_AniIsActive(var c_npc slf, var string aniname) //BOOL
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel_AniIsActive = 5727888;//0x00576690
CALL_zStringPtrParam(Str_Upper(aniname));
CALL__thiscall(ptr,zCModel_AniIsActive);
return CALL_RetValAsInt();
};
/**
* get pointer to ani object with given name of ani
* @param slf npc
* @param aniname name of ani
*/
func int zCModel_AniFromAniName(var c_npc slf, var string aniName) //zCModelAni*
{
const int zCModel__AniIDFromAniName = 6365296; //0x00612070
const int zCModel__GetAniFromAniID = 4665168; //0x00472F50
var int model; model = oCNpc_GetModel(slf);
// get ani id
CALL_zStringPtrParam(Str_Upper(aniName));
CALL__thiscall(model,zCModel__AniIDFromAniName);
var int id; id = CALL_RetValAsInt();
// get ani object ptr
CALL_PtrParam(id);
CALL__thiscall(model,zCModel__GetAniFromAniID);
// return ptr to ani object
return CALL_RetValAsInt();
};
And my new functions:
// Active ani functions
/**
* get progress of given ani as float (0.0 to 1.0)
* given ani should be active (check first)
* @param slf npc
* @param aniName name of the ani
*/
func int zCModel_GetProgressPercent(var c_npc slf, var string aniName) // float
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel__GetProgressPercent = 5763728; // 0x0057F290
CALL_RetValIsFloat(); // method returns float
CALL_zStringPtrParam(Str_Upper(aniName));
CALL__thiscall(ptr,zCModel__GetProgressPercent);
return CALL_RetValAsFloat();
};
/**
* get pointer to active ani object
* @param slf npc
* @param zCModelAniPtr pointer to ani object
*/
func int zCModel_GetActiveAni(var c_npc slf, var int zCModelAniPtr) // zCModelAniActive*
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel__GetActiveAni = 5745568; // 0x0057ABA0
CALL_PtrParam(zCModelAniPtr);
CALL__thiscall(ptr,zCModel__GetActiveAni);
return CALL_RetValAsInt();
};
/**
* get the progress of current active ani as float (0.0 to 1.0)
* @param zCModelAniActivePtr pointer to active ani object
*/
func int zCModelAniActive_GetProgressPercent(var int zCModelAniActivePtr) // float
{
const int zCModelAniActive__GetProgressPercent = 5729376; // 0x00576C60
CALL_RetValIsFloat(); // method returns float
CALL__thiscall(zCModelAniActivePtr,zCModelAniActive__GetProgressPercent);
return CALL_RetValAsFloat();
};
// Model Ani
/**
* check if ani object is active
* @param slf npc
* @param zCModelAniPtr pointer to ani object
*/
func int zCModel_IsAniActive(var c_npc slf, var int zCModelAniPtr) //BOOL
{
var int ptr; ptr = oCNpc_GetModel(slf);
const int zCModel__IsAniActive = 4665232; //0x00472F90
CALL_PtrParam(zCModelAniPtr);
CALL__thiscall(ptr,zCModel__IsAniActive);
return CALL_RetValAsInt();
};
/**
* get pointer to ani with given id
* @param slf npc
* @param id id of ani
*/
func int zCModel_GetAniFromAniID(var c_npc slf, var int id) //zCModelAni*
{
const int zCModel__GetAniFromAniID = 4665168; //0x00472F50
var int model; model = oCNpc_GetModel(slf);
CALL_IntParam(id);
CALL__thiscall(model,zCModel__GetAniFromAniID);
return CALL_RetValAsInt();
};
/**
* get name of ani
* @param zCModelAniPtr pointer to ani object
*/
func string zCModel_GetAniName(var int zCModelAniPtr) //zSTRING&
{
const int zCModelAni__GetAniName = 5886304; //0x0059D160
CALL__thiscall(zCModelAniPtr,zCModelAni__GetAniName);
return CALL_RetValAszStringPtr();
};
/**
* recursive function to find the current active animation
* @param aniIdLoop current point of search (should be 0 at start)
* @param endLoop ending point of search
*/
func int GetAniID(var int aniIdLoop, var int endLoop) //zCModelAni*
{
if (aniIdLoop <= endLoop)
{
var int zCModelAniPtr;
zCModelAniPtr = zCModel_GetAniFromAniID(hero, aniIdLoop);
// check if ani is active
if (zCModelAniPtr > 0 && zCModel_IsAniActive(hero, zCModelAniPtr))
{
return zCModelAniPtr; // found the ani!
} else {
return GetAniID(aniIdLoop + 1, endLoop); // recursive call
};
} else {
return -1; // failed to find current ani (shouldn't happen)
};
};
zCModel_GetProgressPercent() and zCModelAniActive_GetProgressPercent() returning the same float value as int. Don't forget to cast the int values to floats with the scripts in float.d (castFromIntf(floatnumber) ).
@Sektenspinner könntest du noch Zerxes Liste in dein Ikarus Paket mit aufnehmen? Wäre ganz praktisch, da ich erst das halbe Forum durchforsten musste, bis ich loslegen konnte.
Link: https://forum.worldofplayers.de/forum/threads/1023720-Skriptpaket-LeGo/page14?p=17631567#post17631567
mud-freak
27.03.2019, 08:19
@Sektenspinner [...]
Hey Mann, neu hier? https://upload.worldofplayers.de/files11/mud.png
Sektenspinner ist soweit ich weiß lange nicht mehr aktiv. Ich denke eine Verlinkung im Ikarus-Startpost könnte nicht schaden. Direkt in Ikarus mit aufnehmen halte ich aber nicht für sinnvoll.
@Bisasam: Ich kann das nachvollziehen. Es ist ärgerlich, dass dieses Feature möglicherweise in deiner Mod nun nicht mehr so innovativ wirkt. Allerdings auch nur, wenn jemand eine Mod mit dem Feature vor deiner veröffentlicht. Selbst dann denke ich aber, dass deine Mod, die du von Beginn an (oder zumindest seit langem) um dieses Feature herum aufgebaut hast, viel mehr Tiefe bietet als das jede andere Mod könnte, die das Feature jetzt einfach noch irgendwie quer reinschlägt einfach nur "weil es ein cooles Feature" ist.
Das Gefühl, dass "jeder alles besser kann", kannst du deshalb auch in größerem Kontext betrachten. Solange es funktioniert, ist dem Spieler völlig egal, wie ein Feature nun in einer Mod implementiert ist. Was aber nicht egal ist und wo es viel wichtiger ist "gut" drin zu sein, ist es wie man eine Geschichte erzählt und eben solche Features in Quests einbindet.
Grundsätzlich kann ich natürlich den Unmut oder die Frustration über die Geschehnisse verstehen, allerdings möchte ich eben verhindern, dass sich jemand aus diesen Gründen mit der Veröffentlichung von Scripten o.ä. zurückhält, z.B. um dir oder jemand anderem nicht auf die Füße zu treten. Denn das wäre wahrlich ein trauriger Verlust - unsere Community ist bereits jetzt klein genug und die meisten sind mit ihren eigenen Projekten beschäftigt, so dass nur wenige etwas beitragen, von dem wirklich alle Modder profitieren können. Ebenso glücklicher bin ich, dass der ScriptBin hier relativ gut angenommen wurde (und von mir in der nächsten Zeit auch mal aktualisiert wird - versprochen).
Davon abgesehen würde ich gerne an mud-freaks Post verweisen, der das besser in Worte gefasst hat als ich es könnte. Technische Features mögen zwar eine Grundlage für Mods bieten, aber der eigentliche Erfolg - also eine gute Mod - kommt erst durch gutes Storywriting und eine immersive/interaktive Welt (und das weißt du doch eigentlich auch ;)).
Dass es dir als (annähernde?) Allrounderin so vorkommt, als wären 'alle' besser als du ist nicht weiter verwunderlich - ich (und einige andere) haben sich die Freiheit genommen, nur das zu tun, was wir wirklich gut und gerne machen ((Feature-)Scripting in meinem Fall). Wenn ich mich mit dir im Spacern, 3D-Editing oder Story-/Questwriting vergleichen würde, bräuchte es keine 5 Minuten um jedem klarzumachen, dass ich den Kürzeren ziehen werde. Nach diesem Maßstab wärst du also sogar dreimal so gut wie ich ;)
@Sektenspinner könntest du noch Zerxes Liste in dein Ikarus Paket mit aufnehmen? Wäre ganz praktisch, da ich erst das halbe Forum durchforsten musste, bis ich loslegen konnte.
Link: https://forum.worldofplayers.de/forum/threads/1023720-Skriptpaket-LeGo/page14?p=17631567#post17631567
Abgesehen von dem, was mud-freak sagt, findet man den Link dazu auch im LeGo-Wiki (https://lego.worldofplayers.de/) (unter Sonstiges -> EngineFunktionen). Hilft dir jetzt natürlich nicht mehr viel ;)
Hey Mann, neu hier? https://upload.worldofplayers.de/files11/mud.png
Hey, für WoG Verhältnisse wahrscheinlich schon :D Aber Gothic spiele ich schon weitaus länger ;)
Sektenspinner ist soweit ich weiß lange nicht mehr aktiv. Ich denke eine Verlinkung im Ikarus-Startpost könnte nicht schaden. Direkt in Ikarus mit aufnehmen halte ich aber nicht für sinnvoll.
Ah schade, aber ja, so eine Verlinkung würde schon ausreichen. Mein Vorschlag war eher diese Funktionen schon als fertig aufrufbare Scripts zu implementieren und in das Ikarus Paket aufzunehmen. Wenn man dann noch nach und nach dokumentieren würde, was die Funktionen genau machen, würde das denke ich mal immens beim modden helfen.
@Thora: Sehe das im SP Bereich halb so wild. Du hast ja nicht wirklich Konkurrenz hier, ganz im Gegensatz zu den MP Projekten, wo es schnell zum Problem wird wenn du nicht genügend Spieler auf dem Server hast ;)
Ich weiß noch, wie wir damals für CK nächtelang geforscht haben was alles GMP tauglich ist und was nicht. #Wetterwanze :D
Letztendlich war die Lösung für den synchronen Regen einfach nur das Verstellen der Engine-Zeit auf 16:30 Uhr, weil es am ersten Tag genau um diese Uhrzeit immer regnet. Mittlerweile aber total nutzlos, weil es viel bessere Methoden gibt und der GMP auch weiter entwickelt wird. Was ich damit sagen will: man muss immer mit der Zeit gehen und auch erwarten, dass andere dasselbe machen. Also Kopf hoch und weitermachen.
Hey, für WoG Verhältnisse wahrscheinlich schon :D Aber Gothic spiele ich schon weitaus länger ;)
Ah schade, aber ja, so eine Verlinkung würde schon ausreichen. Mein Vorschlag war eher diese Funktionen schon als fertig aufrufbare Scripts zu implementieren und in das Ikarus Paket aufzunehmen. Wenn man dann noch nach und nach dokumentieren würde, was die Funktionen genau machen, würde das denke ich mal immens beim modden helfen.
Das sind mehrere tausend Funktionen, wenn ich mich nicht irre. Da der Daedalus-Compiler keine Optimierungen vornimmt, landen die dann immer auch in der Gothic.dat. Ich würde schätzen, dass das die Anzahl der Symbole um 5-10x erhöhen würde. Theoretisch ist das nicht schlimm (außer der Codestack hat eine maximale Größe?), aber praktisch sollte man das vermutlich lieber vermeiden.
F a w k e s
28.03.2019, 00:50
Hello folks,
Fingers crossed that no one will be pissed off this time.
I would like to share code, which will improve your Gothic 1 combat experience. So far I did not encounter any issues with it, I will appreciate every feedback if you see anything. If player is in fighting mode, this will disable 2 things - animations which are played when damage is inflicted and function oCNpc__Interrupt. In case that player was swarmed by NPCs (Molerats are great example) each attack would interrupt player's action. Which was incredibly annoying and frustrating.
@Lehona - big thank you, I have used your code (original post below) as a starting point, and adapted it for G1:
https://forum.worldofplayers.de/forum/threads/1408430-Skriptpaket-LeGo-3/page9?p=24744467&viewfull=1#post24744467
Required:
class definition for oSDamageDescriptor (from Lehona):
https://forum.worldofplayers.de/forum/threads/1149697-Script-Eigene-Schadensberechnung?p=19002341&viewfull=1#post19002341
/***
Gothic 1
Function will disable/enable oCNpc__Interrupt
***/
FUNC VOID NPC_Interrupt_SetEnabled (var int enabled)
{
//00692830 .text Debug data ?Interrupt@oCNpc@@QAEXH@Z
const int oCNpc__Interrupt = 6891568;
MemoryProtectionOverride (oCNpc__Interrupt, 3);
//RAM hex values @ h 00692830
//83 EC 10 53 56 8B F1 8B 9E B4 09 00 00 ...
if (enabled) {
//Restore original values
MEM_WriteByte (oCNpc__Interrupt, 131); //83
MEM_WriteByte (oCNpc__Interrupt + 1, 236); //EC
MEM_WriteByte (oCNpc__Interrupt + 2, 16); //10
} else {
//Taken from LeGo 2.5.1 ReplaceEngineFuncI -> write return instruction at the beginning of a function
MEM_WriteByte (oCNpc__Interrupt, 194);
MEM_WriteByte (oCNpc__Interrupt + 1, 4);
MEM_WriteByte (oCNpc__Interrupt + 2, 0);
};
};
/***
Gothic 1
Hook below will disable animation & function oCNpc__Interrupt - both would interrupt player while fighting.
***/
FUNC VOID _HOOK_DMG_ONDMG_ANIMATION ()
{
var C_NPC slf;
slf = _^ (ECX);
var oSDamageDescriptor dmgDescriptor;
dmgDescriptor = _^ (MEM_ReadInt (ESP + 4));
//Enable by default (seems like oCNpc__Interrupt is called right after oCNpc__OnDamage_Anim)
NPC_Interrupt_SetEnabled (TRUE);
//If damage was inflicted by Barrier (no attackerNpc) player was not thrown away
if (!dmgDescriptor.attackerNpc) { return; };
if (C_NPC_IsPlayer (slf))
{
//If player is not in fight mode, we can apply animations and interruption
if (!NPC_IsInFightMode (slf, FMODE_FIST))
&& (!NPC_IsInFightMode (slf, FMODE_MELEE))
&& (!NPC_IsInFightMode (slf, FMODE_FAR))
&& (!NPC_IsInFightMode (slf, FMODE_MAGIC)) {
return;
};
//We need to find out damageType
var int damageType; damageType = 0;
var C_NPC oth;
oth = _^ (dmgDescriptor.attackerNpc);
//Spell
if (dmgDescriptor.spellID != 0)
&& (dmgDescriptor.spellID != -1) {
//Seems like damageType = dmgDescriptor.spellLevel
damageType = dmgDescriptor.spellLevel;
};
var C_Item weapon;
//Weapon
if (dmgDescriptor.itemWeapon != 0) {
weapon = _^ (dmgDescriptor.itemWeapon);
//Ranged weapon has spellID == -1
//In case of ranged weapon dmgDescriptor.itemWeapon returns amunition
//So we have to check either Readied weapon or Equipped weapon (fingers crossed NPC didn't switch these that fast)
if (dmgDescriptor.spellID == -1) {
if (NPC_IsInFightMode(oth, FMODE_FAR)) {
weapon = NPC_GetReadiedWeapon (oth);
} else if (NPC_HasEquippedRangedWeapon (oth)) {
weapon = NPC_GetEquippedRangedWeapon (oth);
};
};
//Get weapon damageType
damageType = weapon.damageType;
//Fist mode - get NPC damageType
} else {
damageType = oth.damageType;
};
//If damage was inflicted by Troll (DAM_FLY) player was not thrown away - so don't do anything here
if (damageType & DAM_FLY) { return; };
if (damageType != 0) {
//EAX = 0 will disable animation T_STUMBLE / T_STUMBLEB / T_GOTHIT / (maybe more animations ?)
EAX = 0;
//Disable interruption for player
NPC_Interrupt_SetEnabled (FALSE);
};
};
};
Hook it with:
//I am sure this address is from Lehona as well :)
HookEngine (7609592, 9, "_HOOK_DMG_ONDMG_ANIMATION");
Many thanks for ur efforts. Looks like not many G1 bugs left.
I heard about some problem with ice-wave spell (not tested!). Also haste potions needs to be fixed after save/load game.
F a w k e s
28.03.2019, 11:00
Many thanks for ur efforts Fawkes. Looks like not many G1 bugs left.
I heard about some problem with ice-wave spell. Also haste potions needs to be fixed after save/load game.
Hi pawbuj,
You can fix these spells: SPL_ICECUBE, SPL_ICEWAVE, SPL_FEAR (maybe more of them, I noticed these were trouble-makers) within hook for damage calculation. When these spells will hit NPC, their respective B_AssessMagic* functions will be called -> you can remove their B_AssessMagic* functions from func void B_AssessMagic ().
If you don't have such hook, you can even use this one from my previous post _HOOK_DMG_ONDMG_ANIMATION:
FUNC VOID _HOOK_DMG_ONDMG_ANIMATION ()
{
var C_NPC slf;
slf = _^ (ECX);
var oSDamageDescriptor dmgDescriptor;
dmgDescriptor = _^ (MEM_ReadInt (ESP + 4));
//Enable by default (seems like oCNpc__Interrupt is called right after oCNpc__OnDamage_Anim)
NPC_Interrupt_SetEnabled (TRUE);
//If damage was inflicted by Barrier (no attackerNpc) player was not thrown away
if (!dmgDescriptor.attackerNpc) { return; };
var C_NPC oth;
oth = _^ (dmgDescriptor.attackerNpc);
//--- fix for spells SPL_ICECUBE, SPL_ICEWAVE, SPL_FEAR
if (dmgDescriptor.spellID != 0)
&& (dmgDescriptor.spellID != -1) {
var C_NPC self_backup;
var C_NPC other_backup;
//--- Backup self & other
self_backup = Hlp_GetNPC (self);
other_backup = Hlp_GetNPC (other);
/***
Call B_AssessMagic functions
self = victim & other = attacker within B_AssessMagic
***/
self = Hlp_GetNPC (slf);
other = Hlp_GetNPC (oth);
if (dmgDescriptor.spellID == SPL_ICECUBE)
|| (dmgDescriptor.spellID == SPL_ICEWAVE) {
MEM_Call (B_AssessMagic_IceCube);
};
if (dmgDescriptor.spellID == SPL_FEAR) {
MEM_Call (B_AssessMagic_Fear);
};
//Restore self & other
self = Hlp_GetNPC (self_backup);
other = Hlp_GetNPC (other_backup);
};
//---
if (C_NPC_IsPlayer (slf))
{
//If player is not in fight mode, we can apply animations and interruption
if (!NPC_IsInFightMode (slf, FMODE_FIST))
&& (!NPC_IsInFightMode (slf, FMODE_MELEE))
&& (!NPC_IsInFightMode (slf, FMODE_FAR))
&& (!NPC_IsInFightMode (slf, FMODE_MAGIC)) {
return;
};
//We need to find out damageType
var int damageType; damageType = 0;
//Spell
if (dmgDescriptor.spellID != 0)
&& (dmgDescriptor.spellID != -1) {
//Seems like damageType = dmgDescriptor.spellLevel
damageType = dmgDescriptor.spellLevel;
};
var C_Item weapon;
//Weapon
if (dmgDescriptor.itemWeapon != 0) {
weapon = _^ (dmgDescriptor.itemWeapon);
//Ranged weapon has spellID == -1
//In case of ranged weapon dmgDescriptor.itemWeapon returns amunition
//So we have to check either Readied weapon or Equipped weapon (fingers crossed NPC didn't switch these that fast)
if (dmgDescriptor.spellID == -1) {
if (NPC_IsInFightMode(oth, FMODE_FAR)) {
weapon = NPC_GetReadiedWeapon (oth);
} else if (NPC_HasEquippedRangedWeapon (oth)) {
weapon = NPC_GetEquippedRangedWeapon (oth);
};
};
//Get weapon damageType
damageType = weapon.damageType;
//Fist mode - get NPC damageType
} else {
damageType = oth.damageType;
};
//If damage was inflicted by Troll (DAM_FLY) player was not thrown away - so don't do anything here
if (damageType & DAM_FLY) { return; };
if (damageType != 0) {
//EAX = 0 will disable animation T_STUMBLE / T_STUMBLEB / T_GOTHIT / (maybe more animations ?)
EAX = 0;
//Disable interruption for player
NPC_Interrupt_SetEnabled (FALSE);
};
};
};
F a w k e s
31.03.2019, 13:52
Hi mud-freak,
Hope you can help me - I am trying to pinpoint an NPC to its waypoint (to get same result as with AI_AlignToWP). I was trying functions GetTrafoFromWP & AlignVobAt from your InsertAnything.d package, but I failed to get it working. Can you advise what am I doing wrong here ? :gratz
var zCVob vobSelf;
vobSelf = Hlp_GetNPC (self);
var int Y; Y = vobSelf.trafoObjToWorld [07];
var int vobPtr;
vobPtr = _@ (vobSelf);
var int trafoPtr;
GetTrafoFromWP (self.WP, trafoPtr);
AlignVobAt (vobPtr, trafoPtr);
vobSelf.trafoObjToWorld [07] = Y;
mud-freak
01.04.2019, 12:22
Hi mud-freak,
Hope you can help me - I am trying to pinpoint an NPC to its waypoint (to get same result as with AI_AlignToWP). I was trying functions GetTrafoFromWP & AlignVobAt from your InsertAnything.d package, but I failed to get it working. Can you advise what am I doing wrong here ? :gratz
I think the problem is that trafoPtr has to be allocated appropriately. Either with MEM_Alloc and MEM_Free, or - in my opinion - easier with
var int trafo[16];
...
_@(trafo)
F a w k e s
01.04.2019, 13:51
thank you - it works now! :)
QuickSlot :D:D:D
Download files from my GDrive (https://drive.google.com/open?id=15nVCEHD5OEcVv483VOjX7Emx_3bIVnDQ)
Parse files after Startup.D
In Init_Global add this line:
QuickSlot_Init();
Features:
* Full QuickSlot source code for Gothic 2
* Powered by Ikarus, LeGo, and System Pack.
* You can assign items for every key
* Highlight items in inventory if this is assign to QS key
* Full Config file
* And more...
You must add this line to SystemPack patch file (MACHINE CODE)
(It's fix for climbing)
[FIX_CLIMBING_1]
Addr = "0x0050C585"
Type = "hex"
New = "68 C8 00 00 00 EB 06 90 90 90 90 EB F3"
[FIX_CLIMBING_2]
Addr = "0x0050C586"
Type = "int"
New = "400"
And it's all :D
Big thanks for:
* @mud-freak
* @Lehona
* @Gratt
* @Boguś
If you planing use this code, add me as author :D
Regards Siemekk!
Ingame (Screen from version v0.5.0, you can change texture in config file - with position, maybe I will made tutorial about that):
https://i.imgur.com/VO1LuJe.png
Siemekk
Great improvement after this version: https://forum.worldofplayers.de/forum/threads/1500134-Gothic-II-QuickBar
I try new version and get crashes, after try use weapon from slot, that contain more than one instance. On old version thats crashes absents.
Some zSpy Log
02:27 Info: 10 U: (oCWorld::RemoveVob) ITMW_1H_DAEMONBLADE_01 (1) .... <oWorld.cpp,#475>
02:27 Info: 10 U: (oCWorld::CreateVob) 18788 .... <oWorld.cpp,#315>
02:27 Info: 10 U: (oCWorld::RemoveVob) ITMW_1H_MACE_WAR_04 (1) .... <oWorld.cpp,#475>
02:27 Warn: 0 D: zModel.cpp(zCModel::StartAni): Ani not found: CALL I 835126688 13537 .... <zModel.cpp,#2581>
02:27 Info: 9 U: Mark Conversationmessage for deletion .... <oNpc.cpp,#10781>
02:27 Fault: 0 Q: [start of stacktrace]
02:27 Fault: 0 Q: ASMINT_CALLMYEXTERNAL() + 5 bytes
02:27 Fault: 0 Q: [end of stacktrace]
02:27 Fault: 0 Q: Exception handler was invoked. Ikarus tried to print a Daedalus-Stacktrace to zSpy. Gothic will now crash and probably give you a stacktrace of its own.
02:27 Info: 5 X: Vid_SetScreenMode: No changes ... .... <zRndD3D_Vid.cpp,#559>
02:27 Info: 5 X: Vid_SetScreenMode: No changes ... .... <zRndD3D_Vid.cpp,#559>
02:27 Info: 8 C: WM_CANCELMODE received .... <zWin32.cpp,#1728>
02:27 Info: 8 C: WM_ACTIVATE received .... <zWin32.cpp,#1720>
02:32 Info: 8 C: WM_ACTIVATE received .... <zWin32.cpp,#1720>
48279
that contain more than one instance
Example... I, and in 3 other testers don't had any bugs.
Example... I, and in 3 other testers don't had any bugs.
Well, it probably conflicts with my other scripts... I will try to look for the cause.
Fisk2033
25.07.2019, 07:08
QuickSlot :D:D:D
Download files from my GDrive (https://drive.google.com/open?id=15nVCEHD5OEcVv483VOjX7Emx_3bIVnDQ)
Parse files after Startup.D
In Init_Global add this line:
QuickSlot_Init();
Features:
* Full QuickSlot source code for Gothic 2
* Powered by Ikarus, LeGo, and System Pack.
* You can assign items for every key
* Highlight items in inventory if this is assign to QS key
* Full Config file
* And more...
You must add this line to SystemPack patch file (MACHINE CODE)
(It's fix for climbing)
[FIX_CLIMBING_1]
Addr = "0x0050C585"
Type = "hex"
New = "68 C8 00 00 00 EB 06 90 90 90 90 EB F3"
[FIX_CLIMBING_2]
Addr = "0x0050C586"
Type = "int"
New = "400"
And it's all :D
Big thanks for:
* @mud-freak
* @Lehona
* @Gratt
* @Boguś
If you planing use this code, add me as author :D
Regards Siemekk!
Ingame (Screen from version v0.5.0, you can change texture in config file - with position, maybe I will made tutorial about that):
https://i.imgur.com/VO1LuJe.png
Amazing! Thanks for sharing. I was using your old script for my mod but now I'll switch to this. :)
QuickSlot :D:D:D
What LeGo flags you inicialize in Init_Global?
Please write you string.
What LeGo flags you inicialize in Init_Global?
Please write you string.
I used:
LeGo_Init(LeGo_ALL);
But you can use this:
LeGo_Init(YOU_LEGO_FLAGS & QS_LeGo_Flags);
Siemekk
In a separate directory installed G2NotR, then Clean Scripts from G2MDK, then Ikarus, LeGo and QuickSlots, and I get the same crash when using more than one weapon in the slot.
48282
Mystic! :D
@Fisk - It's work?
@ukur - Try this:
Fresh Gothic 2 NoTR -> Fix 2.6 -> PlayerKit -> SystemPack 1.7 -> Ikarus 1.2.1 -> LeGo -> QuickBar.
@ukur - Try this:
Fresh Gothic 2 NoTR -> Fix 2.6 -> PlayerKit -> SystemPack 1.7 -> Ikarus 1.2.1 -> LeGo -> QuickBar.
I tried this way on clean G2NoTR and also my Mod, I use LeGo v2.6, tried to compile it with a game through GothicStarter_Mod.exe and through Spacer. Crash with multiple instances weapon in one slot and other weapon same type present in all cases when i try switch weapon to use. For Example in first slot hang some clubs, in second hang one sword, then switch between them and get crash. Same crash get with ranged weapon if in one slot hangs some bows, in other slot one crossbow, when switching also get crash.
Try use LeGo 2.5.1 ;)
I try with LeGo 2.5.1 on Clean G2NoTR - Same Crash.
Hätte jemand zufällig für diese Funktionen: https://forum.worldofplayers.de/forum/threads/969446-Skriptpaket-Ikarus-3?p=16944337&viewfull=1#post16944337 die G1 Adressen? Die sind nämlich ziemlich praktisch. Hatte vor, das zu benutzen um eine herrlich variable und flexibel einsetzbare visuelle Übergabe von Items zu ermöglichen.
Das Skript für diese Spielerei wollte ich dann auch hier reinstellen.
F a w k e s
23.08.2019, 09:37
Hätte jemand zufällig für diese Funktionen: https://forum.worldofplayers.de/forum/threads/969446-Skriptpaket-Ikarus-3?p=16944337&viewfull=1#post16944337 die G1 Adressen? Die sind nämlich ziemlich praktisch. Hatte vor, das zu benutzen um eine herrlich variable und flexibel einsetzbare visuelle Übergabe von Items zu ermöglichen.
Das Skript für diese Spielerei wollte ich dann auch hier reinstellen.
Didn't tested this - but it should be working like this for G1:
/*
Neither address nor function in LeGo 2.5.1 EngineAdr_G1.d are correct.
Function below was changed as per advice from MudFreak.
*/
func void oCNpc_RemoveFromSlot (var c_npc slf, var string SlotName, var int retVal, var int SlotID) {
//006A5E80 .text Debug data ?RemoveFromSlot@oCNpc@@QAEPAVoCVob@@ABVzSTRING@@H@Z
const int oCNPC__RemoveFromSlot_G1 = 6971008;
//0x0074A270 public: class oCVob * __thiscall oCNpc::RemoveFromSlot(class zSTRING const &,int,int)
const int oCNPC__RemoveFromSlot_G2 = 7643760;
var int oCNPC__RemoveFromSlot;
oCNPC__RemoveFromSlot = MEMINT_SwitchG1G2 (oCNPC__RemoveFromSlot_G1, oCNPC__RemoveFromSlot_G2);
CALL_IntParam (SlotID);
if (GOTHIC_BASE_VERSION == 1) {
CALL_PutRetValTo (_@(retVal)); // Gothic 1: Return value
} else {
CALL_IntParam (retVal); // Gothic 2: Parameter
};
CALL_zStringPtrParam (SlotName);
CALL__thiscall (MEM_InstToPtr (slf), oCNPC__RemoveFromSlot);
};
/*
This is working and available in EngineAdr_G1.d
Will put it here just for the sake of code completion.
*/
func int oCNpc_PutInSlot (var c_npc slf, var string SlotName, var int oCVobPtr, var int SlotID) {
//006A5940 .text Debug data ?PutInSlot@oCNpc@@QAEXABVzSTRING@@PAVoCVob@@H@Z
const int oCNpc__PutInSlot_G1 = 6969664;
//0x00749CB0 public: void __thiscall oCNpc::PutInSlot(class zSTRING const &,class oCVob *,int)
const int oCNpc__PutInSlot_G2 = 7642288;
var int oCNpc__PutInSlot;
oCNpc__PutInSlot = MEMINT_SwitchG1G2 (oCNpc__PutInSlot_G1, oCNpc__PutInSlot_G2);
CALL_IntParam (SlotID);
CALL_PtrParam (oCVobPtr);
CALL_zStringPtrParam (SlotName);
CALL__thiscall (MEM_InstToPtr (slf), oCNpc__PutInSlot);
return CALL_RetValAsInt ();
};
//************************************************
// Waffen die anderes visual haben, wenn gezogen.
//************************************************
func void HandleDynamicWeaponVisual(var int itemEquipped, var int itemReadied) {
var oCItem weap;
if (Npc_HasReadiedMeleeWeapon(hero) || Npc_HasReadiedRangedWeapon(hero)) {
weap = Npc_GetReadiedWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemEquipped) {
//remove from slot
var int killEffect; killEffect = TRUE;
var int dontDrop; dontDrop = FALSE;
oCNpc_RemoveFromSlot (hero, "ZS_RIGHTHAND", dontDrop, killEffect);
//exchange items
Npc_RemoveInvItem(hero, itemEquipped);
CreateInvItem(hero, itemReadied);
//put in Slot
var int isInInventory; isInInventory = TRUE;
if (weap.flags & ITEM_BOW) {
//bogen links!
oCNpc_PutInSlot (hero, "ZS_LEFTHAND", MEM_InstToPtr(item), isInInventory);
} else {
oCNpc_PutInSlot (hero, "ZS_RIGHTHAND", MEM_InstToPtr(item), isInInventory);
};
};
} else {
var int doIt; doIt = false;
weap = Npc_GetEquippedMeleeWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemReadied) { doIt = true; };
weap = Npc_GetEquippedRangedWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemReadied) { doIt = true; };
if (doIt) {
CreateInvItem(hero, itemEquipped);
EquipWeapon(hero, itemEquipped);
Npc_RemoveInvItem(hero, itemReadied);
};
};
};
Ein Beitrag für den Skriptbin. Hoffe das ist für Fawkes okay, da es seine GetAni-Funktion als Kern hat. Habe entsprechende Skriptstelle markiert:
Soo das ist die Animationsabfragefunktion:
///// Npc_GetAniName ist von FAWKES /////
FUNC STRING NPC_GetAniName (var int slfInstance)
{
var oCNPC slf;
slf = Hlp_GetNPC (slfInstance);
var string str_Ani;
str_Ani = oCAniCtrl__GetCurrentAniName (slf.AniCtrl);
return str_Ani;
};
///// Ab hier Bisasam /////
func string BuildAniString(var string Beginning,var string Middle, var string End)
{
var string ReturnString;
ReturnString=Concatstrings(Beginning,Middle);
ReturnString=Concatstrings(ReturnString,End);
return ReturnString;
};
func int Npc_IsInAnimation(var int NscInst, var string CurrAni, var int IgnoreState)
{
var string HerosAniName;
HerosAniName=NPC_GetAniName(NscInst);
var string ConcAni;
////////////////////////////////
// FALLS MAN IN DER ABFRAGE SCHON SO GENAU WAR DASS ES SOFORT PASST
if Hlp_StrCmp(HerosAniName,CurrAni)==TRUE
{
return TRUE;
};
//////////////////////////////////////////
// STATE und TRANSITION, nicht für Bewegungen wie Waffenlauf, Schleichen etc. geeignet!
// Für obige Spezialfälle bitte schon bei Varübergabe konkret werden!
// Es wird deswegen nicht mit || gearbeitet weil Gothic 1 ein Problem mit zu vielen
// Verschachtelungen kriegt wenn ein und dieselbe Funktion oft aufgerufen wird
if IgnoreState==FALSE //Sollen S_X_S1 etc. Funktionen ignoriert werden?
{
ConcAni=BuildAniString("S_",CurrAni,"_S0");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("S_",CurrAni,"_S1");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("S_",CurrAni,"_S2");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("S_",CurrAni,"_S3");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("S_",CurrAni,"_S4");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
};
/////////////////////////////////////////////////
// State Trans
ConcAni=BuildAniString("T_",CurrAni,"_S0_2_S1");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S1_2_S0");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S1_2_S2");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S2_2_S1");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S2_2_S3");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S3_2_S2");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S3_2_S4");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_S4_2_S3");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
//////////////////////////////////////////
// Zurück zu stand
ConcAni=BuildAniString("T_",CurrAni,"_S0_2_STAND");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_STAND_2_S0");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
//////////////////////////////////////
// SPEZIALFALL T_STAND_2_SIT u.ä.
//
ConcAni=BuildAniString("T_STAND_2_",CurrAni,"");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("T_",CurrAni,"_2_STAND");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
if IgnoreState==FALSE
{
ConcAni=BuildAniString("S_",CurrAni,"");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
};
//////////////////////////////////////////////////
// RANDOM ANIS BENCH, CHAIR, ETC.
ConcAni=BuildAniString("R_",CurrAni,"_RANDOM_1");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("R_",CurrAni,"_RANDOM_2");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("R_",CurrAni,"_RANDOM_3");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
ConcAni=BuildAniString("R_",CurrAni,"_RANDOM_4");
if Hlp_StrCmp(HerosAniName,ConcAni)==TRUE
{
return TRUE;
};
/////////////////////////////
// Passt alles nicht?
return FALSE;
};
func int Npc_IsInTransitionAni(var int NscInst)
{
// INSTANZ , ANINAME , S_Anis Ignorieren?
if Npc_IsInAnimation(NscInst,"SLEEPGROUND",TRUE)==TRUE
|| Npc_IsInAnimation(NscInst,"WASH",TRUE)==TRUE
|| Npc_IsInAnimation(NscInst,"SIT",TRUE)==TRUE
|| Npc_IsInAnimation(NscInst,"LGUARD",TRUE)==TRUE
|| Npc_IsInAnimation(NscInst,"HGUARD",TRUE)==TRUE
{
return TRUE;
};
return FALSE;
};
Die Abfrage nimmt eine Instanz entgegen, ermittelt deren aktuelle Animation und gleicht ab, ob sie mit der gesuchten Animation übereinstimmt.
var int IgnoreState ist für S-Animationen zuständig. Also z.B. S_SIT, S_BATHTUB_S1 etc. Wenn ihr dort TRUE reinschreibt, wird die Überprüfung auf die genannten Animationen NICHT stattfinden. Das ist vor allem dann wichtig, wenn ihr nur die T- oder R-Animationen überprüfen möchtet. Ich benutze das um rauszukriegen, ob sich mein Held gerade in einer Übergangsanimation befindet. Dann sollen bestimmte Dinge nicht möglich sein um Bugs auszumerzen.
Wenn ihr FALSE schreibt, wird die Prüfung auf S_BATHTUB_S1 stattfinden.
Für Kampf- und Bewegungsanimationen konnte ich kein Standardschema erstellen da diese immer unterschiedlich sind. Aus diesem Grund solltet ihr diese Fälle konkret eingeben statt nur den Mittelteil. Statt "RUN_2_WALK" also lieber "T_RUN_2_WALK"
Orc Hunter UA
16.02.2020, 17:15
Siemekk
In a separate directory installed G2NotR, then Clean Scripts from G2MDK, then Ikarus, LeGo and QuickSlots, and I get the same crash when using more than one weapon in the slot.
48282
Mystic! :D
I have the same problem
F a w k e s
05.05.2020, 15:20
Hello folks,
I have couple of useful functions. These can be used to improve vanilla G1 scripts. (might be these work with G2A as well)
NPC_HasOverlay
Usage: In G1 if NPC has active overlay 'HUMANS_DRUNKEN.MDS' and goes into attack - whole game freezes.
If you use function Mdl_RemoveOverlayMDS (self, "HUMANS_DRUNKEN.MDS"); to remove this overlay while it was not previously applied - it might screw up some AI behavior of NPC (in my case any NPC which was in fightmode FMODE_FAR froze).
//returns true if NPC has an overlay, otherwise false
FUNC INT NPC_HasOverlay (var int slfInstance, var string testOverlay)
{
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (Hlp_IsValidNPC (slf)) {
var int i; i = 0;
var int loop; loop = MEM_StackPos.position;
if (i < slf.activeOverlays_numInArray)
{
var string overlay; overlay = MEM_ReadStringArray (slf.activeOverlays_array, i);
if (Hlp_StrCmp (overlay, testOverlay)) {
return TRUE;
};
i += 1;
if (i < slf.activeOverlays_numInArray) {
MEM_StackPos.position = loop;
};
};
};
return FALSE;
};
...
FUNC VOID ZS_Attack ()
{
...
if (NPC_HasOverlay (self, "HUMANS_DRUNKEN.MDS")) {
Mdl_RemoveOverlayMDS (self, "HUMANS_DRUNKEN.MDS");
};
};
NPC_IsInActiveVobList
Usage: If you call NPC_ClearAIQueue on NPC which is not in activeVobList_array - game will crash:
//Code taken from Broadcasts.d
//https://forum.worldofplayers.de/forum/threads/775333-Script-Broadcasts?p=17232705&viewfull=1#post17232705
//returns true if NPC is in activeVobList_array, otherwise false
FUNC INT NPC_IsInActiveVobList (var int slfInstance)
{
var C_NPC slf; slf = Hlp_GetNPC (slfInstance);
if (Hlp_IsValidNPC (slf)) {
var int i; i = 0;
var int loop; loop = MEM_StackPos.position;
if (i < MEM_World.activeVobList_numInArray) {
var int ptr;
ptr = MEM_ReadIntArray(MEM_World.activeVobList_array, i);
if (Hlp_Is_oCNpc (ptr)) {
var C_NPC npc; npc = MEM_PtrToInst (ptr);
if (Hlp_GetInstanceID (npc) == Hlp_GetInstanceID (slf))
{
return TRUE;
};
};
i += 1;
if (i < MEM_World.activeVobList_numInArray) {
MEM_StackPos.position = loop;
};
};
};
return FALSE;
};
...
FUNC VOID B_FullStop (var C_NPC npc)
{
if (NPC_IsInActiveVobList (npc)) {
Npc_ClearAIQueue (npc);
AI_StandupQuick (npc);
};
};
NPC_GetAnglesVob, NPC_IsInAngleXNPC, NPC_IsInAngleYNPC
Usage: Many ZS functions which call AI_TurnToNPC can be improved. For example ZS_Smalltalk: 2 NPCs talking with each other will turn to each other every loop.
AI_TurnToNPC will cause something like 'shivering' effect on NPC. So by checking angle, you can avoid unnecessary turn animation.
//NPC_GetAnglesVob (self, vobPtr, angleXPtr, angleYPtr)
//returns angle of self in relation to vobPtr, returned values are written to angleXPtr & angleYPtr
//angleX: Negative number vob is on lefthand-side
// 0 vob is directly in front of npc
// Positive number vob is on righthand-side
//angleY: Negative number vob is below npc
// 0 vob is directly in front of NPC
// Positive number vob is above npc
FUNC VOID NPC_GetAnglesVob (var int slfInstance, var int vobPtr, var int angleXPtr, var int angleYPtr)
{
//0074C0D0 .text Debug data ?GetAngles@oCNPC@@QAEXPAVzCVob@@AAM1@Z
const int oCNPC__GetAnglesVob_G1 = 7651536;
//0x00681680 public: void __thiscall oCNpc::GetAngles(class zCVob *,float &,float &)
const int oCNPC__GetAnglesVob_G2 = 6821504;
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!vobPtr) { return; };
if (Hlp_IsValidNPC (slf)) {
CALL_PtrParam (angleYPtr);
CALL_PtrParam (angleXPtr);
CALL_PtrParam (vobPtr);
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNPC__GetAnglesVob_G1, oCNPC__GetAnglesVob_G2));
};
};
//NPC_IsInAngleXNPC (self, other, angle)
//returns true if self is looking at other within 'angle' on X axis
FUNC INT NPC_IsInAngleXNPC (var int slfInstance, var int npcInstance, var int angle)
{
var oCNPC slf;
var oCNPC npc;
slf = Hlp_GetNPC (slfInstance);
npc = Hlp_GetNPC (npcInstance);
if (Hlp_IsValidNPC (slf))
&& (Hlp_IsValidNPC (npc))
{
var int angleX;
var int angleY;
NPC_GetAnglesVob (slfInstance, _@ (npc), _@ (angleX), _@ (angleY));
if (gef (angleX, negf (mkf (angle))))
&& (lef (angleX, mkf (angle)))
{
return TRUE;
};
};
return FALSE;
};
//NPC_IsInAngleYNPC (self, other, angle)
//returns true if self is looking at other within 'angle' on Y axis
FUNC INT NPC_IsInAngleYNPC (var int slfInstance, var int npcInstance, var int angle)
{
var oCNPC slf;
var oCNPC npc;
slf = Hlp_GetNPC (slfInstance);
npc = Hlp_GetNPC (npcInstance);
if (Hlp_IsValidNPC (slf))
&& (Hlp_IsValidNPC (npc))
{
var int angleX;
var int angleY;
NPC_GetAnglesVob (slfInstance, _@ (npc), _@ (angleX), _@ (angleY));
if (gef (angleY, negf (mkf (angle))))
&& (lef (angleY, mkf (angle)))
{
return TRUE;
};
};
return FALSE;
};
...
FUNC VOID ZS_Smalltalk_Loop ()
{
...
if (!NPC_IsInAngleXNPC (self, other, 10)) {
AI_TurnToNPC (self, other);
};
...
};
mud-freak
04.06.2020, 12:25
Hier ist ein einfaches Skript, das die (HP-, Mana-, Schwimm- und Fokus-) Balken anders positionieren lässt. Das Skript greift dabei direkt in die Positionierung der Engine hinein für minimalen Eingriff. Folglich werden die behalten die Balken ihre Eigenschaften bei und folgen auch weiterhin der Auflösung.
setBarPositions.d – This script conveniently repositions the interface bars with minimal invasion.
/*
* setBarPositions.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=26426112
*
* Specify the positions of hp, mana, swim and focus bars.
*
* - Requires Ikarus, LeGo (HookEngine)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Adjust the customizable function to determine the bar positions
* - Initialize from Init_Global with
* SetBarPositions_Init(TRUE/FALSE, TRUE/FALSE);
* where the first and second arguments state whether the mana and swim bars should be always visible
*/
/*
* Customization function to specify the position of the bars in virtual coordinates
*/
func int SetBarPosition(var int barPtr) {
var oCViewStatusBar bar; bar = _^(barPtr);
var int x; var int y;
if (barPtr == MEM_Game.hpBar) {
// Original
x = Print_ToVirtual(10, PS_X); // 10 px from the left
y = PS_VMax - Print_ToVirtual(10 + bar.zCView_psizey, PS_Y); // 10 px from the bottom
} else if (barPtr == MEM_Game.manaBar) {
// Original
x = PS_VMax - Print_ToVirtual(10 + bar.zCView_psizex, PS_X); // 10 px from the right
y = PS_VMax - Print_ToVirtual(10 + bar.zCView_psizey, PS_Y); // 10 px from the bottom
} else if (barPtr == MEM_Game.swimBar) {
// Original
x = (PS_VMax - bar.zCView_vsizex) / 2; // Centered
y = PS_VMax - Print_ToVirtual(10 + bar.zCView_psizey, PS_Y); // 10 px from the bottom
} else if (barPtr == MEM_Game.focusBar) {
// Original
x = (PS_VMax - bar.zCView_vsizex) / 2; // Centered
y = Print_ToVirtual(10, PS_Y); // 10 px from the top
};
return x | (y << 14);
};
/*
* Internal function
*/
func void SetBarPositions() {
MEM_InitGlobalInst();
const int MASK_X = (PS_VMax << 1) - 1;
const int MASK_Y = ((PS_VMax << 1) - 1) << 14;
var int xy;
xy = SetBarPosition(MEM_Game.hpBar);
ViewPtr_MoveTo(MEM_Game.hpBar, (xy & MASK_X), (xy & MASK_Y) >> 14);
xy = SetBarPosition(MEM_Game.manaBar);
ViewPtr_MoveTo(MEM_Game.manaBar, (xy & MASK_X), (xy & MASK_Y) >> 14);
xy = SetBarPosition(MEM_Game.swimBar);
ViewPtr_MoveTo(MEM_Game.swimBar, (xy & MASK_X), (xy & MASK_Y) >> 14);
xy = SetBarPosition(MEM_Game.focusBar);
ViewPtr_MoveTo(MEM_Game.focusBar, (xy & MASK_X), (xy & MASK_Y) >> 14);
};
/*
* Initialization function
*/
func void SetBarPositions_Init(var int manaAlwaysOn, var int swimAlwaysOn) {
MEM_InitAll();
const int oCGame__UpdateScreenResolution_removeBarViews_G1 = 6524729; //0x638F39
const int oCGame__UpdateScreenResolution_removeBarViews_G2 = 7090409; //0x6C30E9
const int oCGame__UpdatePlayerStatus_manaBarJmp_G1 = 6525351; //0x6391A7
const int oCGame__UpdatePlayerStatus_manaBarJmp_G2 = 7091198; //0x6C33FE
const int oCGame__UpdatePlayerStatus_swimBarJmp_G1 = 6525142; //0x6390D6
const int oCGame__UpdatePlayerStatus_swimBarJmp_G2 = 7090988; //0x6C332C
var int addr;
// Hook positioning
HookEngineF(MEMINT_SwitchG1G2(oCGame__UpdateScreenResolution_removeBarViews_G1,
oCGame__UpdateScreenResolution_removeBarViews_G2), 6, SetBarPositions);
// Always show mana bar
if (manaAlwaysOn) {
addr = MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_manaBarJmp_G1, oCGame__UpdatePlayerStatus_manaBarJmp_G2);
if (MEM_ReadInt(addr) != /*90 90 90 90*/-1869574000) {
MemoryProtectionOverride(addr, 1);
MEM_WriteByte(addr, 0);
};
};
// Always show swim bar
if (swimAlwaysOn) {
addr = MEMINT_SwitchG1G2(oCGame__UpdatePlayerStatus_swimBarJmp_G1, oCGame__UpdatePlayerStatus_swimBarJmp_G2);
MemoryProtectionOverride(addr, 2);
MEM_WriteByte(addr, /*EB*/235); // jmp
MEM_WriteByte(addr+1, /*1C*/ 28); // 0x28
};
};
Zwei Beispiele hier:
Alle Balken dauerhaft auf der linken Seite anzeigen.
https://upload.worldofplayers.de/files12/hp_mana_swim.png
Anpassungen in der Funktion SetBarPosition. Initialisieren mit SetBarPositions_Init(TRUE, TRUE);.
/*
* Customization function to specify the position of the bars in virtual coordinates
*
* EXAMPLE: Stacked on the left (all visible)
* Initialized with
* SetBarPositions_Init(TRUE, TRUE);
*/
func int SetBarPosition(var int barPtr) {
var oCViewStatusBar bar; bar = _^(barPtr);
var int x; var int y;
if (barPtr == MEM_Game.hpBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 3 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.manaBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 2 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.swimBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 1 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.focusBar) {
// Original
x = (PS_VMax - bar.zCView_vsizex) / 2; // Centered
y = Print_ToVirtual(10, PS_Y); // 10 px from the top
};
return x | (y << 14);
};
Alle Balken auf der linken Seite, Schwimmbalken oben und nur unter Wasser sichtbar.
https://upload.worldofplayers.de/files12/hp_mana.png https://upload.worldofplayers.de/files12/swim_hp_mana.png
Anpassungen in der Funktion SetBarPosition. Initialisieren mit SetBarPositions_Init(TRUE, FALSE);.
/*
* Customization function to specify the position of the bars in virtual coordinates
*
* EXAMPLE: Stacked on the left (swim bar non-visible on top)
* Initialized with
* SetBarPositions_Init(TRUE, FALSE);
*/
func int SetBarPosition(var int barPtr) {
var oCViewStatusBar bar; bar = _^(barPtr);
var int x; var int y;
if (barPtr == MEM_Game.hpBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 2 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.manaBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 1 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.swimBar) {
x = Print_ToVirtual(10, PS_X);
y = PS_VMax - Print_ToVirtual(6 + 3 * (4 + bar.zCView_psizey), PS_Y);
} else if (barPtr == MEM_Game.focusBar) {
// Original
x = (PS_VMax - bar.zCView_vsizex) / 2; // Centered
y = Print_ToVirtual(10, PS_Y); // 10 px from the top
};
return x | (y << 14);
};
PS: Beim Schreiben ist mir auch aufgefallen, dass das overrideBars Skript einen Absturz beim Weltenwechsel herbei geführt hat. Das habe ich im Skript des entsprechenden Posts verbessert. Außerdem habe ich gemerkt, dass das Skript etwas umständlich ist. Dort sollte eigentlich ein einzelner Eingriff genügen, um die Textur auszutauschen. Das fortlaufende Austauschen der Texture sollte nicht nötig sein.
Since the recent update of Union 1.0 h, a constant in gothic.ini in the INTERNAL section has been added for modders
The constant is called UnionActivated, and it will take the values 1 and 0, whether Union is running or not.
This way you can avoid duplicating all fixes that have been written for a long time, because they also exist in the Union package.
For greater compatibility, I did the following in mods:
CheckUnion.d
//Starting from version 1.0h in gothic.ini there will be a setting for modders,
//with which you can make a menu for plugins, check the presence of Union, and more.
func void CheckUnion() {
//check for a variable UnionActivated in the section INTERNAL in gothic.ini
if(!MEM_GothOptExists("INTERNAL", "UnionActivated")) {
//Print to zspy
MEM_Info("Union Disabled, fixed enabled");
//Your fixes
UnequipItemFix();
ArmorFixSP();
MoreAlphaVobs(2048); //normal: 256
MoreAlphaPolys(16384); //normal: 2048
FixPlayerControllsTurnOnNpcAttackMoveBug();
//***more
} else {
//Print to zspy
MEM_Info("Union Activated, fixed disable");
};
};
Call the CheckUnion function in INIT_GLOBAL
Maybe it will be useful to someone.
In principle, you can shorten the code and remove the second part from else, but I use it for more information.
There is a flaw in this method. The variable can be manually registered in gothic. ini, thereby creating a conflict of fixes.
I still have only an idea in my head, to check the value of a variable and assign it inside the mod to another variable and already from its value-to call fix functions or not.
If you have better ideas or an example, I would be grateful.
mud-freak
20.07.2020, 00:39
Bloodfly91 hat in dem gameKeyEvents-Skript einen Fehler entdeckt, bei dem auf bestimmte Tastendrücke im Spiel nicht länger reagiert wurde. Ich habe das Skript in dem verlinkten Post aktualisiert (es ist dadurch noch kürzer und simpler geworden).
Diese Funktion gibt die Entfernung eines NPC zu einem Vob in einer Ganzzahl zurück, genau wie z.B. Npc_GetDistToNpc.
Nur das man mit dieser Funktion nicht nur auf Items, Npcs o.ä limitiert ist, sondern auf einzelne Vobs runterbrechen kann (https://forum.worldofplayers.de/forum/threads/1560334-Entfernung-NPC-Freepoint-irgendwie-messbar).
Wenn man möchte kann man den Parameter "var c_npc npc" durch ein "var zCVob vonVob" ersetzen (entprechend die verwendungen im Quellcode dann anpassen) und somit die Entfernung zwischen zwei Vobs messen.
/*
* Gibt die Approximation der Entfernung des npc zu dem toVob zurück.
*
* Beispielaufruf:
* var zCVob xard; xard = Hlp_GetNpc(NONE_100_Xardas);
* var int dist; dist = Npc_GetDistToVob(hero, xard);
*/
func int Npc_GetDistToVob(var C_NPC npc, var zCVob toVob) {
// für den Fall das man mehr Genauigkeit möchte, kann man diese Adressen unten im CALL_Thiscall austauschen. Sollte nicht nötig sein!
const int zCVob__GetDistanceToVob = 6404368; // 0x0061B910
const int zCVob__GetDistanceToVobApprox = 6404464; // 0x0061B970
var int npcPtr; npcPtr = _@(npc);
var int toPtr; toPtr = _@(toVob);
const int call = 0;
if (CALL_Begin(call)) {
CALL_RetValIsFloat();
CALL_PtrParam(_@(toPtr));
CALL_PutRetValTo(_@(flt));
CALL__thiscall(_@(npcPtr), zCVob__GetDistanceToVobApprox);
call = CALL_End();
};
var int flt; flt = roundf(+flt);
return +flt;
};
mud-freak
21.07.2020, 10:24
Hier ist ein einfaches Skript, das dem Spiel einen Kompass hinzufügt. In der Vergangenheit, gab es bereits Ansätze, um so etwas umzusetzen (bspw. der von NicoDE über Schriftzeichen). Da mittlerweile das Float-Paket und die Sprites von LeGo zur Verfügung stehen, ist das Skript hier allerdings sehr viel simpler und leichter. Der Kern zur Ausrichtung der Nadel nach Norden ist im Grunde nur eine Codezeile. Zusätzlich wird der Kompass allerdings noch automatisch versteckt (z.B. in Menüs, Dialogen und im Inventar) und automatisch an die Bildschirmauflösung angepasst. Die Größe ist angegeben in Pixeln und lässt sich über eine Konstante anpassen. Ebenso kann der Kompass über eine weitere Konstante aus anderen Skripten heraus verborgen werden.
Der Anstoß und die Grafiken (siehe unten "Ressourcen"), die hier verwendet werden, stammen von Sporthistoriker. Bei Verwendung halte ich eine Erwähnung seines Namens für angemessen.
compass.d – This script adds a lightweight on-screen compass to the game.
/*
* compass.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=26483751
*
* Add an on-screen compass.
*
* - Requires Ikarus, LeGo (FrameFunctions and Sprite)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Initialize from Init_Global with
* Compass_Init();
*/
/* Constants */
const string Compass_Tex_Back = "COMPASS_BACK.TGA";
const string Compass_Tex_Needle = "COMPASS_NEEDLE.TGA";
const int Compass_Size = 150; // Width and height in pixels
const int Compass_Show = 1; // Switch for outside functions to hide it (TRUE/FALSE)
/*
* Initialization function to be called from Init_Global
*/
func void Compass_Init() {
// Requires LeGo FrameFunctions and Sprite
if (_LeGo_Flags & (LeGo_Sprite | LeGo_FrameFunctions)) {
FF_ApplyOnce(Compass);
};
};
/*
* FrameFunction to create, rotate, adjust and draw the compass on the screen
*/
func void Compass() {
// Create sprites once
var int back; var int needle;
if (!Hlp_IsValidHandle(needle)) {
back = Sprite_CreatePxl(0, 0, 250, 250, -1, Compass_Tex_Back);
needle = Sprite_CreatePxl(0, 0, 250, 250, -1, Compass_Tex_Needle);
Sprite_SetPrio(needle, 1); // Keep needle on top
};
// Make robust to changes in screen resolution
var int x; var int y;
Print_GetScreenSize();
if (Print_Screen[PS_X] != x) || (Print_Screen[PS_Y] != y) {
x = Print_Screen[PS_X] - (Compass_Size/2 + 15);
y = Compass_Size/2 + 15; // Padding of extra 15 px in case there is a status bar
Sprite_SetPosPxl(back, x, y);
Sprite_SetPosPxl(needle, x, y);
Sprite_SetDimPxl(back, Compass_Size, Compass_Size);
Sprite_SetDimPxl(needle, Compass_Size, Compass_Size);
Sprite_SetRotationSC(back, FLOATNULL, FLOATONE); // Bug fix: Angles are lost for after loading
x = Print_Screen[PS_X];
y = Print_Screen[PS_Y];
};
// Auto show and hide
var oCNpc her; her = Hlp_GetNpc(hero);
var int on; on = _Bar_PlayerStatus() && (!(her.bitfield[0] & oCNpc_bitfield0_movlock)) && Compass_Show;
Sprite_SetVisible(back, on);
Sprite_SetVisible(needle, on);
// Update orientation
var zCVob vob; vob = Hlp_GetNpc(hero);
Sprite_SetRotationSC(needle, negf(vob.trafoObjToWorld[2]), vob.trafoObjToWorld[10]);
};
Ressourcen (https://upload.worldofplayers.de/files12/compass_resources.zip)
https://upload.worldofplayers.de/files12/compass_preview_thumbnail.png (https://upload.worldofplayers.de/files12/LPlMj33sFUccompass_preview.png)
Auch wenn ich persönlich einen Kompass ohne jegliche storytechnische Einbindung in eine Mod nicht besonders sinnvoll finde, habe ich daraus einen Patch erstellt. Dieser findet sich im Modifikationsforum. Selbstverständlich überprüft der Patch, ob das obige Skript bereits in einer Mod vorhanden ist, um dessen Kompass nicht zu überschreiben. Hier zum Releasethread: [Patch] Compass.
Hier ist ein Hook welcher für das Item im Fokus die "description" anzeigt anstelle des Namens.
Das kennt man doch: "Oh eine Steintafel, was das wohl für eine ist. Ah - 'Steintafel' - toll..."
Mit diesem Hook heißt wird die Steintafel dann auch "Steintafel des Geschicks I" genannt und man weiß ob es sich lohnt ins Inventar zu gehen ;)
Voraussetzungen:
- Ikarus
- LeGo + Flag 'LeGo_HookEngine'
Wie der Hook arbeitet:
Er hookt sich in den UpdatePlayerStatus-Zweig welcher ausgeführt wird, wenn ein Item angeguckt wird und überschreibt die lokale variable welchen den Namen für die Ausgabe beinhaltet mit der "description" des angeschauten Items.
/// Es hookt sich in den `else if (dynamic_cast<oCItem*>(focus))` Zweig
/// der oCGame::UpdatePlayerStatus Methode ein.
/// Direkt nachdem der Text zum darstellen mit (oCItem*)->GetName() zugewiesen wurde.
const int _DisplayFokusItemName_Hook_StackOffset = 196;
const int _DisplayFokusItemName_Hook_LocalVarText = 140;
func void _DisplayFokusItemName_Hook_WasItem() {
// EDI ist hier der Pointer zum zCVob im Fokus
var oCItem itm; itm = _^(EDI);
MEM_WriteString((ESP+_DisplayFokusItemName_Hook_StackOffset)-_DisplayFokusItemName_Hook_LocalVarText, itm.description);
};
/// Sorgt dafür, das die Item-Beschreibungen anstelle der Item-Instanz Namen verwendet werden.
/// Dadurch heißen `Steintafeln` nun z.B. `Steintafel des Geschicks I`
func void DisplayFokusItemName_Init() {
const int once = TRUE;
const int oCGame__UpdatePlayerStatus__ShowFocus_oCItem_G1 = 6526014; // 0x0063943E
const int oCGame__UpdatePlayerStatus__ShowFocus_oCItem_G2 = 7091845; // 0x006C3685
if (once) {
if (MEMINT_SwitchG1G2(1, 0)) {
_DisplayFokusItemName_Hook_StackOffset = 128 /*80h*/;
_DisplayFokusItemName_Hook_LocalVarText = 72;
HookEngineF(oCGame__UpdatePlayerStatus__ShowFocus_oCItem_G1, 5, _DisplayFokusItemName_Hook_WasItem);
} else {
HookEngineF(oCGame__UpdatePlayerStatus__ShowFocus_oCItem_G2, 5, _DisplayFokusItemName_Hook_WasItem);
};
once = FALSE;
};
};
@Kirides is this code for G2 only?
@Kirides is this code for G2 only?
Oh, yes. i only tested Gothic 2 NotR and only looked for the addresses in G2. I could take a look at my Gothic1 IDA profile if i can make if "cross platform"
EDIT: I modified the code a bit, it "could" work with Gothic1 now. But someone would have to test... i've got no time for it today...
Complete remaster of all Gothic 1 and Gothic 2 animations. Before using it, you need to ask the author's permission, for decency's sake. :)
https://worldofplayers.ru/threads/38390/page-17
https://youtu.be/vg7etWZXrzo
Important note! The new anims (Humans) will not be compatible with all the new armor/animations, etc., so if you want to use a new anims (Humans) in the mod, you will have to redo everything yourself.
mud-freak
07.08.2020, 10:12
Complete remaster of all Gothic 1 and Gothic 2 animations. Before using it, you need to ask the author's permission, for decency's sake. :)
https://worldofplayers.ru/threads/38390/page-17
https://youtu.be/vg7etWZXrzo
Nice! Thanks for sharing.
This would probably be better off in its own forum thread. This thread here is for sharing scripts and code.
strings.d – Miscellaneous string functions
/*
* strings.d
*
* Miscellaneous string functions
*
* - Requires Ikarus
* - Compatible with Gothic 1 and Gothic 2
*
*
* func string STR_ReplaceOnce(string haystack, string needle, string replace)
* func string STR_ReplaceAll (string haystack, string needle, string replace)
*
* func string STR_Postfix(string str, int off)
* func string STR_Trim(string str, string tok)
* func string STR_TrimChar(string str, int chr)
*
* func int STR_ToFloat(string str)
*
* func int STR_IndexOfFirstNonNumeric(string str)
*/
/*
* Replace first occurrence of needle in haystack and replace it
*/
func string STR_ReplaceOnce(var string haystack, var string needle, var string replace) {
var zString zSh; zSh = _^(_@s(haystack));
var zString zSn; zSn = _^(_@s(needle));
if (!zSh.len) || (!zSn.len) {
return haystack;
};
var int startPos; startPos = STR_IndexOf(haystack, needle);
if (startPos == -1) {
return haystack;
};
var string destStr; destStr = "";
destStr = STR_Prefix(haystack, startPos);
destStr = ConcatStrings(destStr, replace);
destStr = ConcatStrings(destStr, STR_Substr(haystack, startPos+zSn.len, zSh.len-(startPos+zSn.len)));
return destStr;
};
/*
* Replace all occurrences of needle in haystack and replace them
*/
func string STR_ReplaceAll(var string haystack, var string needle, var string replace) {
var string before; before = "";
while(!Hlp_StrCmp(haystack, before));
before = haystack;
haystack = STR_ReplaceOnce(before, needle, replace);
end;
return haystack;
};
/*
* Complement to STR_Prefix in Ikarus
*/
func string STR_Postfix(var string str, var int off) {
return STR_SubStr(str, off, STR_Len(str)-off);
};
/*
* Retrieve the string position (starting at 0) of the first non-numeric character
*/
func int STR_IndexOfFirstNonNumeric(var string str) {
var int len; len = STR_Len(str);
var int buf; buf = STR_toChar(str);
var int valid; valid = FALSE;
var int index; index = 0;
while(index < len);
var int chr; chr = MEM_ReadInt(buf + index) & 255;
if (chr >= 48 /* 0 */) && (chr <= 57 /* 9 */) {
valid = TRUE;
} else if ((chr != 45 /*-*/) && (chr != 43 /*+*/)) || (index != 0) {
// Allow leading plus/minus, e.g. -5
break;
};
index += 1;
end;
// "-" is not a number
if (!valid) {
index = 0;
};
return index;
};
/*
* Retrieve a string trimmed from left and right by all characters in tok
* E.g. (" .. hello .. ", ". ") -> "hello"
*/
func string STR_Trim(var string str, var string tok) {
var int lenS; lenS = STR_Len(str);
var int lenT; lenT = STR_Len(tok);
// Start from the beginning
var int startP; startP = 0;
while(startP < lenS);
var string ss; ss = STR_Substr(str, startP, 1);
var int cont; cont = FALSE;
var int t; t = 0;
while(t < lenT);
var string ts; ts = STR_Substr(tok, t, 1);
if (Hlp_StrCmp(ss, ts)) {
cont = TRUE;
break;
};
t += 1;
end;
if (!cont) {
break;
};
startP += 1;
end;
// Start from the end
var int endP; endP = lenS-1;
while(endP >= startP);
ss = STR_Substr(str, endP, 1);
cont = FALSE;
t = 0;
while(t < lenT);
ts = STR_Substr(tok, t, 1);
if (Hlp_StrCmp(ss, ts)) {
cont = TRUE;
break;
};
t += 1;
end;
if (!cont) {
break;
};
endP -= 1;
end;
// Convert offset to length (0 -> 1, 1 -> 2, ...)
endP += 1;
if (startP >= endP) {
return "";
} else {
return STR_Substr(str, startP, endP-startP);
};
};
/*
* Trim string from left and right of a certain char
*/
func string STR_TrimChar(var string str, var int chr) {
var int sPtr; sPtr = _@s(str);
const int zSTRING__TrimLeft_G1 = 4617312; //0x467460
const int zSTRING__TrimLeft_G2 = 4638256; //0x46C630
const int call = 0;
if (CALL_Begin(call)) {
CALL_IntParam(_@(chr));
CALL__thiscall(_@(sPtr), MEMINT_SwitchG1G2(zSTRING__TrimLeft_G1, zSTRING__TrimLeft_G2));
call = CALL_End();
};
const int zSTRING__TrimRight_G1 = 4617632; //0x4675A0
const int zSTRING__TrimRight_G2 = 4638576; //0x46C770
const int call1 = 0;
if (CALL_Begin(call1)) {
CALL_IntParam(_@(chr));
CALL__thiscall(_@(sPtr), MEMINT_SwitchG1G2(zSTRING__TrimRight_G1, zSTRING__TrimRight_G2));
call1 = CALL_End();
};
return str;
};
/*
* Convert string to IEEE754 32-bit float
*/
func int STR_ToFloat(var string str) {
var zString zStr; zStr = _^(_@s(str));
var int ptr; ptr = zStr.ptr;
var int f;
var int fPtr; fPtr = _@(f);
const int _atoflt_G1 = 7876596; //0x782FF4
const int _atoflt_G2 = 8243410; //0x7DC8D2
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(ptr));
CALL_PtrParam(_@(fPtr));
CALL_PutRetValTo(0);
CALL__cdecl(MEMINT_SwitchG1G2(_atoflt_G1, _atoflt_G2));
call = CALL_End();
};
return f;
};
convert.d – Number conversion functions (hex to dec, dec to bin, dec to hex, ...)
/*
* convert.d
*
* Number conversion functions
*
* - Requires Ikarus, strings.d
* - Compatible with Gothic 1 and Gothic 2
*
*
* func string dec2hex(int dec)
* func int hex2dec(string hex)
*
* func string dec2bin(int dec)
* func int bin2dec(string bin)
*
* func string hex2bin(string hex)
* func string bin2hex(string bin)
*
* func int h(string hex) Shorter alias for hex2dec
* func int b(string bin) Shorter alias for bin2dec
*/
/*
* Fix function from Ikarus: Based on MEMINT_ByteToKeyHex
*/
func string byte2hex(var int byte) {
const int ASCII_0 = 48;
const int ASCII_A = 65;
byte = byte & 255;
// Fix ASCII characters (A to F)
var int c1; c1 = (byte >> 4);
if (c1 >= 10) {
c1 += ASCII_A-ASCII_0-10;
};
var int c2; c2 = (byte & 15);
if (c2 >= 10) {
c2 += ASCII_A-ASCII_0-10;
};
const int mem = 0;
if (!mem) { mem = MEM_Alloc(3); };
MEM_WriteByte(mem , c1 + ASCII_0);
MEM_WriteByte(mem + 1, c2 + ASCII_0);
return STR_FromChar(mem);
};
/*
* Convert decimal to hexadecimal (big endian)
*/
func string dec2hex(var int dec) {
var string hex; hex = "";
hex = ConcatStrings(hex, byte2hex(dec >> 24));
hex = ConcatStrings(hex, byte2hex(dec >> 16));
hex = ConcatStrings(hex, byte2hex(dec >> 8));
hex = ConcatStrings(hex, byte2hex(dec));
return hex;
};
/*
* Convert hexadecimal to decimal (big endian)
*/
func int hex2dec(var string hex) {
var zString zStr; zStr = _^(_@s(hex));
if (!zStr.len) {
return 0;
};
// Remove 0x prefix and h postfix
hex = STR_Lower(hex);
if (Hlp_StrCmp(STR_Prefix(hex, 2), "0x")) {
hex = STR_SubStr(hex, 2, zStr.len-2);
} else if (MEM_ReadByte(zStr.ptr+zStr.len-1) == /*h*/ 104) {
hex = STR_Prefix(hex, zStr.len-1);
};
// Remove any spaces
hex = STR_ReplaceAll(hex, " ", "");
// Check length
if (zStr.len > 8) {
MEM_Error("hex2dec: Hexadecimal number to big. Considering the last 4 bytes only.");
hex = STR_Postfix(hex, zStr.len-8);
};
// Iterate over all characters (from back to front)
var int dec; dec = 0;
repeat(i, zStr.len); var int i;
dec += MEMINT_HexCharToInt(MEM_ReadByte(zStr.ptr+(zStr.len-1)-i)) << 4*i;
end;
return dec;
};
func int h(var string hex) {
return hex2dec(hex);
};
/*
* Convert decimal to binary
*/
func string dec2bin(var int dec) {
var string bin; bin = "";
repeat(i, 32); var int i;
if (dec & (1 << i)) {
bin = ConcatStrings("1", bin);
} else {
bin = ConcatStrings("0", bin);
};
end;
return bin;
};
/*
* Convert hexadecimal to binary
*/
func string hex2bin(var string hex) {
return dec2bin(hex2dec(hex));
};
/*
* Convert binary to decimal
*/
func int bin2dec(var string bin) {
var zString zStr; zStr = _^(_@s(bin));
if (!zStr.len) {
return 0;
};
// Remove any spaces
bin = STR_ReplaceAll(bin, " ", "");
// Check length
if (zStr.len > 32) {
MEM_Error("bin2dec: Binary number to big. Considering the last 32 bits only.");
bin = STR_Postfix(bin, zStr.len-32);
};
// Iterate over all characters (from back to front)
const int ASCII_0 = 48;
const int ASCII_1 = 49;
var int dec; dec = 0;
repeat(i, zStr.len); var int i;
var int c; c = MEM_ReadByte(zStr.ptr+(zStr.len-1)-i);
if (c < ASCII_0) || (c > ASCII_1) {
MEM_Error(ConcatStrings("bin2dec: Invalid binary char: ", STR_FromChar(_@(c))));
return 0;
};
dec += (c - ASCII_0) << i;
end;
return dec;
};
func int b(var string bin) {
return bin2dec(bin);
};
/*
* Convert binary to hexadecimal
*/
func string bin2hex(var string bin) {
return dec2hex(bin2dec(bin));
};
Die convert.d setzt eine STR_Lower Funktion voraus die nirgends definiert wird. Quelle? In der strings.d ist sie nicht.
Die convert.d setzt eine STR_Lower Funktion voraus die nirgends definiert wird. Quelle? In der strings.d ist sie nicht.
Wird mit Ikarus 1.2.2 mitgeliefert:
https://forum.worldofplayers.de/forum/threads/1299679-Skriptpaket-Ikarus-4/page18?p=26439597&viewfull=1#post26439597
Danke Dada!
Es wäre nett wenn die im EP von Ikarus verlinkt werden würde. Da steht nämlich nur die 1.2.1
Milky-Way
30.09.2020, 17:11
Es wäre nett wenn die im EP von Ikarus verlinkt werden würde. Da steht nämlich nur die 1.2.1
gesagt, getan :)
F a w k e s
06.11.2020, 12:41
Hello folks,
Edit: 2020-11-07 code updated - there was an error causing that multipage book would be reopened if player pressed left/right arrow in a 'standard' book.
I've been using functions created by Rhobar, Gottfried and TheEternal allowing multi-page books for a while now - and I love them!
I tried to get rid of Bookhelper NPC (his key capturing AI was not 100% reliable, and ESCape didn't always work for me) and instead used LeGo to hook oCDocumentManager__HandleEvent.
All seems to be working fine, you can change pages with arrows/mouse scrolling wheel, closing with both mouse right button and escape works reliably, so I want to share this small piece with you :gratz
I took 1 liberty of changing original global variables - I added suffix 'MultiPageBook_' to each as they seemed very generic to me, but this should not be a huge issue.
Requires both Ikarus & LeGo:
In case of lego you need to initialize LeGo_AI_Function:
LeGo_Init ( flags | LeGo_AI_Function);
//Original functions:
//Rhobar, Gottfried:
//http://forum.worldofplayers.de/forum/threads/790720-Scriptpaket-Zugriff-auf-ZenGine-Objekte/page16
//TheEternal mDoc_SetPagenumbers:
//https://forum.worldofplayers.de/forum/threads/1299679-Skriptpaket-Ikarus-4/page4?p=23136430&viewfull=1#post23136430
//################################################################################ ########
//## ##
//## B_MultiPageBooks ##
//## ================ ##
//## ##
//## Dieses Script ermöglicht die Erstellung von mehrseitigen Büchern, die ##
//## per Tastendruck (Pfeiltasten links und rechts) durchgeblättert werden können. ##
//## In jedem "Multibuch" müss in der On_State Funktion "B_MultiPageBooks" ##
//## aufgerufen werden. Als Parameter erwartet die Funktion einen String, ##
//## der den Funktionsnamen der Buchseiten enthält (GROß GESCHRIEBEN!). In der ##
//## neuen Version wird keine Seitenzahl mehr benötigt, sie wird nun vom Spiel ##
//## selbst erkannt. Außerdem können nun alle Seiten in eine einzige Funktion ##
//## geschrieben werden und das ganze System ist nun denke ich verständlicher ##
//## ##
//################################################################################ ########
var string MultiPageBook_CurrentBook;
var int MultiPageBook_CurrentBookLoopable;
var int MultiPageBook_CurrentPage;
var int MultiPageBook_CurrentBookMaxPage;
//----------------------------------
// Nützliche Konstanten
//----------------------------------
// Wird verwendet um eine Schriftart, Margins oder das Hintergrundbild dauerhaft zu setzen (siehe Beispiel)
const int LEFT_PAGE = -3;
const int RIGHT_PAGE = -2;
const int FONT_EVER = -1;
// Flags (können über mDoc_Create(var int flag) gesetzt werden
const int DOC_FLAG_NONE = 0; // Nichts
const int DOC_FLAG_LOOP = 1; // Buch fängt hinten wieder von vorne an
func void B_MultiPageBooks (var string fnc)
{
MultiPageBook_CurrentBookMaxPage = 2;
MultiPageBook_CurrentBook = fnc;
MultiPageBook_CurrentPage = 0;
MEM_CallByString (MultiPageBook_CurrentBook);
};
//----------------------------------
// Die Doc Funktionen zum
// Gebrauch von mehr als
// nur zwei Seiten
//----------------------------------
// Die Namen wurden alle beibehalten, es wurde nur überall ein 'm' vorne angehängt (m wie multi)
// Neu: Flags (siehe Konstanten)
func int mDoc_Create(var int flags) {
if(flags==DOC_FLAG_LOOP) {
MultiPageBook_CurrentBookLoopable = 1;
}
else {
MultiPageBook_CurrentBookLoopable = 0;
};
MultiPageBook_CurrentBookMaxPage = 0;
return Doc_Create();
};
// Neu: Seitenzahl wird nicht mehr benötigt
func void mDoc_SetPages(var int id) {
Doc_SetPages(id, 2);
};
// Neu: Mehr als nur 0, 1 bei 'page' möglich
func void mDoc_SetMargins(var int id, var int page, var int x1, var int x2, var int x3, var int x4, var int pxl) {
if(page<-1) {
Doc_SetMargins(id, page + 3, x1, x2, x3, x4, pxl);
return;
};
if(page>MultiPageBook_CurrentBookMaxPage) { MultiPageBook_CurrentBookMaxPage = page; };
if(page-MultiPageBook_CurrentPage < 0||page-MultiPageBook_CurrentPage > 1) {
return;
};
Doc_SetMargins(id, page - MultiPageBook_CurrentPage, x1, x2, x3, x4, pxl);
};
// Neu: Mehr als nur 0, 1 bei 'page' möglich
func void mDoc_SetPage(var int id, var int page, var string tex, var int scl) {
if(page<-1) {
Doc_SetPage(id, page+3, tex, scl);
return;
};
if(page>MultiPageBook_CurrentBookMaxPage) { MultiPageBook_CurrentBookMaxPage = page; };
if(page-MultiPageBook_CurrentPage < 0||page-MultiPageBook_CurrentPage > 1) {
return;
};
Doc_SetPage(id, page - MultiPageBook_CurrentPage, tex, scl);
};
// Neu: Mehr als nur -1, 0 und 1 bei 'page' möglich
func void mDoc_SetFont(var int id, var int page, var string tex) {
if(page<-1) {
Doc_SetFont(id, page+3, tex);
return;
};
if(page==-1) {
Doc_SetFont(id, page, tex);
return;
};
if(page>MultiPageBook_CurrentBookMaxPage) { MultiPageBook_CurrentBookMaxPage = page; };
if(page-MultiPageBook_CurrentPage < 0||page-MultiPageBook_CurrentPage > 1) {
return;
};
Doc_SetFont(id, page - MultiPageBook_CurrentPage, tex);
};
// Neu: Mehr als nur 0, 1 bei 'page' möglich
func void mDoc_PrintLine(var int id, var int page, var string text) {
if(page>MultiPageBook_CurrentBookMaxPage) { MultiPageBook_CurrentBookMaxPage = page; };
if(page-MultiPageBook_CurrentPage < 0||page-MultiPageBook_CurrentPage > 1) {
return;
};
Doc_PrintLine(id, page - MultiPageBook_CurrentPage, text);
};
// Neu: Mehr als nur 0, 1 bei 'page' möglich
func void mDoc_PrintLines(var int id, var int page, var string text) {
if(page>MultiPageBook_CurrentBookMaxPage) { MultiPageBook_CurrentBookMaxPage = page; };
if(page-MultiPageBook_CurrentPage < 0||page-MultiPageBook_CurrentPage > 1) {
return;
};
Doc_PrintLines(id, page - MultiPageBook_CurrentPage, text);
};
// Neue Hilfsfunktion: Erstellt ein normales Buch (es fehlen nur noch Hintergründe)
func void mDoc_CreateStandard(var int DocID) {
mDoc_SetPages (DocID);
mDoc_SetMargins (DocID, LEFT_PAGE, 275, 20, 30, 20, 1);
mDoc_SetMargins (DocID, RIGHT_PAGE, 30, 20, 275, 20, 1);
};
// Unverändert [existiert nur der Ordnung halber]
func void mDoc_Show(var int id) {
// Überflüssig ich weiß :p
Doc_Show(id);
};
func void mDoc_SetPagenumbersLayout (var int DocID)
{
mDoc_SetMargins (DocID, LEFT_PAGE, 355, 468, 30, 20, 1);
mDoc_SetMargins (DocID, RIGHT_PAGE, 140, 468, 30, 20, 1);
};
func void mDoc_CreatePageNumbers(var int DocID, var int Pagenumber, var int PagenumberMax)
{
if (Pagenumber > 0) {
var string pageNoString; pageNoString = IntToString (Pagenumber);
pageNoString = ConcatStrings (pageNoString, "/");
pageNoString = ConcatStrings (pageNoString, IntToString (PagenumberMax));
mDoc_PrintLines (DocID, Pagenumber - 1, pageNoString);
mDoc_CreatePageNumbers(DocID, Pagenumber - 1, PagenumberMax);
};
};
func void mDoc_SetPagenumbers(var int DocID, var int Pagenumbers)
{
mDoc_SetPagenumbersLayout(DocID);
mDoc_CreatePageNumbers(DocID, Pagenumbers, Pagenumbers);
};
//--- Hooked function
FUNC VOID AIQ_MultiPageBook_Update () {
MEM_CallByString (MultiPageBook_CurrentBook);
};
//00724D30 .text Debug data ?HandleEvent@oCDocumentManager@@UAEHH@Z
const int oCDocumentManager__HandleEvent_G1 = 7490864;
//0x0065F450 public: virtual int __thiscall oCDocumentManager::HandleEvent(int)
const int oCDocumentManager__HandleEvent_G2 = 6681680;
func void _HOOK_DOCUMENTMANAGER_HANDLEEVENT ()
{
var int update; update = FALSE;
var int key; key = MEM_ReadInt (ESP + 4);
//2050 - Left Mouse button
//2052 - Right Mouse button
//2057 - Wheel up
//2058 - Wheel down
if (key == KEY_LEFTARROW) || (key == 2057) || (key == KEY_A)
{
if (!Hlp_StrCmp (MultiPageBook_CurrentBook, "")) {
if (MultiPageBook_CurrentPage > 0) {
MultiPageBook_CurrentPage -= 2;
update = TRUE;
} else if (MultiPageBook_CurrentBookLoopable) {
MultiPageBook_CurrentPage = MultiPageBook_CurrentBookMaxPage - (MultiPageBook_CurrentBookMaxPage%2);
update = TRUE;
};
};
};
if (key == KEY_RIGHTARROW) || (key == 2058) || (key == KEY_D)
{
if (!Hlp_StrCmp (MultiPageBook_CurrentBook, "")) {
if (MultiPageBook_CurrentPage < MultiPageBook_CurrentBookMaxPage-2) {
MultiPageBook_CurrentPage += 2;
update = TRUE;
} else if (MultiPageBook_CurrentBookLoopable) {
MultiPageBook_CurrentPage = 0;
update = TRUE;
};
};
};
if (key == KEY_ESCAPE) || (key == 2052) || (key == KEY_TAB) {
MultiPageBook_CurrentBook = "";
};
if (update) {
//Replace current key with KEY_ESCAPE - this will close current book
MEM_WriteInt (ESP + 4, KEY_ESCAPE);
EDI = KEY_ESCAPE;
//call update using hero's AI queue
AI_Function (hero, AIQ_MultiPageBook_Update);
};
};
Needs to be hooked with:
HookEngine (MEMINT_SwitchG1G2 (oCDocumentManager__HandleEvent_G1, oCDocumentManager__HandleEvent_G2), 6, "_HOOK_DOCUMENTMANAGER_HANDLEEVENT");
Example --> book with 3 pages:
instance Book_Example_3_Pages (C_Item)
{
name = "Book with 3 pages";
mainflag = ITEM_KAT_DOCS;
flags = 0;
value = 0;
visual = "ItWr_Book_02_01.3ds";
material = MAT_LEATHER;
scemeName = "MAP";
description = name;
text[5] = NAME_Value;
count[5] = Value;
on_state[0] = Book_Example_3_Pages_OnState;
};
func void Book_Example_3_Pages_OnState ()
{
if (NPC_IsPlayer (self)) {
B_MultiPageBooks ("BOOK_EXAMPLE_3_PAGES_CONTENT");
};
};
func void Book_Example_3_Pages_CONTENT ()
{
var int DocID;
DocID = mDoc_Create (DOC_FLAG_NONE); //Non loopable
mDoc_CreateStandard (DocID);
mDoc_SetPage (DocID, LEFT_PAGE, "Book_Brown_L.tga", 0);
mDoc_SetPage (DocID, RIGHT_PAGE, "Book_Brown_R.tga", 0);
mDoc_SetFont (DocID, FONT_EVER, "font_10_book.tga");
// 1. Page
mDoc_SetMargins (DocID, 0, 275, 20, 30, 20, 1);
mDoc_PrintLines (DocID, 0, "Some text on Page 1.");
// 2. Page
mDoc_SetMargins (DocID, 1, 30, 20, 275, 20, 1);
mDoc_PrintLines (DocID, 1, "Some text on Page 2.");
// 3. Page
mDoc_SetMargins (DocID, 2, 275, 20, 30, 20, 1);
mDoc_PrintLines (DocID, 2, "Some text on Page 3.");
// 4. Page - we need to be setup 4 pages!
mDoc_SetMargins (DocID, 3, 30, 20, 275, 20, 1);
//Add page numbers 1 - 3
mDoc_SetPagenumbers (DocID, 3);
mDoc_Show (DocID);
};
Example --> Bookstand with 3 pages:
/*
BookStand ON_STATE function called from Mob when used
DO NOT USE ON_STATE function with numbers --> BOOKSTAND_EXAMPLE_1_S1 - Gothic does not like that :-(
*/
FUNC VOID BOOKSTAND_EXAMPLE_S1 ()
{
if (NPC_IsPlayer (self)) {
AI_UseMob (self, "BOOK", -1);
B_MultiPageBooks ("BOOKSTAND_EXAMPLE_CONTENT");
};
};
FUNC VOID BOOKSTAND_EXAMPLE_CONTENT ()
{
//We need to clear hero's AI queque here! as it is busy with 'AI_UseMob'
NPC_ClearAIQueue (hero);
var int DocID;
DocID = mDoc_Create (DOC_FLAG_NONE);
mDoc_CreateStandard (DocID);
mDoc_SetPage (DocID, LEFT_PAGE, "Book_Brown_L.tga", 0);
mDoc_SetPage (DocID, RIGHT_PAGE, "Book_Brown_R.tga", 0);
mDoc_SetFont (DocID, FONT_EVER, "font_10_book.tga");
// 1. Page
mDoc_SetMargins (DocID, 0, 275, 20, 30, 20, 1);
mDoc_PrintLines (DocID, 0, "Bittida en morgon innan solen upprann");
mDoc_PrintLines (DocID, 0, "Innan foglarna började sjunga");
mDoc_PrintLines (DocID, 0, "Bergatrollet friade till fager ungersven");
mDoc_PrintLines (DocID, 0, "Hon hade en falskeliger tunga");
mDoc_PrintLines (DocID, 0, "");
mDoc_PrintLines (DocID, 0, "Herr Mannelig herr Mannelig trolofven i mig");
mDoc_PrintLines (DocID, 0, "För det jag bjuder sa gerna");
mDoc_PrintLines (DocID, 0, "I kunnen väl svara endast ja eller nej");
mDoc_PrintLines (DocID, 0, "Om i viljen eller ej:");
mDoc_PrintLines (DocID, 0, "");
mDoc_PrintLines (DocID, 0, "Eder vill jag gifva de gangare tolf");
mDoc_PrintLines (DocID, 0, "Som ga uti rosendelunde");
mDoc_PrintLines (DocID, 0, "Aldrig har det varit nagon sadel uppa dem");
mDoc_PrintLines (DocID, 0, "Ej heller betsel uti munnen");
// 2. Page
mDoc_SetMargins (DocID, 1, 30, 20, 275, 20, 1);
mDoc_PrintLines (DocID, 1, "Herr Mannelig herr Mannelig trolofven i mig");
mDoc_PrintLines (DocID, 1, "För det jag bjuder sa gerna");
mDoc_PrintLines (DocID, 1, "I kunnen väl svara endast ja eller nej");
mDoc_PrintLines (DocID, 1, "Om i viljen eller ej:");
mDoc_PrintLines (DocID, 1, "");
mDoc_PrintLines (DocID, 1, "Eder vill jag gifva de qvarnarna tolf");
mDoc_PrintLines (DocID, 1, "Som sta mellan Tillö och Ternö");
mDoc_PrintLines (DocID, 1, "Stenarna de äro af rödaste gull");
mDoc_PrintLines (DocID, 1, "Och hjulen silfverbeslagna");
// 3. Page
mDoc_SetMargins (DocID, 2, 275, 20, 30, 20, 1);
mDoc_PrintLines (DocID, 2, "Herr Mannelig herr Mannelig trolofven i mig");
mDoc_PrintLines (DocID, 2, "För det jag bjuder sa gerna");
mDoc_PrintLines (DocID, 2, "I kunnen väl svara endast ja eller nej");
mDoc_PrintLines (DocID, 2, "Om i viljen eller ej:");
mDoc_PrintLines (DocID, 2, "");
mDoc_PrintLines (DocID, 2, "Eder vill jag gifva ett förgyllande svärd");
mDoc_PrintLines (DocID, 2, "Som klingar utaf femton guldringar");
mDoc_PrintLines (DocID, 2, "Och strida huru I strida vill");
mDoc_PrintLines (DocID, 2, "Stridsplatsen skolen i väl vinna");
// 4. Page - we need to be setup 4 pages!
mDoc_SetMargins (DocID, 3, 30, 20, 275, 20, 1);
//Add page numbers 1 - 3
mDoc_SetPagenumbers (DocID, 3);
mDoc_Show (DocID);
};
Damit sie nicht verloren gehen, hier die Skripte zur Überarbeitung der Begleiter KI: https://forum.worldofplayers.de/forum/threads/1540269-G1-Followers-%28inventory-management%29
Edit: hat jemand das Skript gesehen, mit dem man Begleiter an ort und Stelle warten lassen kann? Ich finds nicht.
F a w k e s
25.12.2020, 18:43
Hello folks,
I suppose AI_TurnToSound and AI_GotoSound have become my favorite functions over last couple of days -->
With a small change (& thanks to Ikarus!) we can turn them into universal functions working with XYZ coordinates:
AI_TurnToPos(hero,_@(wp.pos));//will turn hero to position of Waypoint.
AI_GotoPos(hero,_@(wp.pos));//will force hero to go to position of a Waypoint. (just an example on how to use it)
In order to simplify usage I have added couple of wrapper functions, also adding here some other functions, that allow G1 users to have their own version of 'ExternalAcceptVobs':
AI_TurnToPos(var int slfInstance,var int posPtr) - turns NPC to pos pointer (posPtr has to be pointer to an int[3] array containing XYZ coordinates);
AI_TurnToWP(var int slfInstance,var string waypoint) - turns NPC to waypoint
AI_TurnAwayWP(var int slfInstance,var string waypoint) - turns NPC away from waypoint
AI_TurnToVobPtr(var int slfInstance,var int vobPtr) - turns NPC away from vobPtr (vobPtr has to be pointer to a vob)
AI_TurnAwayVobPtr(var int slfInstance,var int vobPtr) - turns NPC away from vobPtr
AI_GotoPos(var int slfInstance,var int posPtr) - NPC goes to posPtr
AI_GotoVobPtr(var int slfInstance,var int vobPtr) - NPC goes to vobPtr
NPC_CanSee(var int slfInstance,var int vobPtr,var int lineOfSight) - returns true if NPC can see vobPtr.
if (lineOfSight == 0) - NPC does not have to face vobPtr
if (lineOfSight == 1) - NPC has to face vobPtr
NPC_GetDistToPos(var int slfInstance,var int posPtr) - returns distance from NPC to posPtr
NPC_GetDistToVobPtr(var int slfInstance,var int vobPtr) - returns distance from NPC to vobPtr
Code should work with both G1 & G2A engines - I didn't test G2A though.
You will need functions SearchWaypointByName & TrfToPos from Scriptbin (author: mud-freak)
https://forum.worldofplayers.de/forum/threads/1495001-Scriptsammlung-ScriptBin/page2?p=25712257&viewfull=1#post25712257
Also you will need couple of functions that can help with Vectors:
//copy vector v1 to v2
//v1 - pointer to vector 1
//v2 - pointer to vector 2
func void copyVector (var int v1, var int v2){
MEM_CopyBytes (v1, v2, 12);
};
//adds vector v2 to v1
//v - output pointer
//v1 - pointer to vector 1
//v2 - pointer to vector 2
func void addVectors (var int v, var int v1, var int v2){
MEM_WriteIntArray (v, 0, addf(MEM_ReadIntArray(v1, 0), MEM_ReadIntArray(v2, 0)));
MEM_WriteIntArray (v, 1, addf(MEM_ReadIntArray(v1, 1), MEM_ReadIntArray(v2, 1)));
MEM_WriteIntArray (v, 2, addf(MEM_ReadIntArray(v1, 2), MEM_ReadIntArray(v2, 2)));
};
//subtracts vector v2 from v1
//v - output pointer
//v1 - pointer to vector 1
//v2 - pointer to vector 2
func void subVectors (var int v, var int v1, var int v2){
MEM_WriteIntArray (v, 0, subf(MEM_ReadIntArray(v1, 0), MEM_ReadIntArray(v2, 0)));
MEM_WriteIntArray (v, 1, subf(MEM_ReadIntArray(v1, 1), MEM_ReadIntArray(v2, 1)));
MEM_WriteIntArray (v, 2, subf(MEM_ReadIntArray(v1, 2), MEM_ReadIntArray(v2, 2)));
};
//scale the vector with division
//v1 - pointer to vector
//m - division scalar
func void divVector (var int v1, var int d){
if (!d) { return; };
MEM_WriteIntArray (v1, 0, divf(MEM_ReadIntArray(v1, 0), d));
MEM_WriteIntArray (v1, 1, divf(MEM_ReadIntArray(v1, 1), d));
MEM_WriteIntArray (v1, 2, divf(MEM_ReadIntArray(v1, 2), d));
};
//scale the vector with multiplication
//v1 - pointer to vector
//m - multiplication scalar
func void mulVector (var int v1, var int m){
MEM_WriteIntArray (v1, 0, mulf(MEM_ReadIntArray(v1, 0), m));
MEM_WriteIntArray (v1, 1, mulf(MEM_ReadIntArray(v1, 1), m));
MEM_WriteIntArray (v1, 2, mulf(MEM_ReadIntArray(v1, 2), m));
};
//returns magnitude of a vector
//v1 - pointer to vector
FUNC INT magVector (var int v1){
return sqrtf (addf (addf (sqrf (MEM_ReadIntArray(v1, 0)), sqrf (MEM_ReadIntArray(v1, 1))), sqrf (MEM_ReadIntArray(v1, 2))));
};
//normalize vector
//v - output pointer
//v1 - pointer to vector 1
func void normalizeVector (var int v1){
var int m; m = magVector (v1);
if (!m) { return; };
divVector (v1, m); };
And finally functions this topic was all about:
func void AI_TurnToPos (var int slfInstance, var int posPtr) {
if (!posPtr) { return; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
slf.soundPosition[0] = MEM_ReadIntArray(posPtr, 0);
slf.soundPosition[1] = MEM_ReadIntArray(posPtr, 1);
slf.soundPosition[2] = MEM_ReadIntArray(posPtr, 2);
AI_TurnToSound (slf);
};
func void AI_TurnToWP (var int slfInstance, var string waypoint) {
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var int wpPtr; wpPtr = SearchWaypointByName (waypoint);
if (!wpPtr) { return; };
var zCWaypoint wp; wp = _^ (wpPtr);
AI_TurnToPos (slf, _@ (wp.pos));
};
/*
AI_TurnAwayWP
[waypoint]
/
/
[self] ([dir vector] = [waypoint] - [self])
/
/
[pos] (pos = [self] - [dir vector])
*/
func void AI_TurnAwayWP (var int slfInstance, var string waypoint) {
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var int wpPtr; wpPtr = SearchWaypointByName (waypoint);
if (!wpPtr) { return; };
var zCWaypoint wp; wp = _^ (wpPtr);
var int pos[3]; //position - vob
copyVector (_@ (wp.pos), _@ (pos));
var int posSelf[3]; //position - slf
var int dir[3]; //direction - slf - wp
//get position of slf
TrfToPos (_@ (slf._zCVob_trafoObjToWorld), _@ (posSelf));
//subtract posSelf from pos - to get 'direction vector'
subVectors (_@ (dir), _@(pos), _@ (posSelf));
//subtract direction vector from posSelf - should be basically pos rotated by 180 around self pos
subVectors (_@ (pos), _@ (posSelf), _@ (dir));
AI_TurnToPos (slf, _@ (pos));
};
func void AI_TurnToVobPtr (var int slfInstance, var int vobPtr) {
if (!vobPtr) { return; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var zCVob vob; vob = _^(vobPtr);
var int pos[3];
TrfToPos (_@(vob.trafoObjToWorld), _@ (pos));
AI_TurnToPos (slf, _@ (pos));
};
func void AI_TurnAwayVobPtr (var int slfInstance, var int vobPtr) {
if (!vobPtr) { return; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var zCVob vob; vob = _^(vobPtr);
var int pos[3]; //position - vob
TrfToPos (_@ (vob.trafoObjToWorld), _@ (pos));
var int posSelf[3]; //position - slf
var int dir[3]; //direction - slf - vob
//get position of slf
TrfToPos (_@ (slf._zCVob_trafoObjToWorld), _@ (posSelf));
//subtract pos slf from pos - to get 'direction vector'
subVectors (_@ (dir), _@(pos), _@ (posSelf));
//subtract direction vector from self - should be basically pos rotated by 180 around self pos
subVectors (_@ (pos), _@ (posSelf), _@ (dir));
AI_TurnToPos (slf, _@ (pos));
};
func void AI_GotoPos (var int slfInstance, var int posPtr) {
if (!posPtr) { return; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
slf.soundPosition[0] = MEM_ReadIntArray(posPtr, 0);
slf.soundPosition[1] = MEM_ReadIntArray(posPtr, 1);
slf.soundPosition[2] = MEM_ReadIntArray(posPtr, 2);
AI_GotoSound (slf);
};
func void AI_GotoVobPtr (var int slfInstance, var int vobPtr) {
if (!vobPtr) { return; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var zCVob vob; vob = _^(vobPtr);
var int pos[3];
TrfToPos (_@ (vob.trafoObjToWorld), _@ (pos));
AI_GotoPos (slf, _@ (pos));
};
func int NPC_CanSee (var int slfInstance, var int vobPtr, var int lineOfSight){
if (!vobPtr) { return 0; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return 0; };
//0069E010 .text Debug data ?CanSee@oCNPC@@QAEHPAVzCVob@@H@Z
const int oCNPC__CanSee_G1 = 6938640;
//0x00741C10 public: int __thiscall oCNPC::CanSee(class zCVob *,int)
const int oCNPC__CanSee_G2 = 7609360;
CALL_IntParam (lineOfSight);
CALL_PtrParam (vobPtr);
CALL__thiscall (MEM_InstToPtr (slf), MEMINT_SwitchG1G2 (oCNPC__CanSee_G1, oCNPC__CanSee_G2));
return CALL_RetValAsInt();
};
func int NPC_GetDistToPos (var int slfInstance, var int posPtr) {
if (!posPtr) { return -1; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return -1; };
slf.soundPosition[0] = MEM_ReadIntArray(posPtr, 0);
slf.soundPosition[1] = MEM_ReadIntArray(posPtr, 1);
slf.soundPosition[2] = MEM_ReadIntArray(posPtr, 2);
return Snd_GetDistToSource (slf);
};
func int NPC_GetDistToVobPtr (var int slfInstance, var int vobPtr) {
if (!vobPtr) { return -1; };
var oCNPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return -1; };
var zCVob vob; vob = _^(vobPtr);
var int pos[3];
TrfToPos (_@(vob.trafoObjToWorld), _@ (pos));
return NPC_GetDistToPos (slf, _@ (pos)); };
Let me know in case of any issues. :gratz
F a w k e s
30.12.2020, 18:42
Hello folks,
I found recently post from Neconspictor in which he presented script that removes all items from oCMobContainer (chest). Using this example I prepared couple of functions that can help with item removal from chest. Besides sharing - I am looking for advice/approval. In case of function Mob_RemoveItems I am reducing item amount by adjusting oCItem.amount property. And I don't know if this is right method, am I causing memory leaks with this approach?
You can find in a code below these functions:
func void oCMobContainer_Remove(var int mobPtr,var int itmPtr) - removes item from oCMobContainer by its pointer.
func int oCMobContainer_GetItemPtrBySlot(var int mobPtr,var int itmSlot) - returns pointer to oCItem in a specific inventory slot in a chest.
func void Mob_RemoveAllItems(var int mobPtr) - removes all items from oCMobContainer.
func void Mob_RemoveItems(var int mobPtr,var int itemInstance,var int qty) - removes item(s) from chest.
func void Mob_TransferItemsToNPC(var int mobPtr,var int slfInstance) - transfers chest content to NPC's inventory.
Original Neconspictor's post:
//https://forum.worldofplayers.de/forum/threads/1449528-Mob_RemoveItems?p=24599541&viewfull=1#post24599541 (https://forum.worldofplayers.de//https://forum.worldofplayers.de/forum/threads/1449528-Mob_RemoveItems?p=24599541&viewfull=1#post24599541)
All of them should be working with both G1 & G2A, I didn't test G2A though:
//Removes item from oCMobContainer
func void oCMobContainer_Remove (var int mobPtr, var int itmPtr){
if (!mobPtr) { return; };
if (!itmPtr) { return; };
//00683EB0 .text Debug data ?Remove@oCMobContainer@@UAEXPAVoCItem@@@Z
const int oCMobContainer__Remove_G1 = 6831792;
//0x00725FF0 public: virtual void __thiscall oCMobContainer::Remove(class oCItem *)
const int oCMobContainer__Remove_G2 = 7495664;
CALL_PtrParam (itmPtr);
CALL__thiscall (mobPtr, MEMINT_SwitchG1G2 (oCMobContainer__Remove_G1, oCMobContainer__Remove_G2));
};
//Returns pointer to oCItem in a specific inventory slot in a chest
//oCMobContainer_GetItemPtrBySlot (mobPtr, 0); //first item in a chest inventory
func int oCMobContainer_GetItemPtrBySlot (var int mobPtr, var int itmSlot){
if (!mobPtr) { return 0 ; };
var int ptr;
var int itmPtr;
var zCListSort list;
var oCMobContainer container; container = _^ (mobPtr);
var int i; i = 0;
ptr = container.containList_next;
while (ptr);
list = _^ (ptr);
itmPtr = list.data;
if (itmPtr) {
if (itmSlot == i) {
return itmPtr;
};
i += 1;
};
ptr = list.next;
end;
return 0;
};
//Mob_RemoveAllItems (her.focus_vob);
func void Mob_RemoveAllItems (var int mobPtr){
if (!mobPtr) { return; };
var int ptr;
var oCMobContainer container; container = _^(mobPtr);
var zCListSort list;
ptr = container.containList_next;
while (ptr != 0);
list = _^(ptr);
var int itmPtr; itmPtr = list.data;
if (itmPtr == 0) {
ptr = list.next;
} else {
oCMobContainer_Remove(mobPtr, itmPtr);
ptr = container.containList_next;
};
end;
};
//Removes items from chest
//Mob_RemoveItems (her.focus_vob, ItarScrollFireBolt, 1);
func void Mob_RemoveItems (var int mobPtr, var int itemInstance, var int qty){
if (!mobPtr) { return; };
var oCItem itm;
var int itmPtr; itmPtr = 1;
var int i; i = 0;
//Loop through all inventory slots to find itemInstance
while (itmPtr);
itmPtr = oCMobContainer_GetItemPtrBySlot (mobPtr, i);
if (itmPtr) {
itm = _^ (itmPtr);
if (Hlp_GetInstanceID (itm) == itemInstance) {
if (itm.amount > qty) {
//Adjust qty - is this okay?
itm.amount = (itm.amount - qty);
} else {
//Remove everything
oCMobContainer_Remove (mobPtr, itmPtr);
};
return;
};
};
i += 1;
end;
};
//Transfers all items to inventory of an NPC
//Mob_TransferItemsToNPC (her.focus_vob, hero);
func void Mob_TransferItemsToNPC (var int mobPtr, var int slfInstance){
if (!mobPtr) { return; };
var C_NPC slf; slf = Hlp_GetNPC (slfInstance);
if (!Hlp_IsValidNPC (slf)) { return; };
var oCItem itm;
var int i;
var int ptr;
var oCMobContainer container; container = _^ (mobPtr);
var zCListSort list;
ptr = container.containList_next;
while (ptr != 0);
list = _^(ptr);
var int itmPtr; itmPtr = list.data;
if (itmPtr == 0) {
ptr = list.next;
} else {
itm = _^ (itmPtr);
i = 0;
while (i<itm.amount);
CreateInvItem (slf, Hlp_GetInstanceID (itm));
i += 1;
end;
oCMobContainer_Remove(mobPtr, itmPtr);
ptr = container.containList_next;
};
end;
};
mud-freak
02.01.2021, 13:43
Vor einer Weile hatte ich hier ein Item-Äquivalent zu Npc_GetPortalGuild geteilt. Fawkes und ich hatten uns anschließend etwas damit auseinandergesetzt, dass solche Portal-Abfragen (inkl. der externen Engine-Funktionen Npc_GetPortalGuild und Npc_GetPortalOwner) nicht verlässlich funktionieren. Der Grund ist, dass bei mehreren Portalzuweisungen Duplikate in der Portalliste enstehen. Ob ein Portalraum einem NPC gehört und/oder einer Gilde kann nicht immer beides korrekt festgestellt werden. Dieses Problem war evtl. dem ein oder anderen schon bekannt.
Ich hänge hier unten nun eine Sammlung von nützlichen Funktionen an, um Portalzuweisungen (auch für Items oder Vobs) abzufragen. Außerdem wird dort der Bug der Dupliakte in den Zuweisungen behoben, in dem neue Zuweisungen bestehende aktualisieren, anstatt neue zu erstellen.
Entweder kann man mit Wld_AssignRoomFix_Init die externen Funktionen Wld_AssignRoomToNpc und Wld_AssignRoomToGuild vor ihrer Nutzung patchen und/oder nach deren Nutzung mit Wld_PortalMergeDuplicates alle Mehrfach-Zuweisungen zu einer einzelnen kombinieren. Beide Ansätz haben den gleichen Effekt: Die Zuweisungsabfragen aus den Skripten heraus sind anschließend eindeutig.
Getestet habe ich das ganze mehr oder weniger ausgiebig für Gothic 1. Für Gothic 2 sollte es aber genauso funktionieren.
portalFunctions.d – Supply more portal room retrieval functions, e.g. for items, and fix the bug of duplicate portal room assignments.
/*
* portalFunctions.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=26646175
*
* Supply more portal retrieval functions, e.g. for items, and fix duplicate portal room assignments.
*
* - Requires Ikarus, LeGo (HookEngine)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions (to fix the duplicate assignments)
* - (Optional) Initialize from Init_Global with
* Wld_AssignRoomFix_Init();
* - Alternatively, call this after room assignments
* Wld_PortalMergeDuplicates();
*
*
* func int Item_GetPortalGuild(C_Item itm)
* func C_Npc Item_GetPortalOwner(C_Item itm)
*
* func int Vob_GetPortalIndex(int)
*
* func int GetPortalIndexByName(string name)
* func string GetPortalNameByIndex(int index)
* func oCPortalRoom* GetPortalByIndex(int index)
*
* func void Wld_PortalMergeDuplicates()
*/
/*
* Remove duplicate portal room assignments to allow correct retrieval of room assignments
*/
func void Wld_PortalMergeDuplicates() {
MEM_InitAll(); // MEM_InitGlobalInst and MEM_InitLabels
if (!MEM_Game.portalman) {
return;
};
const int zCArraySort_RemoveOrderIndex_G1 = 5051504; //0x4D1470
const int zCArraySort_RemoveOrderIndex_G2 = 5104352; //0x4DE2E0
const int oCPortalRoom___oCPortalRoom_G1 = 7121104; //0x6CA8D0
const int oCPortalRoom___oCPortalRoom_G2 = 7807568; //0x772250
// Iterate over all portal assignments
// Portal assignments are sorted by portal name with string comparison (std::string::compare)
// Therefore, assignments to the same room are adjacent to each other and later assignments have a higher index
var oCPortalRoomManager pman; pman = _^(MEM_Game.portalman);
var int portalsArrPtr; portalsArrPtr = _@(pman.portals_array);
var string prevPortalName; prevPortalName = "";
var int i; i = 0;
while(i < pman.portals_numInArray);
var int portalPtr; portalPtr = MEM_ReadIntArray(pman.portals_array, i);
var oCPortalRoom portal; portal = _^(portalPtr);
// Check if duplicate, same as previous (same assignments are adjacent)
if (Hlp_StrCmp(portal.portalName, prevPortalName)) {
// Update existing portal with later assignments (only update properties that are non-empty)
var oCPortalRoom portalFirst; portalFirst = _^(MEM_ReadIntArray(pman.portals_array, i-1));
if (!Hlp_StrCmp(portal.ownerNpc, "")) {
portalFirst.ownerNpc = portal.ownerNpc;
};
if (portal.ownerGuild >= 0) {
portalFirst.ownerGuild = portal.ownerGuild;
};
// Remove later assignment from portal room list
const int call = 0;
if (CALL_Begin(call)) {
CALL_IntParam(_@(i));
CALL__thiscall(_@(portalsArrPtr), MEMINT_SwitchG1G2(zCArraySort_RemoveOrderIndex_G1,
zCArraySort_RemoveOrderIndex_G2));
CALL__thiscall(_@(portalPtr), MEMINT_SwitchG1G2(oCPortalRoom___oCPortalRoom_G1,
oCPortalRoom___oCPortalRoom_G2));
call = CALL_End();
};
} else {
// Otherwise advance to the next element
prevPortalName = portal.portalName;
i += 1;
};
end;
};
/*
* Obtain the portal room index from portal name. This function is case-sensitive!
*/
func int GetPortalIndexByName(var string name) {
Wld_PortalMergeDuplicates();
MEM_InitGlobalInst();
if (!MEM_Game.portalman) {
return -1;
};
const int oCPortalRoomManager__GetPortalRoomIndex_G1 = 7123424; //0x6CB1E0
const int oCPortalRoomManager__GetPortalRoomIndex_G2 = 7810016; //0x772BE0
//name = STR_Upper(name); // Not necessarily! See huts in INIT_SUB_OLDCAMP in Gothic 1
var int namePtr; namePtr = _@s(name);
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(namePtr));
CALL_PutRetValTo(_@(index));
CALL__thiscall(_@(MEM_Game.portalman), MEMINT_SwitchG1G2(oCPortalRoomManager__GetPortalRoomIndex_G1,
oCPortalRoomManager__GetPortalRoomIndex_G2));
call = CALL_End();
};
var int index;
return +index;
};
/*
* Obtain portal room pointer from portal index
*/
func int GetPortalByIndex(var int index) {
MEM_InitGlobalInst();
if (!MEM_Game.portalman) {
return 0;
};
var oCPortalRoomManager pman; pman = _^(MEM_Game.portalman);
if (index < 0) || (index >= pman.portals_numInArray) {
MEM_Error(ConcatStrings("GetPortalNameByIndex: Index is not in valid bounds: ", IntToString(index)));
return 0;
};
return MEM_ReadIntArray(pman.portals_array, index);
};
/*
* Obtain the portal room name from portal index
*/
func string GetPortalNameByIndex(var int index) {
var int portalPtr; portalPtr = GetPortalByIndex(index);
if (portalPtr) {
var oCPortalRoom portal; portal = _^(portalPtr);
return portal.portalName;
} else {
return "";
};
};
/*
* Obtain the index of the portal room of a valid zVob
*/
func int Vob_GetPortalIndex(var int vobPtr) {
MEM_InitGlobalInst();
if (!vobPtr) || (!MEM_Game.portalman) {
return -1;
};
// Get sector name
const int zCVob__GetSectorNameVobIsIn_G1 = 6117008; //0x5D5690
const int zCVob__GetSectorNameVobIsIn_G2 = 6294240; //0x600AE0
const int call = 0;
if (CALL_Begin(call)) {
CALL_PutRetValTo(_@(sectorNamePtr));
CALL__thiscall(_@(vobPtr), MEMINT_SwitchG1G2(zCVob__GetSectorNameVobIsIn_G1, zCVob__GetSectorNameVobIsIn_G2));
call = CALL_End();
};
var int sectorNamePtr;
if (!sectorNamePtr) {
return -1;
};
// Get portal index
const int oCPortalRoomManager__GetPortalRoomIndex_G1 = 7123424; //0x6CB1E0
const int oCPortalRoomManager__GetPortalRoomIndex_G2 = 7810016; //0x772BE0
const int call2 = 0;
if (CALL_Begin(call2)) {
CALL_PtrParam(_@(sectorNamePtr));
CALL_PutRetValTo(_@(index));
CALL__thiscall(_@(MEM_Game.portalman), MEMINT_SwitchG1G2(oCPortalRoomManager__GetPortalRoomIndex_G1,
oCPortalRoomManager__GetPortalRoomIndex_G2));
call2 = CALL_End();
};
var int index;
return +index;
};
/*
* Equivalent function to Npc_GetPortalGuild for items
*/
func int Item_GetPortalGuild(var C_Item itm) {
if (!Hlp_IsValidItem(itm)) {
return -1;
};
// Get portal index
var int index; index = Vob_GetPortalIndex(_@(itm));
if (index == -1) {
return -1;
};
// Obtain portal room
var oCPortalRoom portal; portal = _^(GetPortalByIndex(index));
// Return guild
return +portal.ownerGuild;
};
/*
* Equivalent function to Npc_GetPortalOwner for items
*/
func C_Npc Item_GetPortalOwner(var C_Item itm) {
if (!Hlp_IsValidItem(itm)) {
MEM_NullToInst();
return;
};
// Get portal index
var int index; index = Vob_GetPortalIndex(_@(itm));
if (index == -1) {
MEM_NullToInst();
return;
};
// Obtain portal room
var oCPortalRoom portal; portal = _^(GetPortalByIndex(index));
// Obtain owner
// Checking for validity is pointless, because we are returning an instance:
// Hlp_IsValidNpc has to be called on the return value anyway
Hlp_GetNpc(MEM_GetSymbolIndex(portal.ownerNpc));
};
/*
* Fix portal room assignments. Gothic does not update existing assignments, resulting in duplicates that are exclusive.
* This fix updates Wld_AssignRoomToNpcFix and Wld_AssignRoomToGuildFix to update existing assignments instead creating
* new ones.
*/
func void Wld_AssignRoomFix_Init() {
const int Wld_AssignRoomToNpc_portalman_G1 = 6635274; //0x653F0A
const int Wld_AssignRoomToNpc_portalman_G2 = 7227772; //0x6E497C
const int Wld_AssignRoomToGuild_portalman_G1 = 6635537; //0x654011
const int Wld_AssignRoomToGuild_portalman_G2 = 7227985; //0x6E4A51
MEM_InitAll();
var int addr; addr = MEMINT_SwitchG1G2(Wld_AssignRoomToNpc_portalman_G1, Wld_AssignRoomToNpc_portalman_G2);
if (!IsHooked(addr)) {
MemoryProtectionOverride(addr, 5);
MEM_WriteInt(addr, -1869574000); //0x90909090
MEM_WriteByte(addr+4, ASMINT_OP_nop);
HookEngineF(addr, 5, _Wld_AssignRoomToNpcFix);
};
addr = MEMINT_SwitchG1G2(Wld_AssignRoomToGuild_portalman_G1, Wld_AssignRoomToGuild_portalman_G2);
if (!IsHooked(addr)) {
MemoryProtectionOverride(addr, 5);
MEM_WriteInt(addr, -1869574000); //0x90909090
MEM_WriteByte(addr+4, ASMINT_OP_nop);
HookEngineF(addr, 5, _Wld_AssignRoomToGuildFix);
};
};
func void _Wld_AssignRoomToNpcFix() {
MEM_InitGlobalInst();
var string portalName; portalName = MEM_ReadString(ESP+12);
var int index; index = GetPortalIndexByName(portalName);
if (index >= 0) {
var oCPortalRoomManager pman; pman = _^(MEM_Game.portalman);
var oCPortalRoom portal; portal = _^(MEM_ReadIntArray(pman.portals_array, index));
var oCNpc npc; npc = _^(ESI);
portal.ownerNpc = MEM_ReadString(MEM_GetSymbolByIndex(npc.instanz));
EAX = 0; // Exit Wld_AssignRoomToNpc prematurely
} else {
EAX = MEM_Game.portalman;
};
};
func void _Wld_AssignRoomToGuildFix() {
MEM_InitGlobalInst();
var string portalName; portalName = MEM_ReadString(ESP+16);
var int index; index = GetPortalIndexByName(portalName);
if (index >= 0) {
var oCPortalRoomManager pman; pman = _^(MEM_Game.portalman);
var oCPortalRoom portal; portal = _^(MEM_ReadIntArray(pman.portals_array, index));
portal.ownerGuild = MEM_ReadInt(ESP+12);
EAX = 0; // Exit Wld_AssignRoomToGuild prematurely
} else {
EAX = MEM_Game.portalman;
};
};
Die Funktion zum Austauschen von Waffen-Visuals updated für Gothic 1.
Original für Gothic 2: https://forum.worldofplayers.de/forum/threads/969446-Skriptpaket-Ikarus-3?p=16944337&viewfull=1#post16944337
Code für Gothic 1:
func void HandleDynamicWeaponVisual(var int itemEquipped, var int itemReadied) {
var oCItem weap;
var int ReturnWert;
if (Npc_HasReadiedMeleeWeapon(hero) || Npc_HasReadiedRangedWeapon(hero)) {
weap = Npc_GetReadiedWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemEquipped) {
//remove from slot
oCNpc_RemoveFromSlot(hero, "ZS_RIGHTHAND", ReturnWert, 0);
//exchange items
Npc_RemoveInvItem(hero, itemEquipped);
CreateInvItem(hero, itemReadied);
Npc_GetInvItem(hero,itemReadied); //Befüllt globale var item
//put in Slot
if (weap.flags & ITEM_BOW) {
ReturnWert=oCNpc_PutInSlot(hero, "ZS_LEFTHAND", _@(item), 1);
} else {
ReturnWert=oCNpc_PutInSlot(hero, "ZS_RIGHTHAND", _@(item), 1);
};
};
} else {
var int doIt; doIt = false;
weap = Npc_GetEquippedMeleeWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemReadied) { doIt = true; };
weap = Npc_GetEquippedRangedWeapon(hero);
if (Hlp_GetInstanceID(weap) == itemReadied) { doIt = true; };
if (doIt) {
CreateInvItem(hero, itemEquipped);
EquipWeapon(hero, itemEquipped);
Npc_RemoveInvItem(hero, itemReadied);
};
};
};
Cryp18Struct
08.01.2021, 02:02
Die g1 Adressen für oCNpc__PutInSlot/oCNpc__RemoveFromSlot sind bei LeGo mit drin:
https://github.com/Lehona/LeGo/blob/dev/EngineAdr_G1.d#L41
Danke Cryp. Der Code wurde aktualisiert und ist nun 100% mit G1 kompatibel.
In Gothic 1 kommt es bei Mobsi-Dialogen zu Aufhängern, wenn man per AI_UseMob die Benutzung beenden möchte. Dieser Code vom User F a w k e s schafft Abhilfe.
Die Funktion sollte VOR AI_StopProcessInfos aufgerufen werden, da es ansonsten zu Fehlern kommen kann:
func void oCMobInter_SendStateChange (var int mobPtr, var int fromState, var int toState){
//0067D8C0 .text Debug data ?SendStateChange@oCMobInter@@IAEXHH@Z
const int oCMobInter__SendStateChange_G1 = 6805696;
//0x0071ED90 public: void __thiscall oCMobInter::SendStateChange(int,int)
const int oCMobInter__SendStateChange_G2 = 7466384;
if (!mobPtr) { return; };
CALL_IntParam (toState);
CALL_IntParam (fromState);
CALL__thiscall (mobPtr, MEMINT_SwitchG1G2 (oCMobInter__SendStateChange_G1, oCMobInter__SendStateChange_G2));
};
Anwendungsbeispiel (Es wird vom Zustand S1 zu Zustand Stand gewechselt):
func void PC_SCHMIEDEN_TEST_Info(){
var oCNpc slf; slf = Hlp_GetNPC (self);
var int mobPtr; mobPtr = slf.interactMob;
oCMobInter_SendStateChange (mobPtr, 1, 0);
AI_StopProcessInfos (self);
self.aivar[AIV_INVINCIBLE]=FALSE;
};
mud-freak
22.01.2021, 17:42
Ich habe mit der Idee von Bisasam hier ein Skript geschrieben, das Rüstungsteile für jegliche Modell-Bones ermöglicht.
Dabei wird die Limitierung der C_Item.wear Werte ausgehebelt. Dort gab es bisher nur WEAR_TORSO und WEAR_HEAD. Nun werden weitere Bones unterstützt und die Rüstungsteile sind tatsächlich komplett von der Engine verwaltet. Es handelt sich dabei auch wirklich um Rüstungs-Items - es ist also in dem Sinne keine hackige Lösung.
Das heißt auch, dass Rüstungsteile wie erwartet über Laden und Speichern angelegt bleiben, dass NPCs diese auch nutzen können und dass auch Rüstungswerte wie erwartet funktionieren.
Man braucht dazu nur das Skript in der InitPerceptions ausführen und danach werden neben den existierenden Konstanten für C_Item.wear weitere unterstützt, wie z.B. WEAR_R_UPPERARM. Ein Rüstungsteil-Item ist also sehr einfach zu erstellen.
instance ItAr_StoneArmor_RightShoulder(C_Item)
{
name = "Stone Shoulder Plate (right)";
// ...
mainflag = ITEM_KAT_ARMOR;
wear = WEAR_R_UPPERARM;
// ...
};
Ist eine gültige ASC-Datei im Feld C_Item.visual_change angegeben, wird diese mit anderen kombiniert (rechte Bilder), andernfalls lässt sich ohne eine Angabe auch das 3DS-Mesh direkt an den Bone hängen (linkes Bild).
https://upload.worldofplayers.de/files12/runnenmannboi.png https://upload.worldofplayers.de/files12/HMvMvmannone.png https://upload.worldofplayers.de/files12/JgthGw4vHWrmanntwo.png
armorParts.d – This script lifts the restriction of armor-wear slots, such that any bone is natively supported as armor and maintained by the engine.
/*
* armorParts.d
* Source: https://forum.worldofplayers.de/forum/threads/?p=26666199
*
* Natively support individual armor parts attached to any bone.
*
* - Requires Ikarus, LeGo (HookEngine)
* - Compatible with Gothic 1 and Gothic 2
*
* Instructions
* - Initialize from InitPerceptions with
* ArmorParts_Init();
* - Extend the constants below to add custom bones
*
* Notes
* - The armor parts work natively, like e.g. helmets, completely handled by the engine.
* - Using WEAR_HEAD2 replaces the head visual (NOT restored on unequip), so it seems useless.
* - The ZS_* nodes are excluded here, because they would interfere with equipping of weapons.
*/
//Internal
// const int WEAR_NONE = 0;
// const int WEAR_TORSO = 1;
// const int WEAR_HEAD = 2;
// const int WEAR_EFFECT = 16;
const int WEAR_BIP01 = 1 << 8; // Continue from 256 to not interfere with any internal slots
const int WEAR_PELVIS = 2 << 8;
const int WEAR_SPINE = 3 << 8;
const int WEAR_SPINE1 = 4 << 8;
const int WEAR_SPINE2 = 5 << 8;
const int WEAR_NECK = 6 << 8;
const int WEAR_HEAD2 = 7 << 8; // Replaces head visual
const int WEAR_L_CLAVICLE = 8 << 8;
const int WEAR_L_UPPERARM = 9 << 8;
const int WEAR_L_FOREARM = 10 << 8;
const int WEAR_L_HAND = 11 << 8;
const int WEAR_L_FINGER = 12 << 8;
const int WEAR_R_CLAVICLE = 13 << 8;
const int WEAR_R_UPPERARM = 14 << 8;
const int WEAR_R_FOREARM = 15 << 8;
const int WEAR_R_HAND = 16 << 8;
const int WEAR_R_FINGER = 17 << 8;
const int WEAR_L_THIGH = 18 << 8;
const int WEAR_L_CALF = 19 << 8;
const int WEAR_L_FOOT = 20 << 8;
const int WEAR_L_TOE = 21 << 8;
const int WEAR_R_THIGH = 22 << 8;
const int WEAR_R_CALF = 23 << 8;
const int WEAR_R_FOOT = 24 << 8;
const int WEAR_R_TOE = 25 << 8;
const int WEAR_SLOTS_MAX = 26;
const string ARMOR_SLOTS[WEAR_SLOTS_MAX] = {
"", // Ignored
"BIP01",
"BIP01 PELVIS",
"BIP01 SPINE",
"BIP01 SPINE1",
"BIP01 SPINE2",
"BIP01 NECK",
"BIP01 HEAD",
"BIP01 L CLAVICLE",
"BIP01 L UPPERARM",
"BIP01 L FOREARM",
"BIP01 L HAND",
"BIP01 L FINGER0",
"BIP01 R CLAVICLE",
"BIP01 R UPPERARM",
"BIP01 R FOREARM",
"BIP01 R HAND",
"BIP01 R FINGER0",
"BIP01 L THIGH",
"BIP01 L CALF",
"BIP01 L FOOT",
"BIP01 L TOE0",
"BIP01 R THIGH",
"BIP01 R CALF",
"BIP01 R FOOT",
"BIP01 R TOE0"
};
/*
* This function is to be called from InitPerceptions.
*/
func void ArmorParts_Init() {
MEM_InitAll();
const int oCNpc__EquipArmor_slotInvalid_G1 = 6910412; //0x6971CC
const int oCNpc__EquipArmor_slotInvalid_G2 = 7579129; //0x73A5F9
const int oCNpc__EquipItem_slotInvalid_G1 = 6879870; //0x68FA7E
const int oCNpc__EquipItem_slotInvalid_G2 = 7546246; //0x732586
const int oCNpc__UnequipItem_slotInvalid_G1 = 6880510; //0x68FCFE
const int oCNpc__UnequipItem_slotInvalid_G2 = 7546867; //0x7327F3
const int oCNpc__PutInSlot_slotCheck_G1 = 6970478; //0x6A5C6E
const int oCNpc__PutInSlot_slotCheck_G2 = 7643133; //0x749FFD
const int oCNpc__RemoveFromSlot_slotCheck_G1 = 6971299; //0x6A5FA3
const int oCNpc__RemoveFromSlot_slotCheck_G2 = 7644061; //0x74A39D
const int oCNpc__RemoveFromSlot_getArmor_G1 = 6971327; //0x6A5FBF
const int oCNpc__RemoveFromSlot_getArmor_G2 = 7644089; //0x74A3B9
const int oCNpc__InitModel_setHead_G1 = 6902287; //0x69520F
const int oCNpc__InitModel_setHead_G2 = 7571055; //0x73866F
const int oCNpc__UnShrink_slotCheck_G1 = 6859728; //0x68ABD0
const int oCNpc__UnShrink_slotCheck_G2 = 7524585; //0x72D0E9
const int oCNpc__Unarchive_slotCheck_G1 = 6963124; //0x6A3FB4
const int oCNpc__Unarchive_slotCheck_G2 = 7635629; //0x7482AD
var int armor; armor = MEMINT_SwitchG1G2(oCNpc__EquipArmor_slotInvalid_G1, oCNpc__EquipArmor_slotInvalid_G2);
var int equip; equip = MEMINT_SwitchG1G2(oCNpc__EquipItem_slotInvalid_G1, oCNpc__EquipItem_slotInvalid_G2);
var int ueqip; ueqip = MEMINT_SwitchG1G2(oCNpc__UnequipItem_slotInvalid_G1, oCNpc__UnequipItem_slotInvalid_G2);
var int putChk; putChk = MEMINT_SwitchG1G2(oCNpc__PutInSlot_slotCheck_G1, oCNpc__PutInSlot_slotCheck_G2);
var int remChk; remChk = MEMINT_SwitchG1G2(oCNpc__RemoveFromSlot_slotCheck_G1, oCNpc__RemoveFromSlot_slotCheck_G2);
var int getArm; getArm = MEMINT_SwitchG1G2(oCNpc__RemoveFromSlot_getArmor_G1, oCNpc__RemoveFromSlot_getArmor_G2);
var int setHead; setHead = MEMINT_SwitchG1G2(oCNpc__InitModel_setHead_G1, oCNpc__InitModel_setHead_G2);
var int unshrnk; unshrnk = MEMINT_SwitchG1G2(oCNpc__UnShrink_slotCheck_G1, oCNpc__UnShrink_slotCheck_G2);
var int unarchv; unarchv = MEMINT_SwitchG1G2(oCNpc__Unarchive_slotCheck_G1, oCNpc__Unarchive_slotCheck_G2);
// Extensive safety checks, in case some idiot makes a Union plugin out of this
if (MEM_ReadInt(armor) == /*6A 00 8D 54*/ 1418526826)
&& (MEM_ReadInt(equip) == MEMINT_SwitchG1G2(/*83 FE 01 75*/1963064963, /*A8 01 74 28*/ 678691240))
&& (MEM_ReadInt(ueqip) == MEMINT_SwitchG1G2(/*83 FE 01 75*/1963064963, /*A8 01 74 25*/ 628359592))
&& (MEM_ReadInt(putChk) == /*52 8B 51 08*/ 139561810)
&& (MEM_ReadInt(remChk) == MEMINT_SwitchG1G2(/*68 38 B5 8D*/2377463912, /*68 A4 1F AB*/-1423989656))
&& (MEM_ReadInt(getArm) == MEMINT_SwitchG1G2(/*E8 2C 95 FE*/ -23778072, /*E8 D2 7B FE*/ -25439512))
&& (MEM_ReadInt(setHead) == MEMINT_SwitchG1G2(/*E8 7C FA FF*/ -361240, /*E8 7C FA FF*/ -361240))
&& (MEM_ReadInt(unshrnk) == MEMINT_SwitchG1G2(/*50 8B 41 08*/ 138513232, /*68 A4 1F AB*/-1423989656))
&& (MEM_ReadInt(unarchv) == /*8B CB FF 12*/318753675) {
const int nop8times[2] = {-1869574000, -1869574000};
const int addESP4movEAXEDI[2] = {-1996176253, 248};
// Equipping
MemoryProtectionOverride(equip, 5);
MemoryProtectionOverride(ueqip, 5);
MEM_CopyBytes(_@(nop8times), equip, MEMINT_SwitchG1G2(5, 4));
MEM_CopyBytes(_@(nop8times), ueqip, MEMINT_SwitchG1G2(5, 4));
HookEngineF(armor, 6, ArmorParts_EquipArmor);
HookEngineF(equip, MEMINT_SwitchG1G2(5, 6), ArmorParts_EquipItem);
HookEngineF(ueqip, MEMINT_SwitchG1G2(5, 6), ArmorParts_UnequipItem);
// Apply ASC to model
MemoryProtectionOverride(remChk+5, 3);
MemoryProtectionOverride(getArm, 5);
MemoryProtectionOverride(setHead, 5);
MemoryProtectionOverride(unshrnk+5, 3);
MEM_CopyBytes(_@(nop8times), setHead, 5);
MEM_CopyBytes(_@(nop8times), remChk+5, 3);
MEM_CopyBytes(_@(addESP4movEAXEDI), getArm, 5);
MEM_CopyBytes(_@(nop8times), unshrnk+5, MEMINT_SwitchG1G2(0, 3));
HookEngineF(putChk, 5, ArmorParts_AscPutSlot);
HookEngineF(remChk, 5, ArmorParts_AscRemoveSlot);
HookEngineF(setHead, 5, ArmorParts_AscInitModel);
HookEngineF(unshrnk, 5, ArmorParts_AscUnShrink);
HookEngineF(unarchv, 6, ArmorParts_AscUnarchive);
};
};
/*
* Check if NPC has slot and create it if necessary
*/
func void ArmorParts_CreateSlotIfNotExist(var int npcPtr, var string slot) {
if (!Hlp_Is_oCNpc(npcPtr)) {
return;
};
const int oCNpc__CreateInvSlot_G1 = 6968448; //0x6A5480
const int oCNpc__CreateInvSlot_G2 = 7641088; //0x749800
const int oCNpc__IsInvSlotAvailable_G1 = 6969456; //0x6A5870
const int oCNpc__IsInvSlotAvailable_G2 = 7642080; //0x749BE0
var int strPtr; strPtr = _@s(slot);
// Create slot if it does not exist
const int call = 0;
if (CALL_Begin(call)) {
CALL_PtrParam(_@(strPtr));
CALL__thiscall(_@(npcPtr), MEMINT_SwitchG1G2(oCNpc__IsInvSlotAvailable_G1, oCNpc__IsInvSlotAvailable_G2));
ASM_2(49285); // test eax, eax
ASM_2(372); // jz 1
ASM_1(ASMINT_OP_retn); // if (EAX != 0): return
CALL_PtrParam(_@(strPtr));
CALL__thiscall(_@(npcPtr), MEMINT_SwitchG1G2(oCNpc__CreateInvSlot_G1, oCNpc__CreateInvSlot_G2));
call = CALL_End();
};
};
/*
* Extend check of C_Item.wear in oCNpc::EquipArmor
*/
func void ArmorParts_EquipArmor() {
var string slot;
const int oCNpc__EquipArmor_slotInvalidMsg_G1 = 6910426; //0x6971DA
const int oCNpc__EquipArmor_slotInvalidMsg_G2 = 7579143; //0x73A607
var int addr; addr = MEMINT_SwitchG1G2(oCNpc__EquipArmor_slotInvalidMsg_G1, oCNpc__EquipArmor_slotInvalidMsg_G2);
const int orig = 608472400; // 50 8D 44 24
const int newb = 0;
if (!newb) {
MemoryProtectionOverride(addr, 12);
ASM_Open(12+1);
ASM_4(7012202); // 6A FF 6A 00 // push FF, push 0
ASM_1(ASMINT_OP_nop);
ASM_1(ASMINT_OP_pushIm);
ASM_4(_@s(slot)+4);
ASM_2(MEMINT_SwitchG1G2(29163, 56299)); // G1: jmp 0x697257, G2: jmp 0x73A5EE
newb = ASM_Close();
};
// Extend slot check
var int valid; valid = FALSE;
if (EAX != 1) {
if (Hlp_Is_oCItem(EBP)) {
var C_Item itm; itm = _^(EBP);
var int wear; wear = itm.wear >> 8;
if (wear > 0) && (wear < WEAR_SLOTS_MAX) {
slot = MEM_ReadStatStringArr(ARMOR_SLOTS, wear);
ArmorParts_CreateSlotIfNotExist(EDI, slot);
valid = TRUE;
};
};
};
// Apply new slot
if (valid) {
if (MEM_ReadInt(addr) == orig) {
MEM_SwapBytes(addr, newb, 12);
};
} else {
if (MEM_CompareBytes(addr, newb, 12)) {
MEM_SwapBytes(addr, newb, 12);
};
};
};
/*
* Extend checks of C_Item.wear in oCNpc::EquipItem and oCNpc::UnequipItem
*/
func void ArmorParts_EquipItemExt(var int addr, var int itmPtr, var int npcPtr) {
var string slot;
const int oCNpc_EquipItem_slotStr_G1 = 6879880;//0x68FA88
const int oCNpc_EquipItem_slotStr_G2 = 7546255;//0x73258F
const int oCNpc_UnequipItem_slotStr_G1 = 6880520;//0x68FD08
const int oCNpc_UnequipItem_slotStr_G2 = 7546875;//0x7327FB
const int orig = 0; orig = MEMINT_SwitchG1G2(/*38 B5 8D 00*/ 9286968, /*A4 1F AB 00*/ 11214756);
const int newb = 0;
if (!newb) {
MemoryProtectionOverride(MEMINT_SwitchG1G2(oCNpc_EquipItem_slotStr_G1, oCNpc_EquipItem_slotStr_G2), 4);
MemoryProtectionOverride(MEMINT_SwitchG1G2(oCNpc_UnequipItem_slotStr_G1, oCNpc_UnequipItem_slotStr_G2), 4);
newb = _@s(slot)+4;
};
// Extend slot check
slot = "";
if (MEMINT_SwitchG1G2(ESI, EAX) != 1) {
if (Hlp_Is_oCItem(itmPtr)) {
var C_Item itm; itm = _^(itmPtr);
var int wear; wear = itm.wear >> 8;
if (wear > 0) && (wear < WEAR_SLOTS_MAX) {
slot = MEM_ReadStatStringArr(ARMOR_SLOTS, wear);
ArmorParts_CreateSlotIfNotExist(npcPtr, slot);
};
};
};
// Apply new slot
if (MEMINT_SwitchG1G2(ESI, EAX) != 1) {
if (MEM_ReadInt(addr) == orig) {
MEM_WriteInt(addr, newb);
};
} else {
if (MEM_ReadInt(addr) == newb) {
MEM_WriteInt(addr, orig);
};
};
};
func void ArmorParts_EquipItem() {
const int oCNpc_EquipItem_slotStr_G1 = 6879880;//0x68FA88
const int oCNpc_EquipItem_slotStr_G2 = 7546255;//0x73258F
var int itm; itm = MEM_ReadInt(ESP+56);
var int npc; npc = MEM_ReadInt(ESP+16);
ArmorParts_EquipItemExt(MEMINT_SwitchG1G2(oCNpc_EquipItem_slotStr_G1, oCNpc_EquipItem_slotStr_G2), itm, npc);
};
func void ArmorParts_UnequipItem() {
const int oCNpc_UnequipItem_slotStr_G1 = 6880520;//0x68FD08
const int oCNpc_UnequipItem_slotStr_G2 = 7546875;//0x7327FB
var int itm; itm = MEM_ReadInt(ESP+48);
var int npc;
if (GOTHIC_BASE_VERSION == 1) {
npc = MEM_ReadInt(ESP+8); // Evaluate only if Gothic 1!
} else {
npc = EBP;
};
ArmorParts_EquipItemExt(MEMINT_SwitchG1G2(oCNpc_UnequipItem_slotStr_G1, oCNpc_UnequipItem_slotStr_G2), itm, npc);
};
/*
* Pass through Items with C_Item.visual_change properties
*/
func void ArmorParts_AscSlotCheck(var int itmPtr) {
if (Hlp_Is_oCItem(itmPtr)) {
var oCItem itm; itm = _^(itmPtr);
if (!Hlp_StrCmp(itm.visual_change, "")) {
ECX = _@s("ZS_TORSO")+4;
};
};
};
func void ArmorParts_AscPutSlot() {
ArmorParts_AscSlotCheck(MEMINT_SwitchG1G2(EBX, EDI));
};
func void ArmorParts_AscRemoveSlot() {
ECX = ESI+4;
ArmorParts_AscSlotCheck(EDI);
};
/*
* Append ASCs in oCNpc::InitModel
*/
func void ArmorParts_AscInitModel() {
var oCNpc slf; slf = _^(ESI);
var int bodyTexVarNr; bodyTexVarNr = (slf.bitfield[0] & oCNpc_bitfield0_body_TexVarNr) >> 14;
var int bodyTexColorNr; bodyTexColorNr = (slf.bitfield[1] & oCNpc_bitfield1_body_TexColorNr);
// Iterate over all slots and apply mesh lib if applicable
repeat(i, slf.invSlot_numInArray); var int i;
// Obtain next slot item
var int slotPtr; slotPtr = MEM_ReadIntArray(slf.invSlot_array, i);
if (Hlp_StrCmp(MEM_ReadString(slotPtr), "ZS_TORSO")) {
continue;
};
var int itmPtr; itmPtr = MEM_ReadInt(slotPtr+48); // TNpcSlot.vob
if (!Hlp_Is_oCItem(itmPtr)) {
continue;
};
// Check validity of item visual name
var oCItem itm; itm = _^(itmPtr);
if (Hlp_StrCmp(itm.visual_change, "")) {
continue;
};
var int pos; pos = STR_IndexOf(itm.visual_change, ".ASC");
if (pos == -1) {
continue;
};
var string visual; visual = STR_Upper(STR_Prefix(itm.visual_change, pos));
var int skinNr; skinNr = itm.visual_skin;
// Call cascade of engine functions to register the mesh
const int zCModel__ApplyMeshLib_G1 = 5653904; //0x564590
const int zCModel__ApplyMeshLib_G2 = 5760528; //0x57E610
const int zCModel__SetMeshLibTexture_G1 = 5652816; //0x564150
const int zCModel__SetMeshLibTexture_G2 = 5759440; //0x57E1D0
const int call2 = 0;
if (CALL_Begin(call2)) {
const int meshStrPtr = 0; meshStrPtr = _@s(visual);
const int bodyStrPtr = 0; bodyStrPtr = _@s("BODY");
const int armorStrPtr = 0; armorStrPtr = _@s("ARMOR");
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__ApplyMeshLib_G1, zCModel__ApplyMeshLib_G2));
CALL_PtrParam(_@(bodyStrPtr));
CALL_IntParam(_@(bodyTexVarNr));
CALL_PtrParam(_@(FALSE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
CALL_PtrParam(_@(bodyStrPtr));
CALL_IntParam(_@(bodyTexColorNr));
CALL_PtrParam(_@(TRUE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
CALL_PtrParam(_@(armorStrPtr));
CALL_IntParam(_@(skinNr));
CALL_PtrParam(_@(FALSE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
call2 = CALL_End();
};
end;
// Redo the call to oCNpc::Set that was overwritten with this hook
const int oCNpc__SetHead_G1 = 6900880; //0x694C90
const int oCNpc__SetHead_G2 = 7569648; //0x7380F0
const int call3 = 0;
if (CALL_Begin(call3)) {
CALL__thiscall(_@(ECX), MEMINT_SwitchG1G2(oCNpc__SetHead_G1, oCNpc__SetHead_G2));
call3 = CALL_End();
};
};
/*
* Choose to render ASC instead of 3DS in oCNpc::UnShrink
*/
func void ArmorParts_AscUnShrink() {
ECX = EDI+4;
var int itmPtr; itmPtr = MEM_ReadInt(EDI+48);
if (Hlp_Is_oCItem(itmPtr)) {
ArmorParts_AscSlotCheck(itmPtr);
};
};
/*
* Choose to render ASC instead of 3DS in oCNpc::Unarchive (loading NPC)
*/
func void ArmorParts_AscUnarchive() {
if (Hlp_Is_oCItem(EBX)) {
var C_Item itm; itm = _^(EBX);
if (!Hlp_StrCmp(itm.visual_change, "")) && (itm.wear != WEAR_TORSO) {
itm.flags = itm.flags | ITEM_ACTIVE_LEGO;
var int code; code = _@(ASMINT_OP_retn);
EDX = _@(code);
EAX = 0;
};
};
};
neocromicon
22.01.2021, 19:10
@Mud-Freak, auch wenn ich das Feature sicher nie nutzen werde...aber Hammer was du da wieder auf die Beine gestellt hast, ich lasse einfach mal ein Danke dafür stehen ;)
mud-freak
23.01.2021, 12:18
Vielleicht noch ein paar Worte zu modularen Rüstungen.
Die Eigenschaft C_Item.visual_change beachtet Gothic nur für Ganzkörperrüstungen (WEAR_TORSO). Das bedeutet für einzelne Rüstungsteile, dass diese nur 3DS-Meshes sein können. Dementsprechend folgen Rüstungsteile nur der Position und Rotation des angehefteten Bones, nicht aber z.B. des gesamten Arms inkl. Beugung am Ellenbogen. Wolle man nun eine gesamte Rüstung aus Einzelteilen zusammensetzen wären das zig Einzelteile.
ABER man kann heimlich ein weiteres Rüstungsteil z.B. am Unterarm spawnen, wenn der Spieler ein Rüstungsteil am Oberarm anlegt, um ihm vorzugaukeln er hätte ein Rüstungsteil für die gesamte Länge des Arms. Das klappt sehr leicht über die on_equip-Funktion des Rüstungsitems. Hier ein Beispiel vom Käsemann:
func void ItAr_Cheese_L_Upperarm_onequip() {
var int vobPtr; vobPtr = MEM_InsertVob("ItFo_Cheese.3ds", ""); // Presupposes an unnamed vob in the world
var string slot; slot = "BIP01 L FOREARM";
ArmorParts_CreateSlotIfNotExist(_@(self), slot);
oCNpc_PutInSlot(self, slot, vobPtr, FALSE); // FALSE == not in inventory
};
func void ItAr_Cheese_L_Upperarm_onunequip() {
oCNpc_RemoveFromSlot(self, "BIP01 L FOREARM", 0, FALSE); // FALSE == do not drop
MEM_DeleteVob(CALL_RetValAsInt());
};
Damit ließe sich eine Ganzkörperrüstung theoretisch in fünf Teile zerlegen ("Brustpanzer", "linkes Bein", "rechtes Bein", "linker Arm" und "rechter Arm") oder auch einfacher in drei Teile ("Brustpanzer", "Beine" und "Arme") unterteilen. Wenn sich jemand die Arbeit machen will (und vorausgesetzt es sieht vernünftig aus), könnte er von bestehenden Rüstungen aus Gothic die Ärmel und Hosenbeine abschneiden und in einzelne Meshes für jeden Bone zerteilen und dann als fünf- oder drei-teilige Rüstungssets anbieten. Das wäre sehr spannend.
Herobrine
23.01.2021, 13:52
Die Eigenschaft C_Item.visual_change beachtet Gothic nur für Ganzkörperrüstungen (WEAR_TORSO). Das bedeutet für einzelne Rüstungsteile, dass diese nur 3DS-Meshes sein können. Dementsprechend folgen Rüstungsteile nur der Position und Rotation des angehefteten Bones, nicht aber z.B. des gesamten Arms inkl. Beugung am Ellenbogen. Wolle man nun eine gesamte Rüstung aus Einzelteilen zusammensetzen wären das zig Einzelteile.Kann man visual_change nicht irgendwie auch so ändern, dass man mehrere .asc-Dateien miteinander gleichzeitig tragen kann? Beim Kompilieren neuer Rüstungen habe ich auch oft das Problem, dass die neue Rüstung mit der alten Rüstung bei NPCs oder dem Spieler überlappen. Wenn man sich das irgendwie zunutze machen kann, z.B. für Knieplatten oder dem Drachenjägerhelm, die/der an verschiedene Vertices von Bones gebunden sind, wäre das wirklich ideal. Beim Drachenjägerhelm ist es ja so, dass man den Kopf dreht und der obere Teil des Helms an den Kopf gebunden ist und der untere Teil des Helms ein wenig an den Oberkörper. Das sieht dann, wenn man sich dreht, besser aus als wäre der Helm nur steiff auf dem Kopf. Das kriegt man mit einer .3ds des Drachenjägerhelms allerdings nicht hin. Ich wüsste jedenfalls nicht, wie man es anständig mit mehreren .3ds-Dateien, die nicht über mehrere Bones benden können, hinkriegen könnte, dass das im Spiel auch einigermaßen vernünftig aussieht.
mud-freak
23.01.2021, 14:34
Dazu fehlt mir das nötige Wissen zum Modellen, Bones, usw. Mit ein paar Änderungen in der Engine, lassen sich mehrere ASCs kombinieren:
https://upload.worldofplayers.de/files12/weirdo.png
Allerdings sind hier natürlich unschöne Überlappungen sichtbar auch bei den Händen. Wenn mir jemand zwei ASCs zur Verfügung stellen könnte die nicht überlappen, z.B. eins das Bein, eins ein Arm, dann kann ich da gern noch etwas weiter probieren.
Herobrine
23.01.2021, 16:20
Allerdings sind hier natürlich unschöne Überlappungen sichtbar auch bei den Händen. Wenn mir jemand zwei ASCs zur Verfügung stellen könnte die nicht überlappen, z.B. eins das Bein, eins ein Arm, dann kann ich da gern noch etwas weiter probieren.Ich habe für Gothic 2 mal die Schattenrüstung in drei Teile gesplittet und so angepasst, dass kein Clipping entstehen sollte:
https://upload.worldofplayers.de/files12/ArmorDiego.png
Und die Drachenjägerrüstung in einmal nur Helm und einmal nur Rüstung:
https://upload.worldofplayers.de/files12/Armor_Djg_H.png
Link: Ruestungen.7z (https://upload.worldofplayers.de/files12/Ruestungen.7z)
mud-freak
23.01.2021, 17:48
Danke dir! Damit funktioniert das gut. Der Helm der Drachenjägerrüstung sieht so bei Kopfbewegungen ganz gut aus. Ich werde mir mal anschauen, wie knifflig es wird, das für Rüstungsteile ein zu bauen.
That can be easily done via unused WEAR flags (see code). Doesn't matter what numbers used for Armor, but it's needed to avoid wearing two objects of the same WEAR flag.
https://youtu.be/sMw8_HXVFNM
I guess some armor has several non-removable elements. For example you can't remove bracers or boots from Paladin armor. Therefore if you equip Paladin armor - active boots, bracers, shoulder and ect should be removed.
const int WEAR_ARMS = 16;
const int WEAR_LEGS = 32;
const int WEAR_HOOD = 64;
const int WEAR_SLD = 128;
...
instance ITAR_Pal_H(C_Item)
{
wear = WEAR_TORSO | WEAR_ARMS | WEAR_LEGS | WEAR_SLD;
...
instance ITAR_PirateGloves(C_Item)
{
name = "Pirate gloves";
wear = WEAR_ARMS;
...
instance ITAR_PirateBoots(C_Item)
{
name = "Pirate boots";
wear = WEAR_LEGS;
...
instance ITAR_PirateHat(C_Item)
{
name = "Pirate hat";
wear = WEAR_HOOD;
...
This can be seen in the picture.
Pirate gloves - ARMS
Pirate boots - LEGS
Pirate hat - HOOD
Paladin armor - TORSO, ARMS, LEGS and SLD
Equip result:
https://cdn.discordapp.com/attachments/692975667985252422/802650418005934130/Picture.jpg
Herobrine
23.01.2021, 22:23
That can be easily done via unused WEAR flags (see code). Doesn't matter what numbers used for Armor, but it's needed to avoid wearing two objects of the same WEAR flag.
https://youtu.be/sMw8_HXVFNM
I guess some armor has several non-removable elements. For example you can't remove bracers or boots from Paladin armor. Therefore if you equip Paladin armor - active boots, bracers, shoulder and ect should be removed.
const int WEAR_ARMS = 16;
const int WEAR_LEGS = 32;
const int WEAR_HOOD = 64;
const int WEAR_SLD = 128;
...
instance ITAR_Pal_H(C_Item)
{
wear = WEAR_TORSO | WEAR_ARMS | WEAR_LEGS | WEAR_SLD;
...
instance ITAR_PirateGloves(C_Item)
{
name = "Pirate gloves";
wear = WEAR_ARMS;
...
instance ITAR_PirateBoots(C_Item)
{
name = "Pirate boots";
wear = WEAR_LEGS;
...
instance ITAR_PirateHat(C_Item)
{
name = "Pirate hat";
wear = WEAR_HOOD;
...
This can be seen in the picture.
Pirate gloves - ARMS
Pirate boots - LEGS
Pirate hat - HOOD
Paladin armor - TORSO, ARMS, LEGS and SLD
Equip result:
https://cdn.discordapp.com/attachments/692975667985252422/802650418005934130/Picture.jpgWhen I try to equip an armor that uses those unused wear-flags, my game tells me that the wear value is invalid. Did you do anything else to make it work or am I doing something wrong? Does it have problems with the mainflag ITEM_KAT_ARMOR?
mud-freak
23.01.2021, 22:48
When I try to equip an armor that uses those unused wear-flags, my game tells me that the wear value is invalid. Did you do anything else to make it work or am I doing something wrong? Does it have problems with the mainflag ITEM_KAT_ARMOR?
That can be easily done via unused WEAR flags (see code). Doesn't matter what numbers used for Armor, but it's needed to avoid wearing two objects of the same WEAR flag.
Not quite, I think. It may not be divisible by 2 or 4, to be handled as a WEAR_TORSO but not as WEAR_HEAD. That leaves numbers like 5, 9, 13, 17, 21, 25, ...
Still this should not work without changes, as the "Mesh Libs" are cleared every time an armor is equipped/unequipped. I wrote a script to change that, I can post it in a little bit.
EDIT: Ich habe mein Skript oben aktualisiert. Nun können damit auch mehrere ASC-Rüstungen gleichzeitig verwendet werden. Die Vorteile bei dieser Implementierung (falls es wohl noch andere gibt):
Alles bleibt erst einmal unverändert, bisherige Ganzkörperrüstungen funktionieren nach wie vor.
Es werden sowohl ASC als auch 3DS Meshes unterstützt. Befindet sich kein gültiger Dateiname im Feld C_Item.visual_change, wird das 3DS-Visual des Items verwendet und statisch an den Bone angeheftet.
Eine herkömmliche Ganzkörperrüstung (WEAR_TORSO) ist mit Teilstücken kompatibel. Es wird zu erst die ASC der Ganzkörperrüstung angewandt und anschließend mögliche weitere. So kann man Rüstungsteile mit oder auch ohne Ganzkörperrüstung tragen.
Vielleicht geht es ja auch ganz ohne das Skript. Das bezweifle ich aber.
Herobrine
24.01.2021, 00:46
EDIT: Ich habe mein Skript oben aktualisiert. Nun können damit auch mehrere ASC-Rüstungen gleichzeitig verwendet werden. Die Vorteile bei dieser Implementierung (falls es wohl noch andere gibt):
Alles bleibt erst einmal unverändert, bisherige Ganzkörperrüstungen funktionieren nach wie vor.
Es werden sowohl ASC als auch 3DS Meshes unterstützt. Befindet sich kein gültiger Dateiname im Feld C_Item.visual_change, wird das 3DS-Visual des Items verwendet und statisch an den Bone angeheftet.
Eine herkömmliche Ganzkörperrüstung (WEAR_TORSO) ist mit Teilstücken kompatibel. Es wird zu erst die ASC der Ganzkörperrüstung angewandt und anschließend mögliche weitere. So kann man Rüstungsteile mit oder auch ohne Ganzkörperrüstung tragen.
Vielleicht geht es ja auch ganz ohne das Skript. Das bezweifle ich aber.Großartig! Danke für die Arbeit. Habe lange auf sowas gewartet. Klappt alles ingame mit dem Skript, genauso wie ich es mir vorgestellt habe. :D
mud-freak
24.01.2021, 00:54
Großartig! Danke für die Arbeit. Habe lange auf sowas gewartet. Klappt alles ingame mit dem Skript, genauso wie ich es mir vorgestellt habe. :D
Sehr cool! Danke fürs Ausprobieren und das schnelle Feedback. Freue mich schon drauf, das mal irgendwo in Action zu sehen.
Herobrine
24.01.2021, 13:26
Noch eine Sache zum armorParts.d Skript: Wie füge ich neue Wear-Konstanten hinzu, die nicht von irgendwelchen Bones abhängig sind? Ich hatte bis jetzt zum Testen die vorhandenen Wear-Consts benutzt wie z.B. WEAR_R_HAND, aber dann habe ich sowohl die .asc nach dem Speichern und Laden an als auch die .3ds der Rüstung am rechten Hand-Bone hängen. Bei anderen Wear-Const-Werten wie z.B. 18 hängt die .3ds des Objekts auch am Head-Bone nach Speichern und Laden.
mud-freak
24.01.2021, 14:41
Cool, dass du dich direkt so ausgiebig damit beschäftigst. Da habe ich wohl einen Check übersehen, der beim Laden aufgerufen wird. Ich werde mir das ansehen. Bis dahin könnte es klappen, wenn du dir einen neuen Slot definierst.
Füge dazu eine neue Konstante vor WEAR_SLOTS_MAX nach dem selben Schema hinzu und erhöhe WEAR_SLOTS_MAX um eins. In dem String-Array darunter fügst du eine neue Zeile hinzu mit einem beliebigen Slotnamen. Wenn du einen wählst, der keinem Bone entspricht, sollte kein 3ds-Mesh gerendert werden (hoffe ich). Dabei das Komma in der überliegenden Zeile nicht vergessen.
Herobrine
24.01.2021, 14:57
Cool, dass du dich direkt so ausgiebig damit beschäftigst. Da habe ich wohl einen Check übersehen, der beim Laden aufgerufen wird. Ich werde mir das ansehen. Bis dahin könnte es klappen, wenn du dir einen neuen Slot definierst.
Füge dazu eine neue Konstante vor WEAR_SLOTS_MAX nach dem selben Schema hinzu und erhöhe WEAR_SLOTS_MAX um eins. In dem String-Array darunter fügst du eine neue Zeile hinzu mit einem beliebigen Slotnamen. Wenn du einen wählst, der keinem Bone entspricht, sollte kein 3ds-Mesh gerendert werden (hoffe ich). Dabei das Komma in der überliegenden Zeile nicht vergessen.Ich hatte bereits versucht neue Slotnamen beim String-Array hinzuzufügen, aber hatte die Strings leer gelassen und mit einem leeren String hat es nicht funktioniert. Habe aber nicht daran gedacht, die Strings so zu benennen, dass sie keinem Bone entsprechen. Ich sollte vielleicht mehr experimentieren. :D
Mit Slotnamen, die keinem Bone entsprechen, funktioniert es mit dem Speichern und Laden. Da wird kein 3ds-Mesh mehr gerendert. Danke.
mud-freak
24.01.2021, 16:51
Ich konnte es nun beheben, dass die 3DS (in Slots mit Bonenamne) nach dem Laden nicht plötzlich auftauchen, wenn sie vorher nicht erwünscht waren. Vielleicht schaffe ich es heute Abend noch, das Skript oben zu aktualisieren. Durch die Änderung wird es allerdings nötig sein, dass Skript bereits aus der InitPerceptions-Funktions aufzurufen, damit die Hooks bereits vor dem Laden registriert sind.
Herobrine
24.01.2021, 17:44
Leider noch etwas: Wenn die Textur der .asc eines Rüstungsteils eine Body-Textur enthält, kriegt der ganze Körper die Textur Hum_Body_Naked_V12_C0.tga, wobei V12 standardmäßig die letzte Body-Textur in Gothic 2 DNDR ist, also wahrscheinlich wird dem ganzen Körper dann immer die letzte Body-Textur zugewiesen, die er finden kann.
https://upload.worldofplayers.de/files12/pZENFlEErKi0TntYqBug.png
Sieht eigentlich ganz witzig aus, ist aber nicht so wirklich das, was ich haben will. :D
EDIT: Gibt es eigentlich auch eine Möglichkeit, Rüstungen oder generell jegliche Items im Inventar zu verstecken? Wenn man einen Gegner looten will, werden ja auch nicht die angelegte Rüstung angezeigt, obwohl der NPC sie im Inventar hat. Ich habe den Oberkörper und den Unterkörper des Hum_Body_Naked0 jeweils in zwei .ascs gesplittet und dem Helden als zwei Rüstungsteile gegeben und equippen lassen. Zusätzlich dazu habe ich die oCNpc::UnequipItem gehookt und dort abgefragt, ob es sich beim item.wear-flag des Items, was man ablegen will, um ein UpperBody- oder LowerBody-Item handelt. Falls dem so ist, wird beim Unequippen eines UpperBody- oder LowerBody-Rüstungsteil, der UpperBody/LowerBody des Hum_Body_Naked0 angelegt. Soweit funktioniert das. Kann natürlich sein, dass ich dann später Probleme kriegen werde, wenn ich zwischen UpperBody/LowerBody Rüstungsteilen hin und her wechsle und dann plötzlich der Oberkörper/Unterkörper des Hum_Body_Naked0 angelegt wird, aber das lässt sich bestimmt leicht fixen. Aber vielleicht gibt es auch eine bessere Lösung hierfür, die ich nutzen könnte.
Leider noch etwas: Wenn die Textur der .asc eines Rüstungsteils eine Body-Textur enthält, kriegt der ganze Körper die Textur Hum_Body_Naked_V12_C0.tga, wobei V12 standardmäßig die letzte Body-Textur in Gothic 2 DNDR ist, also wahrscheinlich wird dem ganzen Körper dann immer die letzte Body-Textur zugewiesen, die er finden kann.
https://upload.worldofplayers.de/files12/pZENFlEErKi0TntYqBug.png
Sieht eigentlich ganz witzig aus, ist aber nicht so wirklich das, was ich haben will. :D
Manchmal muss man eben den Arsch hinhalten :P
Ne ich hab an sich nichts Sachdienliches hinzuhalten. Da es kein Like button gibt, möchte ich einfach ganz manuell Danke für das coole Script sagen :gratz
mud-freak
25.01.2021, 17:59
Diesmal mit Vorhersage. Ich sollte Meteorologe im Ersten werden.
Ich denke aber, dass spätestens nach diesem Forumthread sicher bald irgendein undurchdachtes Union-Plugin dazu auftaucht (wie unsinnig, denn so ein Feature ist nur in einer zusammenhängenden Mod mit Rüstungen sinnvoll) und es wird sich jemand ganz anderes unbeschämt dafür feiern lassen.
// ...
// Extensive safety checks, in case some idiot makes a Union plugin out of this
if (MEM_ReadInt(armor) == /*6A 00 8D 54*/ 1418526826)
// ...
Tada: https://worldofplayers.ru/threads/42225/
@Herobrine Keine Ahnung, wann (ob) ich die Fehler beheben werde.
Herobrine
26.01.2021, 01:12
@Herobrine Keine Ahnung, wann (ob) ich die Fehler beheben werde.Ich habe jetzt nochmal ein wenig rumprobiert und ein wenig was am Script geändert mit der Hoffnung, dass ich es irgendwie gefixt kriege. Bin denke mit mehr Glück als Verstand auf die Lösung gekommen. Ich habe die Pointer skinNrPtr, bodyTexVarPtr und bodyTexColorPtr durch die Variablen an sich ersetzt beim jeweiligen Callen der zCModel::SetMeshLibTexture-Funktion. Der Pointer zum Pointer hat höchstwahrscheinlich Probleme bereitet. Im Spiel wird bei jedem NPC, egal welche Hautfarbe, jedenfalls jetzt die richtige Body-Textur dargestellt.
// Call cascade of engine functions to register the mesh
const int zCModel__ApplyMeshLib_G1 = 5653904; //0x564590
const int zCModel__ApplyMeshLib_G2 = 5760528; //0x57E610
const int zCModel__SetMeshLibTexture_G1 = 5652816; //0x564150
const int zCModel__SetMeshLibTexture_G2 = 5759440; //0x57E1D0
const int call2 = 0;
if (CALL_Begin(call2)) {
const int meshStrPtr = 0; meshStrPtr = _@s(visual);
//const int skinNrPtr = 0; skinNrPtr = skinNr;
//const int bodyTexVarNrPtr = 0; bodyTexVarNrPtr = bodyTexVarNr;
//const int bodyTexColorNrPtr = 0; bodyTexColorNrPtr = bodyTexColorNr;
const int bodyStrPtr = 0; bodyStrPtr = _@s("BODY");
const int armorStrPtr = 0; armorStrPtr = _@s("ARMOR");
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__ApplyMeshLib_G1, zCModel__ApplyMeshLib_G2));
CALL_PtrParam(_@(bodyStrPtr));
CALL_PtrParam(_@(bodyTexVarNr));
CALL_PtrParam(_@(FALSE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
CALL_PtrParam(_@(bodyStrPtr));
CALL_PtrParam(_@(bodyTexColorNr));
CALL_PtrParam(_@(TRUE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
CALL_PtrParam(_@(armorStrPtr));
CALL_PtrParam(_@(skinNr));
CALL_PtrParam(_@(FALSE));
CALL_PtrParam(_@(meshStrPtr));
CALL__thiscall(_@(EDI), MEMINT_SwitchG1G2(zCModel__SetMeshLibTexture_G1, zCModel__SetMeshLibTexture_G2));
call2 = CALL_End();
};
end;
mud-freak
26.01.2021, 08:28
Ich habe jetzt nochmal ein wenig rumprobiert und ein wenig was am Script geändert mit der Hoffnung, dass ich es irgendwie gefixt kriege. Bin denke mit mehr Glück als Verstand auf die Lösung gekommen. Ich habe die Pointer skinNrPtr, bodyTexVarPtr und bodyTexColorPtr durch die Variablen an sich ersetzt beim jeweiligen Callen der zCModel::SetMeshLibTexture-Funktion. Der Pointer zum Pointer hat höchstwahrscheinlich Probleme bereitet. Im Spiel wird bei jedem NPC, egal welche Hautfarbe, jedenfalls jetzt die richtige Body-Textur dargestellt.
Gute Arbeit. Da hatte ich einen Fehler gemacht. Ich nehme das oben im Skript auf. Den Fix, dass die Rüstungesteile nach dem Laden korrekt dargestellt werden, hatte ich ja auch schon fertig. Den füge ich dann auch haute abend hinzu.
Gibt es eigentlich auch eine Möglichkeit, Rüstungen oder generell jegliche Items im Inventar zu verstecken? Wenn man einen Gegner looten will, werden ja auch nicht die angelegte Rüstung angezeigt, obwohl der NPC sie im Inventar hat. Ich habe den Oberkörper und den Unterkörper des Hum_Body_Naked0 jeweils in zwei .ascs gesplittet und dem Helden als zwei Rüstungsteile gegeben und equippen lassen. Zusätzlich dazu habe ich die oCNpc::UnequipItem gehookt und dort abgefragt, ob es sich beim item.wear-flag des Items, was man ablegen will, um ein UpperBody- oder LowerBody-Item handelt. Falls dem so ist, wird beim Unequippen eines UpperBody- oder LowerBody-Rüstungsteil, der UpperBody/LowerBody des Hum_Body_Naked0 angelegt. Soweit funktioniert das. Kann natürlich sein, dass ich dann später Probleme kriegen werde, wenn ich zwischen UpperBody/LowerBody Rüstungsteilen hin und her wechsle und dann plötzlich der Oberkörper/Unterkörper des Hum_Body_Naked0 angelegt wird, aber das lässt sich bestimmt leicht fixen. Aber vielleicht gibt es auch eine bessere Lösung hierfür, die ich nutzen könnte.
Das verstehe ich nicht ganz. Ist die Absicht, zu vermeiden, dass der Körper durch Rüstungsteile hindurchragt? Z.B. wenn die Hand größer ist also ein Handschuh?
Ich würde da vorschlagen, die ASCs der Rüstungsteile so anzupassen, dass sie immer minimal größer sind als der Körper. Das erscheint mir am sinnvollsten.
In anderen Fällen, würde ich ähnlich vorgehen wie hier beschrieben. Dabei wird kein extra Rüstungsteil ins Inventar gelegt. Allerdings müsste man hier ein Item erstellen anstatt eines Vobs. Das könnte man vielleicht ganz einfach mit vobPtr = Itm_GetPtr(itemInstance) bewerkstelligen (und dann MEM_DeleteVob(CALL_RetValAsInt()); weg lassen).
Herobrine
26.01.2021, 10:10
Das verstehe ich nicht ganz. Ist die Absicht, zu vermeiden, dass der Körper durch Rüstungsteile hindurchragt? Z.B. wenn die Hand größer ist also ein Handschuh?
Ich würde da vorschlagen, die ASCs der Rüstungsteile so anzupassen, dass sie immer minimal größer sind als der Körper. Das erscheint mir am sinnvollsten.Ja, genau das versuche ich zu vermeiden. Im Fall von Handschuhen oder Schuhen ist es noch ziemlich einfach, sie so anzupassen, dass der Körper nicht durch sie durchragt und die Animationen auch keine Probleme machen, das Anpassen von Hosen und Hemden von vorhandenen Rüstungen sorgt aber leider für Probleme. Wenn ich neue Rüstungsteile wie Hosen oder Hemden für Oberkörper und Unterkörper modelliere, ist das kein Problem, weil ich sie dann direkt an den Körper anpassen kann. Allerdings ist mir beim Anpassen der Schattenrüstung aufgefallen, dass sich der Körper und die Rüstung an gewissen Stellen nur minimal unterscheiden, an den Schultern und an den Beinen z.B., aber es schon dafür reicht, dass bei wenigen Animationen leider trotzdem Clipping entsteht, obwohl ich schon dafür gesorgt habe, dass alle Rüstungsteile größer sind als der Körper und sie auch an den Körper angepasst habe. Da müsste ich dann die Form der Rüstung verändern oder sie zu groß machen, was ich allerdings gerne vermeiden würde, da ich lieber wollen würde, dass die Rüstungen an sich so gut wie möglich am Original dran sind. Da sah ich es einfacher für mich, wenn ich das dann so irgendwie regeln kann.
[...]
Ich finde es super, was du hier geskriptet (und angeregt) hast bezüglich modularem Rüstungssystem. Die Idee als solche gibt es allerdings schon lange und in Russland und Polen wurde auch schon vorher ähnliches in anderem Kontext realisiert (zumindest wurde mir das gesagt, denn auch wir haben seit über einem Jahr genau so ein System fest eingeplant und auch hier schon öffentlich darüber gesprochen, wie wir das genau machen möchten und vor ein paar Wochen kam ein neuer polnischer Programmierer ins Team und zu meiner Überraschung war die Idee für ihn schon längst bekannt).
Ich würde deshalb den Umstand, dass jetzt so kurz nach dem Release deines Codes in Russland ein Union Plugin auftaucht, welches das gleiche macht, eher als Inspiration und Anstoß verstehen, der von dir ausging, nicht als Respektlosigkeit. Allerdings hoffe ich natürlich, dass du in dem Thread oder im Code erwähnt wurdest, falls da Bestandteile deines Codes enthalten sind. Das hab ich mir jetzt nicht angesehen.
Nur bezüglich der Sinnlosigkeit eines solchen Plugins: Das Gute ist ja, dass der Code fast immer auch auf GitHub zur Verfügung steht und dann kann man ja genau das machen: Das Feature sinnvoll in die eigene Mod integrieren.
mud-freak
26.01.2021, 13:26
[...]
Ja, ich denke, dass muss nicht groß diskutiert werden. Die Idee war nicht neu, die Implementierung sieht auch anders aus und mich dabei irgendwie zu erwähnen wäre deshalb nicht angemessen. Mir ging es eher um etwas anderes. Ich finde es einfach anstrengend, dass es offensichtlich war, dass hier daran gearbeitet wird. Und ich darf mich nun wieder mit Kompatibilitätsproblemen herumschlagen. Es ist nicht das erste Mal, dass die andere Seite sich da keine Gedanken drüber macht.
Dabei soll's hier jetzt auch bleiben. Der Thread hier ist ja der SkriptBin und ich würde mir wünschen, dass die Skripte auffindbar bleiben und nicht durch Diskussionen im "Rauschen" verschwinden.
mud-freak
26.01.2021, 17:14
Das Skript ist jetzt aktualisiert, sodass die 3DS nicht länger nach dem Laden auftaucht, wenn es eine ASC gibt.
Falugify
28.01.2021, 19:06
Hier ist ein einfaches Skript, das die (HP-, Mana-, Schwimm- und Fokus-) Balken anders positionieren lässt. Das Skript greift dabei direkt in die Positionierung der Engine hinein für minimalen Eingriff. Folglich werden die behalten die Balken ihre Eigenschaften bei und folgen auch weiterhin der Auflösung.
setBarPositions.d (...)
Ein Spieler meldete ein Kompatibilitätsproblem mit der SystemPack Einstellung ShowManaBar. Es wird das Alle Balken auf der linken Seite, Schwimmbalken oben und nur unter Wasser sichtbar genutzt
Ich hätte es nicht für möglich gehalten, dass diese Einstellung, die ich bei jeder Installation von Gothic vornehme, der Grund ist, warum ich die Nostalgic Edition nicht mehr zum Laufen gebracht habe ab einem gewissen Punkt, aber ich kann nun mit Sicherheit sagen, dass sie es doch ist:
Man darf die Einstellung "ShowManaBar =" in der Systempack.ini nicht auf 2 stellen, sonst kann man kein Spiel starten vom Hauptmenü aus!
In der Nostalgic Edition ist die Manaleiste standardmäßig dauerhaft sichtbar, also besteht kein Grund, den Wert neben "ShowManaBar =" in der Systempack.ini auf 2 zu stellen. Lässt man ihn auf 1, dann funktioniert alles.
Die Mod funktioniert mit allen Patches und Plugins auf Basis von Union und Ninja, die ich installiert habe.
Ich würde im Startpost darauf hinweisen, dass man die Einstellung nicht verändern darf.
mud-freak
31.01.2021, 14:30
Ein Spieler meldete ein Kompatibilitätsproblem mit der SystemPack Einstellung ShowManaBar. Es wird das Alle Balken auf der linken Seite, Schwimmbalken oben und nur unter Wasser sichtbar genutzt
Danke fürs Teilen!
Ist deine Absicht damit, andere Nutzer des Skripts darauf aufmerksam zu machen oder war das eine Aufforderung, dass ich da nachbessern soll? Wenn letzteres, dann gibt es da zwei Möglichkeiten. Die Mod könnte zurückstecken, wenn ein dritter schon etwas an den entsprechenden Speicheradressen geändert hat oder sie könnte ihren Willen durchsetzen und das überschreiben.
Da ich nicht sicher war, was genau du wolltest, habe ich das Skript so geändert, dass es nichts unternimmt, falls das Anzeigen der Manabar schon modifiziert wurde. Ist im entsprechenden Post aktualisiert.
Falugify
31.01.2021, 20:08
Danke fürs Teilen!
Ist deine Absicht damit, andere Nutzer des Skripts darauf aufmerksam zu machen oder war das eine Aufforderung, dass ich da nachbessern soll? Wenn letzteres, dann gibt es da zwei Möglichkeiten. Die Mod könnte zurückstecken, wenn ein dritter schon etwas an den entsprechenden Speicheradressen geändert hat oder sie könnte ihren Willen durchsetzen und das überschreiben.
Da ich nicht sicher war, was genau du wolltest, habe ich das Skript so geändert, dass es nichts unternimmt, falls das Anzeigen der Manabar schon modifiziert wurde. Ist im entsprechenden Post aktualisiert.
Perfekt, dass du das gleich angepasst hast. Ich wusste nicht genau, was es damit auf sich hat und ob es wirklich an der Einstellung liegt. Nutzt du den Balkenscript nicht auch in deinem Stamina Patch? Auf jeden Fall ist es in der Moe Edition auch integriert. Wird also in mehreren Fällen schon genutzt, also kann es nicht verkehrt sein, das mal als Information anzugeben. :)
mud-freak
31.01.2021, 20:14
Gut zu hören. Dazu sei gesagt, dass ich die Verbesserung selbst noch nicht getestet habe.
Der Stamina-Patch nutzt dieses Skript übrigens nicht, sondern beruht auf dem Gothic Tauchbalken.
EDIT: Aber du hast Recht, dort wird der Tauchbalken ja angezeigt. Ich werde mir das mal bei Zeiten anschauen. Danke für den Hinweis.
EDIT2: Auf den ersten Blick habe ich dort schon darauf geachtet (laut meinen Kommentaren im Code). Ich vertraue mal darauf, dass sich schon jemand melden wird, falls es da doch noch Probleme gibt.
Falugify
31.01.2021, 20:27
Gut zu hören. Dazu sei gesagt, dass ich die Verbesserung selbst noch nicht getestet habe.
Der Stamina-Patch nutzt dieses Skript übrigens nicht, sondern beruht auf dem Gothic Tauchbalken.
EDIT: Aber du hast Recht, dort wird der Tauchbalken ja angezeigt. Ich werde mir das mal bei Zeiten anschauen. Danke für den Hinweis.
EDIT2: Auf den ersten Blick habe ich dort schon darauf geachtet (laut meinen Kommentaren im Code). Ich vertraue mal darauf, dass sich schon jemand melden wird, falls es da doch noch Probleme gibt.
Ich habe es beim anpassen kurz getestet beim Spiel laden. Ohne die Anpassung stürzt das Spiel bei "2" ab. Mit der Anpassung stürzt das Spiel nicht ab, egal ob "1" oder "2".
Es dürfte eigentlich dann nur ein Problem geben, wenn der Tauchbalken dauerhaft angezeigt werden kann und es in der Systempack auch dauerhaft angezeigt werden kann wie beim Manabalken.
mud-freak
31.01.2021, 21:12
Eigentlich hatte ich das dort wohl tatsächlich abgefragt. Jetzt habe ich dort aber einen kleinen Zahlendreher entdeckt. Der sollte unproblematisch sein und würde nur dafür sorgen, dass der Tauchbalken weiterhin dauerhaft angezeigt bliebe, würde man die Sichtbarkeit des Balkens während des Spiels ändern. Das werde ich korrigieren.
dragonkiller1995
06.02.2021, 17:56
Versuche gerade meinen eigen mod zu erstellen aber habe 2 Probleme wie müsste das script für einen monster respawn ausehen ???
wie kann man mit dem gothic scourcer eine mod zerlegen????
mud-freak
23.02.2021, 15:45
Ich hatte es beim Suchen im Forum nicht mehr auf Anhieb finden können, deshalb möchte ich das Skript zum Gothic 1 Taschendiebstahl in Gothic 2 noch hier verlinken.
Falugify
23.02.2021, 22:43
Ich hatte es beim Suchen im Forum nicht mehr auf Anhieb finden können, deshalb möchte ich das Skript zum Gothic 1 Taschendiebstahl in Gothic 2 noch hier verlinken.
Es kommt zu Abstürzen, wenn ein Gegenstand geklaut wird, der gerade benutzt wird (Brot, Schinken, Käse, Wacholder,...)
mud-freak
24.02.2021, 08:12
Es kommt zu Abstürzen, wenn ein Gegenstand geklaut wird, der gerade benutzt wird (Brot, Schinken, Käse, Wacholder,...)
Ich habe hier geantwortet.
Ich glaub wir sollten den Skriptbin mal besser organisieren. Hier mischen sich Skript und Diskussion dazu, sodass der eigentliche Sinn der puren Sammlung zerstört wird.
Das heißt nicht, dass ich Diskussionen zu Skripten schlecht finde. Ich finde nur, dass wir die Diskussion von der Sammlung trennen sollten.
Und ein alphabetisches Verzeichnis wäre auch ganz gut. Sollen wir einen neuen Thread machen, in den Strikt nur die eigentlichen Skripte kommen und wo der Startpost das Verzeichnis enthält? Was meint ihr dazu?
neocromicon
24.02.2021, 17:22
Ich glaub wir sollten den Skriptbin mal besser organisieren. Hier mischen sich Skript und Diskussion dazu, sodass der eigentliche Sinn der puren Sammlung zerstört wird.
Das heißt nicht, dass ich Diskussionen zu Skripten schlecht finde. Ich finde nur, dass wir die Diskussion von der Sammlung trennen sollten.
Und ein alphabetisches Verzeichnis wäre auch ganz gut. Sollen wir einen neuen Thread machen, in den Strikt nur die eigentlichen Skripte kommen und wo der Startpost das Verzeichnis enthält? Was meint ihr dazu?
Du sprichst mir aus der Seele, bloß wer will das schon machen? Es sind ja nun wirklich viele Scripte auf zig Seiten verteilt und dann müsste der jenige auch min 5-10 Jahre noch aktiv im Forum sein, um alles weiterhin zu verwalten.
genau aus diesem Grund habe ich angefangen mir vorerst mein eigenes "script bin" zu schreiben :D
Darin kann ich zumindest schnell suchen und ggf. auch noch Beispiele einbinden, tags, kategorien und andere nette schmankerl inbegriffen
Dort schmeiße ich mir dann immer mal wieder das ein oder andere gefundene Skript rein, mit quelle, datum, author, allem pi-pa-po
50278
mud-freak
25.02.2021, 12:55
Sieht sehr schön aus!
Dort wird es wahrscheinlich ähnlich sein wie mit der ursprünglichen Absicht dieses Threads: Ein Repository mit allen Skripten (oder auch nur eine einfache Verlinkung aller Skripte im Einleitungsthread) ist einfach eine Aufgabe, die gewisse Pflege und Zeit beansprucht. Solange es nur von einer Person verwaltet wird, kommt irgendwann der Punkt, dass sie nicht mehr so aktiv selbst moddet oder anderweitig keine Zeit dafür mehr hat. Abgesehen von einer Art Reviewing-Process, ob ein gepostetes Skript als bugfrei mit guten Gewissen in so einer Liste angeboten werden sollte.
Eine vollständige Verlinkung der Skripte im Einleitungspost (wie ein Inhaltsverzeichnis) ist sicher schön, aber die erfordert wie deine praktische Datenbank gewisse "lebenslange" Pflege. Am schönsten wäre es, wenn (d)eine Datenbank die Skripte automatisch aus diesem Thread (oder sogar dem Forum) filtern und auflisten könnte, sodass sie niemand pflegen muss und irgendwann niemand mehr Zugriff auf die Pflege hat. Dabei wäre meine Sorge, dass schon wieder ein neuer "SkriptBin" entsteht. Nicht zu vergessen, dass es im Wiki eine Liste von nützlichen Skripts gibt, im Editing FAQ meines Wissens nach auch, im Einleitungspost von Ikarus und in dem von LeGo auch. Wenn neben dem SkriptBin noch weitere Sammelungen entstehen, wird es irgendwann ganz im Gegenteil schwieriger Skripte zu finden, weil man im schlimmsten Falle alle dieser Sammlungen durchsuchen muss.
Ich finde das jetzige Format vom Skriptbin ehrlich gesagt gar nicht so schlecht und sehe kein Handlungsbedarf. Die Suche von gewissen Skripten (oder eine Verlinkung dazu) lässt sich vom gesamten Forum auf einen einzigen Thread reduzieren. Das finde ich eine enorme Hilfe. Und selbst, wenn es zwischen den Skript-Posts keine Diskussionen gäbe wäre das Finden von Skripten ebenso "viel Arbeit" als ohne.
Sieht sehr schön aus!
Dort wird es wahrscheinlich ähnlich sein wie mit der ursprünglichen Absicht dieses Threads: Ein Repository mit allen Skripten (oder auch nur eine einfache Verlinkung aller Skripte im Einleitungsthread) ist einfach eine Aufgabe, die gewisse Pflege und Zeit beansprucht. Solange es nur von einer Person verwaltet wird, kommt irgendwann der Punkt, dass sie nicht mehr so aktiv selbst moddet oder anderweitig keine Zeit dafür mehr hat. Abgesehen von einer Art Reviewing-Process, ob ein gepostetes Skript als bugfrei mit guten Gewissen in so einer Liste angeboten werden sollte.
...
Die sache ist die, das dieser "script bin" welchen ich da habe mit "Hugo" realisiert ist.
An sich kann man problemlos ein GitHub Repo davon aufmachen und zu jederzeit dinge aktualisieren oder das ding einfach forken und einen neuen Github.io Link irgendwo hin setzen.
Gemeinsame kollaboration sind super einfach (vorausgesetzt git kenntnisse, oder halt eine belegschaft welche über Issues Skripte aktualisiert/korrigiert)
Und auch das wegsterben ist kein problem, da man dies einfach forken könnte und weitermachen kann wo es gestoppt ist.
Ich selbst schreibe da nur skripte rein, welche ich mal benutzt habe. Vielleicht setzte ich das ding irgendwann mal global auf wie beschrieben.
Entstanden aus : https://forum.worldofplayers.de/forum/threads/1574543-NPC-zuverl%C3%A4ssig-wegschleudern-%28%C3%A4hnl-Windfaust%29
Hier ein Skript welches es einem erlaubt einen beliebigen NPC wegzuschleudern.
Damit lassen sich einige nette Effekte realisieren
Einzelne Schockwelle
https://abload.de/img/single_shockwaverqj19.gif
Schockwelle mit mehreren NPC
https://abload.de/img/multi_shockwavep1j4q.gif
- Benötigt Ikarus
- Getestet mit G2 (G1 sollte auch funktionieren)
Vob_KnockbackNpc.d
/*
Author : Kirides
Date : 13.03.2021
Version: 1
Topics:
- https://forum.worldofplayers.de/forum/threads/1574543-NPC-zuverl%C3%A4ssig-wegschleudern-%28%C3%A4hnl-Windfaust%29
Functions:
Wld_KnockbackNpc(int posPtr, C_NPC targetNpc, int strength)
Vob_KnockbackNpc(zCVob vob , C_NPC targetNpc, int strength)
Npc_KnockbackNpc(C_NPC src , C_NPC targetNpc, int strength)
Wld_KnockbackNpc:
Takes in the source position zVEC3 as a pointer, aswell as
the target npc and the strength of the pushback
Vob_KnockbackNpc:
Takes in any zCVob for source position, aswell as the target
npc and the strength of the pushback
Npc_KnockbackNpc
Takes in any npc for source position, aswell as the target
npc and the strength of the pushback
Changes:
-
*/
func void Wld_KnockbackNpc(var int posPtr, var C_NPC targetNpc, var int strength) {
if (!Hlp_IsValidNpc(targetNpc)) { return; };
// do not apply to enemy that is on the ground
if (Npc_GetBodyState(targetNpc) == BS_LIE) {
return;
};
// Get engine types, so we get access to their world position and AI
var oCNpc oTar; oTar = MEM_CpyInst(targetNpc);
if (!oTar.human_ai) { return; };
var int humAI; humAI = oTar.human_ai;
var int strF; strF = mkf(strength);
const int zVEC3__NormalizeSafe_G1 = 4900544; // 0x4AC6C0
const int zVEC3__NormalizeSafe_G2 = 4819488; // 0x498A20
const int oCAiHuman__StartFlyDamage_G1 = 6381888; // 0x616140
const int oCAiHuman__StartFlyDamage_G2 = 6936896; // 0x69d940
const int sizeof_zVEC3 = 12; // 0x000C
// Get vector "away from source"
var int dirVec[3];
var int dirPtr; dirPtr = _@(dirVec);
// Get source direction
MEM_CopyBytes(posPtr, dirPtr, sizeof_zVEC3);
// substract source from target position to get the "away direction"
dirVec[0] = subf(oTar._zCVob_trafoObjToWorld[zCVob_trafoObjToWorld_X], dirVec[0]);
dirVec[1] = subf(oTar._zCVob_trafoObjToWorld[zCVob_trafoObjToWorld_Y], dirVec[1]);
dirVec[2] = subf(oTar._zCVob_trafoObjToWorld[zCVob_trafoObjToWorld_Z], dirVec[2]);
// Normalize the direction vector
const int call = 0;
if (CALL_Begin(call)) {
CALL__thiscall(_@(dirPtr), MEMINT_SwitchG1G2(zVEC3__NormalizeSafe_G1, zVEC3__NormalizeSafe_G2));
call = CALL_End();
};
// Just to clarify intent: Keep "minimum fall height until damage"
// Set to "TRUE" to enable fall damage without minimum height
oTar.overrideFallDownHeight = FALSE;
// Start actualy flying damage, well technically not really damage
// This applies the direction force and sets the animations
const int call2 = 0;
if (CALL_Begin(call2)) {
CALL_PtrParam(_@(dirPtr));
CALL_FloatParam(_@(strF));
CALL__thiscall(_@(humAI), MEMINT_SwitchG1G2(oCAiHuman__StartFlyDamage_G1, oCAiHuman__StartFlyDamage_G2));
call2 = CALL_End();
};
};
func void Vob_KnockbackNpc(var zCVob vob, var C_NPC targetNpc, var int strength) {
var int pos[3];
pos[0] = vob.trafoObjToWorld[zCVob_trafoObjToWorld_X];
pos[1] = vob.trafoObjToWorld[zCVob_trafoObjToWorld_Y];
pos[2] = vob.trafoObjToWorld[zCVob_trafoObjToWorld_Z];
Wld_KnockbackNpc(_@(pos), targetNpc, strength);
};
func void Npc_KnockbackNpc(var C_NPC src, var C_NPC targetNpc, var int strength) {
if (!Hlp_IsValidNpc(src)) { return; };
var zCVob vob; vob = MEM_CpyInst(src);
Vob_KnockbackNpc(vob, targetNpc, strength);
};
ThreeGods
23.01.2023, 18:06
Hallo zusammen,
ich grabe hier mal ein bisschen, da ich vor einem kleinen Rätsel stehe:
Ich möchte mit den Ikarus-Skripten für VOBs entfernen und einfügen ein Boot einfügen, mit dem man zu einer Insel kommt. Die Idee ist, das Boot an zwei Punkten jeweils wechselseitig zu entfernen und einzufügen, wenn der Player das wünscht (mit ner Überblendung und so, tut jetzt aber nichts zur Sache). Das funktioniert auch an sich wunderbar, das Boot wechselt den Standort. Allerdings wird es nur beim ersten einfügen, das in der Startup gesetzt wird korrekt am WP platziert, danach wird es bei jeder Platzierung zwar am korrekten Standort eingefügt, allerdings auf dem Grund des Meeres. Dabei sind die Befehle und Parameter (FALSE bei align to floor) bei StartUp und in der Funktion genau gleich.
Hier die Skripte:
In der Startup:
MEM_InitAll ();
matrix = GetTrafoFromWP ("BOOT_01", FALSE);
ptrBoat = InsertVobAt(matrix, 0, 0, "HERO_BOAT", "nw_harbour_small_boat_02.3ds", 1, 1, 1, 1);
In den Funktionen für späteren Ortswechsel:
RemoveoCVobSafe(ptrBoat, TRUE);
matrix = GetTrafoFromWP ("BOOT_01", FALSE);
ptrBoat = InsertVobAt(matrix, 0, 0, "HERO_BOAT", "nw_harbour_small_boat_02.3ds", 1, 1, 1, 1);
RemoveoCVobSafe(ptrBoat, TRUE);
matrix = GetTrafoFromWP ("BOOT_02", FALSE);
ptrBoat = InsertVobAt(matrix, 0, 0, "HERO_BOAT", "nw_harbour_small_boat_02.3ds", 1, 1, 1, 1);
Und nur beim ersten Einfügen bei Spielstart landet das Boot "schwimmend" auf der Höhe des WP, bei allem, was dann durch den PC getriggert wird auf Grund.
Jemand eine Idee?
PS: In der InsertAnything.d hab ich in der GetTrafoFromWP Funktion das AlignWPToFloor schon in eine if-Abfrage geschoben, wo ich dann immer FALSE übergebe, siehe unten. Vorher war nämlich das Boot auch am Anfang schon unter Wasser...
func int GetTrafoFromWP(var String waypoint, var int setToGround) {
// Hole Waypoint
const int zCWayNet__GetWaypoint = 8061744; //0x7B0330
CALL__fastcall(_@(MEM_Waynet), _@s(waypoint), zCWayNet__GetWaypoint);
var int wpPtr; wpPtr = CALL_RetValAsInt();
if (!wpPtr){ MEM_Warn("GetTrafoFromWP: Waypoint not found."); return 0; };
// Leiste Spacernacharbeit und richte den Waypoint richtig aus (Höhe)
//AlignWPToFloor(wpPtr);
// Baue Traformationsmatrix von Position und Ausrichtung
var zCWaypoint wp; wp = _^(wpPtr);
var int pos[3]; MEM_CopyWords(_@(wp.pos), _@(pos), 3);
if (setToGround)
{
AlignWPToFloor(wpPtr);
pos[1] = subf(pos[1], mkf(50)); }; // Korrigiere Hoehe
return PosToTrf(_@(pos), _@(wp.dir), 1, 0);
};
Milky-Way
24.01.2023, 01:47
Ich würde das Vob an beiden Orten per Spacer setzen und dann über Ikarus nur das Vob ein und ausblenden. In LoA platzieren wir so ab einem späten Kapitel einige Schiffe im Meer. Ich fand das deutlich angenehmer im Spacer die Schiffe ganz genau auszurichten, als das mit einem WP zu machen.
ThreeGods
24.01.2023, 06:11
Du meinst, die Visibility? Haben die denn dann nicht trotzdem noch Kollision, so dass man unter Umständen gegen ein unsichtbares Objekt schwimmt zum Beispiel?
Und brauche ich dafür nicht auch einen Pointer auf das VOB? Wo kriege ich den her, wenn nicht durch das Einfügen des VOBs wie oben?
Du meinst, die Visibility? Haben die denn dann nicht trotzdem noch Kollision, so dass man unter Umständen gegen ein unsichtbares Objekt schwimmt zum Beispiel?
Und brauche ich dafür nicht auch einen Pointer auf das VOB? Wo kriege ich den her, wenn nicht durch das Einfügen des VOBs wie oben?
Die Kollision kannst du via Script ausschalten (cdDynamic im zCVob bitfield, glaube ich). Vobs können einen Namen haben, und dann kannst du mit MEM_SearchVob (oder so) einen Pointer auf das Vob bekommen :)
ThreeGods
24.01.2023, 15:55
Vielen Dank, das schau ich mir mal an. Das VOBs Namen haben, war mir schon klar, allerdings wusste ich nicht, woher man die Pointer dann bekommt^^. Dann such ich mal nach der Funktion.
Milky-Way
24.01.2023, 17:08
Ich hatte gestern den Code nicht zur Hand, deshalb jetzt hier verspätet die passenden Funktionen:
func int ShowVob(var string vobname)
{
var int ptr; ptr = MEM_SearchVobByName(vobname);
if (ptr)
{
var zCVob myvob; myvob = _^(ptr);
myvob.bitfield[0] = myvob.bitfield[0] | zCVob_bitfield0_showVisual;
return true;
};
return false;
};
func int HideVob(var string vobname)
{
var int ptr; ptr = MEM_SearchVobByName(vobname);
if (ptr)
{
var zCVob myvob; myvob = _^(ptr);
myvob.bitfield[0] = myvob.bitfield[0] & ~zCVob_bitfield0_showVisual;
return true;
};
return false;
};
Zu Spielbeginn sind die im Spacer eingefügten Vobs da -- du möchtest also vermutlich zumindest für eins der beiden Boote HideVob in der Startup aufrufen und dann jeweils beim Wechsel einmal HideVob(<Ursprung>) und ShowVob(<Ziel>). Bei uns beispielsweise: ShowVob("K5_SHIP_1");. Die Funktion gibt, zu Testzwecken zurück, ob solch ein Vob gefunden wurde (true) oder nicht (false). Falls du das nicht brauchst, einfach aus int ein void machen und die returns entfernen.
Wenn ich mich richtig erinnere, reichte ~zCVob_bitfield0_showVisual schon aus, damit man nicht mehr mit der leeren Luft kollidiert. Im Spacer sollte also dynamische Kollision eingestellt sein. Darfst du aber gerne zur Sicherheit noch mal testen :).
ThreeGods
24.01.2023, 17:26
Vielen Dank, habs gerade eingefügt und klappt super§danke§danke§danke
War schon ein wenig am verzweifeln, hab in den ikarus skripten rumgesucht, aber schlussendlich viel einfacher als gedacht^^. Die Funktionen klappen genauso, wie ich wollte, danke nochmal!
Ich möchte das Script screenFade nutzen und konnte es auch schon im Spiel ausprobieren. Aber wie realisiere ich, dass sich der Bildschirm erst nach einer bestimten Aktion (Dialog, Animation, toter NPC oder ähnliches) abgespielt wird? Ich habe es im Dialoge mit IF versucht und mit Events hat es ebenfalls nicht funktioniert.
Gedacht habe ich es mir so:
....
AI_Output (self, other, "DIA_BDT_99000_NPC1_DIALOG_1_3"); //Hallo
//Held wird erschlagen
AI_GotoNpc(self, hero);
AI_PlayAni (self, "T_1HATTACKMOVE");
AI_WaitTillEnd(hero, self);
AI_PlayAni (hero, "T_STAND_2_WOUNDED");
//Begleiter des Helden wird erschlagen
B_Attack (self, MIL_99000_NPC2, AR_KILL, 1); // <---- Der Begleiter
AI_StopProcessInfos(hero);
if (Npc_IsDead (MIL_99000_NPC2)) // <---- Wenn der Begleiter tot ist, soll sich der Bildschirm abdunkeln
{
ScreenFade (5000, 10000, 5000);
};
AI_Function ist dafür gedacht:
func void myScreenFade() {
ScreenFade (5000, 10000, 5000);
};
// im Dialog an der Stelle, an der es passieren soll:
AI_Function(self, myScreenFade);
Achso ja, klingt einleuchtend. Es funktioniert jetzt! Danke.
Powered by vBulletin® Version 4.2.2 Copyright ©2025 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.