PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [G2] Variable Händlerpreise [req: Ikarus, floats]



qiaky
05.11.2013, 13:38
Wer sich mit der Preisbestimmung in Gothic 2 auseinander gesetzt hat ist früher oder später bestimmt über den Eintrag

const float TRADE_VALUE_MULTIPLIER = 0.15;

in der constants.d gestolpert.
Dieser bestimmt die Wertsenkung von Items beim Verkauf. 0.15 bedeutet z.B., dass der Spielercharakter Gegenstände mit einem Wert von 100 für 100 * 0.15 = 15 Gold verkauft.
Der Wert kann recht einfach geändert werden, constants.d öffnen, gewünschten Wert eintragen, speichern, Skripte parsen (Gothic.dat Kompilat erstellen).

Problem ist: Der Wert wird beim Start der Gothic2.exe einmalig von der Engine ausgelesen und dann wird der eingetragene Wert ignoriert. D.h um den Wert zu verändern würde es einen Gothic-Neustart brauchen und Skripte mit Statements wie

TRADE_VALUE_MULTIPLIER = NEW_VALUE;

sind hinfällig.

Mit den Ikarus Funktionen MEM_WriteInt und MEM_ReadInt kann der Wert auch während dem laufenden Spiel per Skript geändert werden.
Die benötigte Speicheradresse ist:

// TRADE_VALUE_MULTIPLIER ADDRESS:
const int Float__ENGINE_TRADEX = 11211032; //0x00AB1118

Auf diese kann bequem mit MEM_WriteInt und MEM_ReadInt zugegriffen werden.

MEM_WriteInt(Float__EngineMultiplier, NEW_MULTIPLIER);
MEM_ReadInt(Float__EngineMultiplier);

Nun handelt es sich aber bei der Wertübergabe jeweils um Integer, während der TRADE_VALUE_MULTIPLIER ja eine Kommazahl, eine sogenannte Float ist.
Zum Glück hat Sektenspinner Floats in Gothic implementiert. Da die reservierte Speicheradresse 32 bit groß ist, können wir per MEM_WriteInt auch einen 32 bit Float schreiben, solange der ByteCode (HexCode) stimmt. Das übernimmt die float-Funktion

CastToIntf(float)

für uns. Beim Lesen mit MEM_ReadInt ist die entsprechende Funktion

CastFromIntf(int)

Somit erhalten wir zwei Funktionen:


// TRADE_VALUE_MULTIPLIER ADDRESS:
const int Float__ENGINE_TRADEX = 11211032; //0x00AB1118

// Schreibt den neuen Wert NEW_MULTIPLIER an die Speicheradresse
func void SetTradeValueMultiplier(var float NEW_MULTIPLIER)
{
var int NEW_VALUE; NEW VALUE = CastToIntf(NEW_MULTIPLIER);
MEM_WriteInt(Float__EngineMultiplier, NEW_VALUE);
};

// fragt den Wert der Speicheradresse ab und übergibt ihn als Float
func float GetTradeValueMultiplier()
{
return CastFromIntf(MEM_ReadInt(Float__EngineMultiplier));
};

Somit kann man z.B. verschiedene Händler verschiedene Preise haben lassen (in den DIA_npc C_Info aufrufen) oder ein neues Talent für feilschen erstellen (bei der Talenterhöhung aufrufen).

Wichtig zu wissen ist, dass der gesetzte Wert für TRADE_VALUE_MULTIPLIER in der Adresse 0x00AB1118 nicht ins Savegame wandert. Deshalb ist eine Initialisierung des Wertes in der Init_Global() (startup.d) notwendig.

EDIT: Methode funktioniert nur für den angezeigten Wert des Gegenstandes, das übertragene Gold wird anderswo im Code berechnet, siehe hier und hier

Viel Spaß damit und danke an Sektenspinner und alle die an Ikarus mitgefeilt haben!

qiaky

EDIT: Hinweis auf Initialisierung in Init_Global() hinzugefügt.
EDIT2: Lösung für übertragenes Gold verlinkt.

Lehona
05.11.2013, 14:25
Etwas ähnliches (http://forum.worldofplayers.de/forum/threads/1126551-Skriptpaket-LeGo-2/page6?p=21130811&viewfull=1#post21130811) ist auch im LeGo-Thread verlinkt.

Zu deinem Script: Es ist wesentlich "besser", immer mit syntaktischen Integern (d.h. Floats, die in Integern stehen) zu arbeiten, da der Engine der Unterschied egal ist, der Parser aber bei fast allen Operationen auf Floats meckert. CastFromIntF() und CastToIntF() sind immer dann unnötig, sobald gerechnet werden muss, anstatt feste Werte anzugeben.
Außerdem treibt man es dem Anwender aus, syntaktische Floats zu benutzen, die sind nämlich doof :p
Ansonsten sieht es relativ solide aus, du solltest aber erwähnen, dass die Veränderung nicht im Savegame landet, d.h. bei einem Neustart von Gothic wird der alte Wert aus den Scripten geladen, bei dem Laden eines anderen Savegames wird der Wert nicht angepasst. Lösung: In der Init_Global() den jeweils aktuellsten Wert auslesen und in die .exe schreiben.

qiaky
05.11.2013, 22:40
Danke Lehona fürs drüberschauen, habe den Hinweis auf die Init_Global() eingefügt.

Jetzt aber zu meinem Problem:
Ich habe festgestellt, das mit meiner Methode zwar die Anzeige für die Gegenstandwerte im Handelsinventar angepasst werden, das Gold, welches man bei Verkauf erhält ist aber immernoch abhängig vom gesetzten TRADE_VALUE_MULTIPLIER Wert in der constants.d.
Hierfür wird anscheinend nicht der gespeicherte Wert in Adresse 0x00AB1118 benutzt.
Ich hab mal eine "Köderzahl" für TRADE_VALUE_MULTIPLIER eingesetzt.
0.68438 in HexCode unter Berücksichtigung von LittleEndian 0x87332F3F (32 bit)
bzw. 0xD82AC1E2 // 0x70E6E53F falls Gothic es in eine double (64 bit) konvertieren sollte.

Dann Gothic gestartet und den IDA-Debugger attached und nach dem Wert gesucht. Konnte allerdings nur den Eintrag in 0x00AB1118 finden.

Bin jetzt ein wenig ratlos.
Die zwei Möglichkeiten auf die ich komme sind:

1. ...,dass Gothic den Wert sämtlicher Iteminstanzen bei Spielstart berechnet und den TRADE_VALUE_MULTIPLIER danach verwirft?

2. ...,dass Gothic bei jeder Handelsaktion TransferLeft (also Verkauf) sich den Wert neu aus der Gothic.dat ausliest.

Da ich komplett neu im IDA-und Programmier-Gebiet bin kann ich der Assembleranweisungen in IDA nicht wirklich folgen.
Einzige Möglichkeit die ich sehe ist, die in Post #2 von Lehona verlinkte Methode. Also den Wert des Items mit MEM_ReadInt holen, mit gewünschtem Wert multiplizieren und per MEM_WriteInt wieder ans Spiel reichen.
Wäre nett wenn jemand mit Ahnung sich mal kurz dazu äußert.

Gruß
qiaky

NicoDE
06.11.2013, 10:37
edit: das wurde hier bereits gelöst: http://forum.worldofplayers.de/forum/showpost.php?p=14839446#post14839446

Der Wert wird auch die ganze Zeit im Händler-Inventar des Info-Managers gehalten.

oCItemContainer::GetValueMultiplier (0x007046F0) liest valueMultiplier (0x00AB1118) aus, soweit so gut und richtig.

oCItemContainer::DrawItemInfo (0x00706E40) hat eine (inline) Kopie von oCItemContainer::GetValueMultiplier (0x007073F4). Deswegen passen die angezeigten Verkaufswerte.

Aber die einzige andere Stelle, die oCItemContainer::GetValueMultiplier verwendet, ist oCViewDialogTrade::oCViewDialogTrade (0x0068ADB0):
.text:0068AFDC call oCItemContainer::GetValueMultiplier(void)
.text:0068AFE1 mov ecx, [esi+0F8h] // this->DlgInventoryNpc (oCViewDialogStealContainer*)
.text:0068AFE7 fstp dword ptr [ecx+10Ch] // DlgInventoryNpc->ValueMultiplier (zREAL)
und die einzige Verwendung findet man in oCViewDialogTrade::OnTransferLeft (0x0068B840):

.text:0068B9B4 mov eax, [edi+0F8h] // this->DlgInventoryNpc (oCViewDialogStealContainer*)
.text:0068B9BA fld dword ptr [eax+10Ch] // DlgInventoryNpc->ValueMultiplier (zREAL)
Der Konstruktor von oCViewDialogTrade wird in oCInformationManager::oCInformationManager (0x0065F930) verwendet:

.text:0065FA2D call oCViewDialogTrade::oCViewDialogTrade(void)
.text:0065FA48 mov [esi+18h], eax // this->DlgTrade (oCViewDialogTrade*)
Und von dem gibt es nur mgrInfos (0x00AAC458), welches von oCInformationManager::GetInformationManager (0x0065F790) zurückgegeben wird.

qiaky
08.11.2013, 21:00
Super. Vielen Dank für die ausführliche Erklärung. Blick beim Code mit den verschiedenen Registern zwar nicht ganz durch, aber das wesentliche hast du ja rausgeschrieben.