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.
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
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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, ...)
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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;
};
Geändert von mud-freak (24.11.2021 um 10:07 Uhr)
Grund: Adding trimming variable declarations in STR_TrimR
sound.d – Sound and music functions Hier ist die Funktion stopAllSounds, die bereits im ScriptBin ist, enthalten.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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);
};
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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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();
};
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.
Code:
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:
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:
Code:
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.
Code:
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:
Code:
// 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 (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?
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.
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.
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!
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.
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 finden (am besten ersetzt du deine Anim8.d im LeGo-Verzeichnis mit der verlinkten). Alternativ kannst du aber auch die "alte" 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.
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.
Code:
/*
* 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].
Code:
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)
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?
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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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);
};
};
Geändert von mud-freak (30.10.2020 um 10:24 Uhr)
Grund: Absturz in overrideBars behoben
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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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.
Spoiler:(zum lesen bitte Text markieren)
Code:
/*
* 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.
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).
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: