überspringe Navigation

Category Archives: Bugfixing

Beseitigung von Programmier- und Designfehlern.

eternally_exhausted

Es ist frustrierend, wenn es überhaupt nicht vorangeht.

Das HTF-System regelt ganz gut die Auswirkungen von Hunger und Durst auf die Spielfigur— wenn der Balken komplett rot gefärbt ist, stirbt man.

Nur bei der Erschöpfung hapert es, die Spielfigur bricht zusammen, man kann nichts mehr machen und muss abwarten, bis einem entweder Hunger oder Durst den endgültigen Garaus machen. Und das kann dauern.

Und wieder einmal verstehe ich nicht, wie Olander und Mannast, die dieses Skriptsystem ausgedacht bzw weiterentwickelt haben, so etwas übersehen können. Na ja.

Zwei Wege bieten sich an: Entweder der Kollaps führt dazu, dass das Toon hinterher wieder etwas mehr Kraft hat, um die nötigen Schritte zu unternehmen, ordentlich rasten zu können. Im Prinzip ist das aber die selbe Sackgasse wie vorher, auch jetzt kann man nicht direkt an Erschöpfung sterben, man kann sich einfach endlos von einem Kollaps zum nächsten durchbuckeln.

Warum also nicht konsequent sein? Wenn die Erschöpfungsanzeige rot ist, ist man tot, genau wie bei Hunger oder Durst. Ganz einfach.

———

Wo ich schon einmal dabei war, habe ich noch eine Kleinigkeit eingeführt, die wir vor einiger Zeit gemeinsam überlegt hatten. Anlass dazu gab die Beobachtung, dass man auf den Servern so gut wie nie „dicke“ Spielfiguren sieht (Phänotyp „kräftig“). Warum sollte man seine Spielfigur untersetzt bauen? Weil es vielleicht rollen(-spiel-)gerecht ist? Als kleinen Anreiz habe ich jetzt eingebaut, dass, wer „kräftig“ aussieht, auch „kräftig“ ist. Der starke Phänotyp wird mit Stärke +1 belohnt. Permanent.

 

VolleFestplatte

Als ich Anfang 2014 den neuen Server aufsetzte, war ich so naiv zu denken, dass 60 GB Festplattenplatz für die Systempartition genug sein müssten.

Denkste. Durch die ständigen Updates, Sicherungen des Update-Verlaufs etc waren zuletzt noch sage und schreibe 7,5 MB übrig.

Zum Leben zu wenig, zum Sterben zu viel.

Und die Einstellung bei Microsoft ist ja weiterhin, den User nicht mit Funktionen zu belasten, die er ohnehin wahrscheinlich nicht benutzen muss.

Bei Win7 habe ich mittlerweile gelernt, wie ich die Backups und Schattenkopien über Bord werfen kann, um Platz zu schaffen. Bei Windows Server 2011 ist so etwas hingegen überhaupt nicht vorgesehen —im Gegenteil, jeder Versuch, ein paar Gigabyte locker zu machen, legt neue Log-Dateien an, die ebenfalls Platz benötigen.

Also Server aus, Festplatte raus, Partitionen an anderem Rechner umschaufeln und hoffen, dass Windows sich damit fürderhin zufrieden gibt.

Schwarzer_Moderkäfer

Der Server lief in den letzten Wochen friedlich vor sich hin, ich dachte mir nichts Böses und beschäftigte mich damit, einige Programmierprobleme zu lösen:

1. „Wald ohne Wiederkehr“ verwendet variable Gebietsübergänge, das Script wurde bisher durch einen simplen Trigger ausgelöst. Wenn ich aber mitten im Abenteuer wieder einsteigen will, brauche ich eine andere Form des Auslösers (zB OnModuleLoad).

2. Die HTF-Scripte (Hunger, Durst, Erschöpfung) müssen so abgeändert werden, dass NPCs, die ich als Spielleiter in Besitz nehme, nicht beim ersten Heartbeat (~ 6 sec) tot umfallen.

3. Es muss eine bessere Verzahnung zwischen persistenten (in der SQL-Datenbank gespeicherten) und nicht-persistenten (im Modul gespeicherten) Variablen erreicht werden, damit der Wiedereinstieg in die Abenteuer auch nach längerer Pause problemlos möglich ist und dabei nicht zu viele Scripte an der Performance nagen.

———

Wie so oft, wenn ich frohgemut mehrere Bereiche simultan bearbeite— als ich das neue Modul geladen hatte und es ausprobieren wollte, FUNKTIONIERTE GAR NICHTS MEHR!
Der Spielleiter-Client brachte das Modul zum Absturz. Variablen wurden weder gespeichert noch korrekt ausgelesen. Das wichtige Start-Script, welches beim Einstieg in die Spielwelt die richtigen Verknüpfungen herstellen soll, brach scheinbar zufällig ab.

Ich war schon ganz verzweifelt und kurz davor, alles hinzuschmeißen, habe dann aber im Laufe der letzten Tage die Fehlerquellen isoliert und beseitigt.
Ich hatte die NWN-Installation auf eine andere Partition des Servers verschoben, da mir auf C:\ wegen der Windows-Updates der Platz ausging, und das schon wieder vergessen. Dadurch gingen alle Kompatibilitätseinstellungen verloren und die SQL-Schnittstelle verwies auf das falsche Verzeichnis. Die Scripte fragten teilweise Variablen ab, die in bestimmten Problemkonstellationen zuvor nicht definiert worden sein konnten, da die ursprünglichen Funktionen auf der Annahme einer einzelnen Spielsession oder eines fortlaufenden Moduls basierten. Hier habe ich Sicherungen eingebaut, damit es auch bei Modul-Reset und Verwendung Eurer eigenen hochstufigen Chars keine Aussetzer mehr gibt.

Unten ein Einblick in eines der wichtigsten Scripte, die bei Spielstart ausgeführt werden müssen.

//Wird am Startpunkt ausgefuehrt;
//neue Spieler mit 0 XP bekommen Startausruestung (und werden ggf zum RP-Rassenstartpunkt geportet;)
//Spieler, die schon XP haben, werden zu ihrer letzten Location geportet
//und wichtige Variablen (HTF, Guns) werden gesetzt

#include „nw_i0_plot“
//#include „crp_inc_control“ (in CRP_INC_MOVE enthalten)
#include „colors_inc“
#include „x2_inc_switches“
#include „crp_inc_move“

//Datenbankschnittstelle integrieren
#include „aps_include“

object oPC = GetEnteringObject();

//Alte Erde Wetter-System:
object oArea = GetArea(oPC);

int nEinmal = GetLocalInt(oPC, „Gestartet?“);

//object oItem = GetFirstItemInInventory(oPC);//wir verzichten darauf, das Gepaeck zu leeren

//Startpunkt „Ursprung“ fuer ganz neue Spieler festlegen
//object oStartALL = GetObjectByTag(„11_WP_URSPRUNG“);

//Textnachricht fuer wiederkehrende Spieler
string sSpieler = GetPCPlayerName(oPC);
string sWelcomeBack = ColorTokenWhite() +(„Schoen, Dich wiederzusehen, „) +sSpieler +(„!“) +ColorTokenEnd();

object oMod = GetModule();

//HTF-System
string sHunger = „HUNGER“;//Name of Module Hunger Value
string sThirst = „DURST“;//Name of Module Thirst Value
string sFatigue = „ERSCHOEPFUNG“;//Name of Module Fatigue Value
string sAlcTotal = „ALKOHOL“;//Used for Alcohol System
int nHungerHP = GetLocalInt(oMod,sHunger);// See htf_mod_onload
int nThirstHP = GetLocalInt(oMod,sThirst);// See htf_mod_onload
int nFatigueHP = GetLocalInt(oMod,sFatigue);// See htf_mod_onload
//Wasserflasche
object oCanteen;
int nCanteenLoads;// Zahl der Ladungen

//Gun-System (um Feuerwaffen im Gepaeck kuemmert sich „u_guns_on_enter“)
object oWeapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, oPC);
int nWeaponType = GetBaseItemType(oWeapon);
object oAmmunition;

/////////////////////////////////////////////////////////////
//
// Schlafmangel initialisieren:
//
/////////////////////////////////////////////////////////////
void ResetWeariness (object oPC)
{
int nSchlafSet = GetPersistentInt(oPC, „WearyStarted“);

if (nSchlafSet != 1)
{
SetLocalInt(oPC, „Schlafmangel“, 0);
SetPersistentInt(oPC, „WearyStarted“, 1);
SetPersistentInt(oPC, „Schlafmangel“, 0);
SendMessageToPC(oPC, „AE-Schlafmangel-System initialisiert.“);
}
else SendMessageToPC(oPC, „AE-Schlafmangel-System aktiv.“);
}

//////////////////////////////////////////////////////////
//
// HAUPTTEIL
//
//////////////////////////////////////////////////////////

void main()
{
//abbrechen, falls das Script bereits einmal fuer diese Spielfigur ausgefuehrt wurde:
if (nEinmal == 1)
{
return;//abbrechen und nichts weiter machen
}
///////////////////////////////////////////////

//ansonsten geht es weiter:
//Sichergehen, dass das Script nur einmal beim Start des Spielers ausgefuehrt wird, auch falls etwas schiefgeht:
SetLocalInt(oPC, „Gestartet?“, 1);

//Portal Atrium gilt immer als bekannt:
SetLocalInt(oPC, „KenntPO_ATRIUM“, 1);
SetLocalInt(oPC, „KenntAtrium“, 1);

if (!GetIsDM(oPC) && GetXP(oPC)== 0)
{
SetLocalString(oPC, „RespawnWP“, „AE_RESP_GENERAL“);//der allgemeine Respawn-Punkt, falls nicht anders definiert

//Heimatportal fuer Schriftrolle des Stadtportals einmalig setzen:
SetLocalString(oPC, „MyHomePortal“, „PO_ATRIUM“);

//den Schlafmangel-Ausgangswert setzen:
//ExecuteScript(„ae_weary_set_0“, oPC);// wird durch entspr Funktion ersetzt:
ResetWeariness(oPC);

//Willkommensnachricht definieren:
sWelcomeBack = ColorTokenWhite() +(„Viel Spass bei ‚Geschichten von der Alten Erde‘, „) +sSpieler +(„!“) +ColorTokenEnd();

object oSoulStone = GetItemPossessedBy(oPC, „U_SEELENSTEIN“);
if (!GetIsObjectValid(oSoulStone))
{
CreateItemOnObject(„u_seelenstein“, oPC, 1,““);
}
//fuer ALLE gueltige Startausruestung geben:
CreateItemOnObject(„u_messer1“, oPC, 1); // Behelfswaffe
CreateItemOnObject(„watercanteen“, oPC, 1); //Feldflasche
CreateItemOnObject(„torch“, oPC, 1); //CRAP-HTF-Fackel

//prueft, ob in „CRP_INC_CONTROL“ Hunger und Erschoepfungssystem aktiviert sind:
int nHUNGERSYSTEM = GetLocalInt(oMod,“HUNGERSYSTEM“);
int nFATIGUESYSTEM = GetLocalInt(oMod,“FATIGUESYSTEM“);

//HTF-Werte auf Ausgangswerte zuruecksetzen:

//Ausgangswerte definieren:
int nHUNGER = 6000;//Default 6000
int nTHIRST = 5500;//Default 5500
int nFATIGUE = 5000;//Default 5000

if ((nHUNGERSYSTEM=1) || (nFATIGUESYSTEM=1))
{
SetLocalInt(oPC,sHunger,nHungerHP);
SetLocalInt(oPC,sThirst,nThirstHP);
SetLocalInt(oPC,sFatigue,nFatigueHP);
SetLocalInt(oPC,sAlcTotal,0);
SetCampaignInt(„HTF“,sHunger,nHungerHP,oPC);
SetCampaignInt(„HTF“,sThirst,nThirstHP,oPC);
SetCampaignInt(„HTF“,sFatigue,nFatigueHP,oPC);
SetCampaignInt(„HTF“,sAlcTotal,0,oPC);
}

//Zum Rassenstartpunkt teleportieren: (wird derzeit nicht verwendet)
//ClearAllActions(TRUE);
//ApplyEffectToObject(DURATION_TYPE_INSTANT, eTransport, oPC, 3.0);
//AssignCommand(oPC, ActionJumpToObject(oStartALL, FALSE));

//Ruhm (=Ansehen in der Welt) auf 0 setzen
SetPersistentInt(oPC, „Ruhm“, 0);

//beim ersten Mal leeren Wert fuer Aktive Quest setzen:
SetPersistentString(oPC, „AktuelleQuest“, „“);

//Rastplatz zuruecksetzen:
SetPersistentInt(oPC, „SichereRast“, FALSE);

//verhindern, das obiges erneut ausgefuehrt wird:
SetPersistentInt(oPC, „Schonmal“, 1);

SetXP(oPC, 1);//egal, wie vorher die XP waren, jetzt auf 1 gesetzt
DelayCommand(2.0, FloatingTextStringOnCreature(sWelcomeBack, oPC, TRUE));
}

if (!GetIsDM(oPC) && GetXP(oPC) >= 2)//falls die Figur aus anderem Spiel importiert wurde
{
int nHasPlayed = GetPersistentInt(oPC, „Schonmal“);
if (nHasPlayed != 1)
{
//Willkommensnachricht definieren:
sWelcomeBack = ColorTokenWhite() +(„Vielen Dank fuer Dein Interesse an dieser Spielwelt, „) +sSpieler +(„!“) +ColorTokenEnd();

//der allgemeine Respawn-Punkt, falls nicht anders definiert
SetLocalString(oPC, „RespawnWP“, „AE_RESP_GENERAL“);

//Heimatportal fuer Schriftrolle des Stadtportals erstmalig setzen:
SetLocalString(oPC, „MyHomePortal“, „PO_ATRIUM“);

//beim ersten Mal leeren Wert fuer Aktive Quest setzen:
SetPersistentString(oPC, „AktuelleQuest“, „“);

//Ruhm (=Ansehen in der Spielwelt) auf 0 setzen:
SetPersistentInt(oPC, „Ruhm“, 0);

//Rastplatz-Ausgangswert setzen:
SetPersistentInt(oPC, „SichereRast“, FALSE);

//verhindern, das obiges erneut ausgefuehrt wird:
SetPersistentInt(oPC, „Schonmal“, 1);

//einmalig fuer Figuren aus anderen Spielen den Schlafmangel-Ausgangswert setzen:
ResetWeariness(oPC);
}

//vergewissern, dass der Spieler einen Seelenstein hat:
object oSoulStone = GetItemPossessedBy(oPC, „U_SEELENSTEIN“);
if (!GetIsObjectValid(oSoulStone))
{
CreateItemOnObject(„u_seelenstein“, oPC, 1,““);
}

//aus der SQL-Datenbank die Daten vom letzten Speicherstand abfragen(Persistenz, Exploit stoppen)

//Ruhm aus der Datenbank fuer das laufende Spiel eintragen:
int nFame = GetPersistentInt(oPC, „Ruhm“);
SetLocalInt(oPC, „Ruhm“, nFame);//(macht nichts, wenn dieser Wert fehlt, weil ohne Datenbank gespielt wird)

//HTF-System
int nHunger = GetPersistentInt(oPC, sHunger);
int nThirst = GetPersistentInt(oPC, sThirst);
int nFatigue = GetPersistentInt(oPC, sFatigue);
int nAlcTotal = GetPersistentInt(oPC, sAlcTotal);

//Rastplatz abfragen
int bRastplatz = GetPersistentInt(oPC, „SichereRast“);
SetLocalInt(oPC, „Rastplatz“, bRastplatz);

//aus der Datenbank die derzeit aktive Quest abfragen und einsetzen:
string sActiveQuest = GetPersistentString(oPC, „AktuelleQuest“);
SetLocalString(oPC, „AktuelleQuest“, sActiveQuest);

//Respawn-Punkt
string sRespawnWP = GetPersistentString(oPC,“RespawnWP“);
//falls es fehlt, weil ohne Datenbank gespielt wird:
if (sRespawnWP == „“) sRespawnWP = „AE_RESP_GENERAL“;
SetLocalString(oPC, „RespawnWP“, sRespawnWP);

//Wasserflasche-Ladungen
oCanteen = GetItemPossessedBy(oPC, „WaterCanteen“);
if(GetIsObjectValid(oCanteen))
{
nCanteenLoads = GetPersistentInt(oPC, „CHARGES“);
SetLocalInt(oCanteen, „CHARGES“, nCanteenLoads);
}
else if(!GetIsObjectValid(oCanteen))
{
CreateItemOnObject(„watercanteen“, oPC, 1);
}

//Gun-System
//int nGunTest = GetPersistentInt(oPC, „xGun“);
int nGunBulletTest = GetPersistentInt(oPC, „xGunBullet“);
int nGunWasLoaded = GetPersistentInt(oPC, „xLoadStatus“);
//Armor-Encumbrance
if ((GetStringLeft(GetResRef(oWeapon), 4) == „gun_“) || (GetStringLeft(GetResRef(oWeapon), 4) == „pst_“))
{
SetLocalInt(oWeapon, „nGun“, 1);
}

int nMode = GetPersistentInt(oPC, „EQUIP_MODE“);

//die Werte aus der Datenbank fuer das laufende Spiel eintragen
SetLocalInt(oPC,sHunger,nHungerHP);
SetLocalInt(oPC,sThirst,nThirstHP);
SetLocalInt(oPC,sFatigue,nFatigueHP);
SetLocalInt(oPC,sAlcTotal,0);
//und in die Kampagnen-Datenbank
SetCampaignInt(„HTF“,sHunger,nHungerHP,oPC);
SetCampaignInt(„HTF“,sThirst,nThirstHP,oPC);
SetCampaignInt(„HTF“,sFatigue,nFatigueHP,oPC);
SetCampaignInt(„HTF“,sAlcTotal,0,oPC);

if (nWeaponType == BASE_ITEM_HEAVYCROSSBOW || nWeaponType == BASE_ITEM_LIGHTCROSSBOW)
{
oAmmunition = GetItemInSlot(INVENTORY_SLOT_BOLTS, oPC);
}
if (nWeaponType == BASE_ITEM_SLING)
{
oAmmunition = GetItemInSlot(INVENTORY_SLOT_BULLETS, oPC);
}

//SetLocalInt(oWeapon, „nGun“, nGunTest);
SetLocalInt(oAmmunition, „nGunBullet“, nGunBulletTest);
SetLocalInt(oWeapon, „nLoadStatus“, nGunWasLoaded);

// Armor-Encumbrance auf Spieler anwenden
SetLocalInt(oPC, „EQUIP_MODE“, nMode);
switch(nMode)
{
case 1: EffectArmorEncumbrance(OBJECT_SELF); return;
case 2: RemoveArmorEncumbrance(OBJECT_SELF); return;
}

////Anti-Cheat, damit nicht ein Toter importiert wird:
//int bCheatingDeath = GetPersistentInt(oPC, „IchBinTot“);
//if (bCheatingDeath == TRUE)
// {
//WP auf den Gefaengnisinseln definieren:
// object oWPKarzeri = GetWaypointByTag(„WP_KARZERI“);
// sWelcomeBack= ColorTokenRed() +(„Du solltest doch tot sein, oder, „) +sSpieler +(„?“) +ColorTokenEnd();
// AssignCommand(oPC, ActionJumpToObject(oWPKarzeri, FALSE));
// DelayCommand(4.0, FloatingTextStringOnCreature(sWelcomeBack, oPC, TRUE));
// }

//else if (bCheatingDeath == FALSE)
// {

// Falls schonmal mit Seelensein gespeichert wurde
// an den letzten Standort transportieren zum Weiterspielen

//aus der SQL-Datenbank den letzten Spieler-Standort abfragen
//(macht nichts, wenn dieser Wert fehlt, weil ohne Datenbank gespielt wird)
int bSoulUsed = GetPersistentInt(oPC, „SoulstoneUsed“);

if(bSoulUsed == TRUE)
{
location locPC = GetPersistentLocation(oPC, „LetzteLocation“);
SetLocalInt(oPC, „Teleported?“, 1);
AssignCommand(oPC, JumpToLocation(locPC));
}
DelayCommand(4.0, FloatingTextStringOnCreature(sWelcomeBack, oPC, TRUE));
// }

}

}

 

Als zweite Geschichte binde ich gerade „Wald ohne Wiederkehr“ wieder in die PW ein und bin dabei über einige Fehler gestolpert, die zu beheben ich mich nun bemühe.

Ein Levelaufstieg als Spielerbelohnung führte bei Chars über Stufe 40 dazu, dass sie faktisch heruntergelevelt wurden.

In „sicheren“ Rastgebieten kam es trotzdem zu Monsterüberfällen während der Nachtruhe. Unschön.

In der Nachwelt ist das Rasten nun nicht mehr möglich. Ich weiß, „Ruhe gibt’s genug nach dem Tod“ und so, aber das war so nicht vorgesehen.

Diverse Probleme mit Fraktionen bearbeitet.

Störend sind die trotz CEP 2.61 immer noch nicht behobenen Glitches im Sumpf-Plattenset— ich arbeite daran.

 

netzteil-fritzbox

Da macht man mal ein paar Tage computerlosen Urlaub, schon gibt zuhause das Netzteil der Fritzbox den Geist auf. Zum Glück hatte V. noch ein altes, sehr ähnliches Modell, dessen Netzteil jetzt als Notnagel herhalten muss.

Dabei wollte ich stolz verkünden: Content!, aber wenn Ihr den Server nicht erreichen könnt, könnt Ihr die erste vollständig überarbeitete Quest auch nicht spielen…