Finished GMST

actorid
Nicolay Korslund 15 years ago
parent 7b42ba6e20
commit 35ddfcfbf5

@ -22,6 +22,24 @@ enum Version
VER_13 = 0x3fa66666
};
enum FileType
{
FT_ESP = 0, // Plugin
FT_ESM = 1, // Master
FT_ESS = 32 // Savegame
};
// Used to mark special files. The original ESM files are given
// special treatment in a few places, most noticably in loading and
// filtering out "dirtly" GMST entries correctly.
enum SpecialFile
{
SF_Other,
SF_Morrowind,
SF_Tribunal,
SF_Bloodmoon
};
/* A structure used for holding fixed-length strings. In the case of
LEN=4, it can be more efficient to match the string as a 32 bit
number, therefore the struct is implemented as a union with an int.
@ -103,24 +121,6 @@ public:
typedef std::vector<MasterData> MasterList;
enum FileType
{
FT_ESP = 0, // Plugin
FT_ESM = 1, // Master
FT_ESS = 32 // Savegame
};
// Used to mark special files. The original ESM files are given
// special treatment in a few places, most noticably in loading and
// filtering out "dirtly" GMST entries correctly.
enum SpecialFile
{
SF_Other,
SF_Morrowind,
SF_Tribunal,
SF_Bloodmoon
};
/*************************************************************************
*
* Information retrieval

@ -0,0 +1,248 @@
#ifndef _ESM_GMST_H
#define _ESM_GMST_H
#include "esm_reader.hpp"
#include "defs.hpp"
namespace ESM {
/*
* Game setting, with automatic cleaning of "dirty" entries.
*
*/
struct GameSetting
{
std::string id;
// One of these is used depending on the variable type
std::string str;
int i;
float f;
VarType type;
// Set to true if this is a 'dirty' entry which should be ignored
bool dirty;
/*
These functions check if this game setting is one of the "dirty"
GMST records found in many mods. These are due to a serious bug in
the official TES3 editor. It only occurs in the newer editor
versions that came with Tribunal and Bloodmoon, and only if a
modder tries to make a mod without loading the corresponding
expansion master file. For example, if you have Tribunal installed
and try to make a mod without loading Tribunal.esm, the editor
will insert these GMST records as a replacement for the entries it
cannot find in the ESMs.
The values of these "dirty" records differ in general from their
values as defined in Tribunal.esm and Bloodmoon.esm, and are
always set to the same "default" values. Most of these values are
nonsensical, ie. changing the "Seller Max" string to "Max Sale",
or change the stats of werewolves to useless values like 1. Some
of them break certain spell effects.
It is most likely that these values are just leftover values from
an early stage of development that are inserted as default values
by the editor code. They are supposed to be overridden when the
correct esm file is loaded. When it isn't loaded however, you get
stuck with the initial value, and this gets written to every mod
by the editor, for some reason.
Bethesda themselves have fallen for this bug. If you install both
Tribunal and Bloodmoon, the updated Tribunal.esm will contain the
dirty GMST settings from Bloodmoon, and Bloodmoon.esm will contain
some of the dirty settings from Tribunal. In other words, this bug
affects the game EVEN IF YOU DO NOT USE ANY MODS!
The guys at Bethesda are well aware of this bug (and many others),
as the mod community and fan base complained about them for a long
time. But unfortunately it was never fixed.
There are several tools available to help modders remove these
records from their files, but not all modders use them, and they
really shouldn't have to. In this file we choose instead to reject
all the corrupt values at load time.
These functions checks if the current game setting is one of the
"dirty" ones as described above. TODO: I have not checked this
against other sources yet, do that later. Currently recognizes 22
values for tribunal and 50 for bloodmoon. Legitimate GMSTs in mods
(setting values other than the default "dirty" ones) are not
affected and will work correctly.
*/
// Some handy macros
#define cI(s,x) { if(id == (s)) return (i == (x)); }
#define cF(s,x) { if(id == (s)) return (f == (x)); }
#define cS(s,x) { if(id == (s)) return (str == (x)); }
/*
Checks for dirty tribunal values. These will be ignored if found
in any file except when they are found in "Tribunal.esm".
*/
bool isDirtyTribunal()
{
/*
Here, id contains the game setting name, and we check the
setting for certain values. If it matches, this is a "dirty"
entry. The correct entry (as defined in Tribunal and Bloodmoon
esms) are given in the comments. Many of the values are correct,
and are marked as 'same'. We still ignore them though, as they
are still in the wrong file and might override custom values
from other mods.
*/
// Strings
cS("sProfitValue", "Profit Value"); // 'Profit:'
cS("sEditNote", "Edit Note"); // same
cS("sDeleteNote", "Delete Note?"); // same
cS("sMaxSale", "Max Sale"); // 'Seller Max'
cS("sMagicFabricantID", "Fabricant"); // 'Fabricant_summon'
cS("sTeleportDisabled",
"Teleportation magic does not work here.");// same
cS("sLevitateDisabled",
"Levitation magic does not work here."); // same
cS("sCompanionShare", "Companion Share"); // 'Share'
cS("sCompanionWarningButtonOne",
"Let the mercenary quit."); // same
cS("sCompanionWarningButtonTwo",
"Return to Companion Share display."); // same
cS("sCompanionWarningMessage",
"Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value.");
// 'Your mercenary is poorer now than when he contracted with
// you. Your mercenary will quit if you do not give him gold
// or goods to bring his Profit to a positive value.'
// [The difference here is "Profit Value" -> "Profit"]
// Strings that matches the id
cS("sEffectSummonFabricant", id);// 'Summon Fabricant'
return false;
}
// Bloodmoon variant
bool isDirtyBloodmoon()
{
// Strings
cS("sWerewolfPopup", "Werewolf"); // same
cS("sWerewolfRestMessage",
"You cannot rest in werewolf form."); // same
cS("sWerewolfRefusal",
"You cannot do this as a werewolf."); // same
cS("sWerewolfAlarmMessage",
"You have been detected changing from a werewolf state.");
// 'You have been detected as a known werewolf.'
// Strings that matches the id
cS("sMagicCreature01ID", id); // 'BM_wolf_grey_summon'
cS("sMagicCreature02ID", id); // 'BM_bear_black_summon'
cS("sMagicCreature03ID", id); // 'BM_wolf_bone_summon'
cS("sMagicCreature04ID", id); // same
cS("sMagicCreature05ID", id); // same
cS("sEffectSummonCreature01", id); // 'Calf Wolf'
cS("sEffectSummonCreature02", id); // 'Calf Bear'
cS("sEffectSummonCreature03", id); // 'Summon Bonewolf'
cS("sEffectSummonCreature04", id); // same
cS("sEffectSummonCreature05", id); // same
// Integers
cI("iWereWolfBounty", 10000); // 1000
cI("iWereWolfFightMod", 100); // same
cI("iWereWolfFleeMod", 100); // same
cI("iWereWolfLevelToAttack", 20); // same
// Floats
cF("fFleeDistance", 3000); // same
cF("fCombatDistanceWerewolfMod", 0.3); // same
cF("fWereWolfFatigue", 400); // same
cF("fWereWolfEnchant", 1); // 0
cF("fWereWolfArmorer", 1); // 0
cF("fWereWolfBlock", 1); // 0
cF("fWereWolfSneak", 1); // 95
cF("fWereWolfDestruction", 1); // 0
cF("fWereWolfEndurance", 150); // same
cF("fWereWolfConjuration", 1); // 0
cF("fWereWolfRestoration", 1); // 0
cF("fWereWolfAthletics", 150); // 50
cF("fWereWolfLuck", 1); // 25
cF("fWereWolfSilverWeaponDamageMult", 1.5); // 2
cF("fWereWolfMediumArmor", 1); // 0
cF("fWereWolfShortBlade", 1); // 0
cF("fWereWolfAcrobatics", 150); // 80
cF("fWereWolfSpeechcraft", 1); // 0
cF("fWereWolfAlteration", 1); // 0
cF("fWereWolfIllusion", 1); // 0
cF("fWereWolfLongBlade", 1); // 0
cF("fWereWolfMarksman", 1); // 0
cF("fWereWolfHandtoHand", 100); // same
cF("fWereWolfIntellegence", 1); // 0
cF("fWereWolfAlchemy", 1); // 0
cF("fWereWolfUnarmored", 100); // same
cF("fWereWolfAxe", 1); // 0
cF("fWereWolfRunMult", 1.5); // 1.3
cF("fWereWolfMagicka", 100); // same
cF("fWereWolfAgility", 150); // same
cF("fWereWolfBluntWeapon", 1); // 0
cF("fWereWolfSecurity", 1); // 0
cF("fWereWolfPersonality", 1); // 0
cF("fWereWolfMerchantile", 1); // 0
cF("fWereWolfHeavyArmor", 1); // 0
cF("fWereWolfSpear", 1); // 0
cF("fWereWolfStrength", 150); // same
cF("fWereWolfHealth", 2); // same
cF("fWereWolfMysticism", 1); // 0
cF("fWereWolfLightArmor", 1); // 0
cF("fWereWolfWillPower", 1); // 0
cF("fWereWolfSpeed", 150); // 90
return false;
}
#undef cI
#undef cF
#undef cS
void load(ESMReader &esm)
{
dirty = false;
// We are apparently allowed to be empty
if(!esm.hasMoreSubs())
{
type = VT_None;
return;
}
// Load some data
esm.getSubName();
NAME n = esm.retSubName();
if(n == "STRV")
{
str = esm.getHString();
type = VT_String;
}
else if(n == "INTV")
{
esm.getHT(i);
type = VT_Int;
}
else if(n == "FLTV")
{
esm.getHT(f);
type = VT_Float;
}
else
esm.fail("Unwanted subrecord type");
int spf = esm.getSpecial();
// Check if this is one of the dirty values mentioned above. If it
// is, we set the dirty flag. This will ONLY work if you've set
// the 'id' string correctly before calling load().
if( ( spf != SF_Tribunal && isDirtyTribunal() ) ||
( spf != SF_Bloodmoon && isDirtyBloodmoon() ) )
dirty = true;
}
};
}
#endif

@ -17,6 +17,7 @@
#include "loadench.hpp"
#include "loadfact.hpp"
#include "loadglob.hpp"
#include "loadgmst.hpp"
#include "loadingr.hpp"
#include "loadligh.hpp"
#include "loadlocks.hpp"
@ -62,6 +63,7 @@ enum RecNameInts
REC_GMST = 0x54534d47,
REC_INFO = 0x4f464e49,
REC_INGR = 0x52474e49,
REC_LAND = 0x0,
REC_LEVC = 0x4356454c,
REC_LEVI = 0x4956454c,
REC_LIGH = 0x4847494c,
@ -71,6 +73,7 @@ enum RecNameInts
REC_MISC = 0x4353494d,
REC_NPC_ = 0x5f43504e,
REC_NPCC = 0x4343504e,
REC_PGRC = 0x0,
REC_PROB = 0x424f5250,
REC_RACE = 0x45434152,
REC_REGN = 0x4e474552,

@ -1,313 +0,0 @@
/*
OpenMW - The completely unofficial reimplementation of Morrowind
Copyright (C) 2008 Nicolay Korslund
Email: < korslund@gmail.com >
WWW: http://openmw.snaptoad.com/
This file (loadgmst.d) is part of the OpenMW package.
OpenMW is distributed as free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 3, as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
version 3 along with this program. If not, see
http://www.gnu.org/licenses/ .
*/
module esm.loadgmst;
import esm.imports;
/*
* Game setting
*/
// TODO: It's likely that we don't need this struct any longer, given
// that game settings are now stored in Monster code. We will still
// use the loading code and the dirty value cleaning code of course,
// but there's no longer any need to store it in a separate lookup
// list, since Monster variables can be looked up just as fast.
struct GameSetting
{
LoadState state;
char[] id;
union
{
char[] str;
int i;
float f;
}
VarType type;
// These functions check if this game setting is one of the "dirty"
// GMST records found in many mods. These are due to a serious bug
// in the official TES3 editor. It only occurs in the newer editor
// versions that came with Tribunal and Bloodmoon, and only if a
// modder tries to make a mod without loading the corresponding
// expansion master file. For example, if you have Tribunal
// installed and try to make a mod without loading Tribunal.esm, the
// editor will insert these GMST records.
// The values of these "dirty" records differ in general from their
// values as defined in Tribunal.esm and Bloodmoon.esm, and are
// always set to the same "default" values. Most of these values are
// nonsensical, ie. changing the "Seller Max" string to "Max Sale",
// or change the stats of werewolves to useless values like 1. Some
// of them break certain spell effects.
// It is most likely that these values are just leftover values from
// an early stage of development that are inserted as default values
// by the editor code. They are supposed to be overridden when the
// correct esm file is loaded. When it isn't loaded, you get stuck
// with the initial value, and this gets written to every mod for
// some reason.
// Bethesda themselves have fallen for this bug. If you install both
// Tribunal and Bloodmoon, the updated Tribunal.esm will contain the
// dirty GMST settings from Bloodmoon, and Bloodmoon.esm will
// contain some of the dirty settings from Tribunal. In other words,
// this bug affects the game EVEN IF YOU DO NOT USE ANY MODS!
// The guys at Bethesda are well aware of this bug (and many
// others), as the mod community and fan base complained about them
// for a long time. But it was never fixed.
// There are several programs available to help modders remove these
// records from their files, but not all modders use them, and they
// really shouldn't have to. In this file we choose instead to
// reject all the corrupt values at load time.
// These functions checks if the current game setting is one of the
// "dirty" ones as described above. TODO: I have not checked this
// against other sources yet, do that later. Currently recognizes 22
// values for tribunal and 50 for bloodmoon. Legitimate GMSTs in
// mods (setting values other than the default "dirty" ones) are not
// affected and will work correctly.
// Checks for dirty tribunal values. These will be ignored if found
// in any file except when they are found in "Tribunal.esm".
bool isDirtyTribunal()
{
bool result = false;
void cI(int ii) { if(ii == i) result = true; }
void cF(float ff) { if(ff == f) result = true; }
void cS(char[] ss) { if(ss == str) result = true; }
// Here, id contains the game setting name, and we check the
// setting for certain values. If it matches, this is a "dirty"
// entry. The correct entry (as defined in Tribunal and Bloodmoon
// esms) are given in the comments. Many of the values are
// correct, and are marked as 'same'. We still ignore them though,
// as they are still in the wrong file and might override custom
// values from other mods.
switch(id)
{
// Strings
case "sProfitValue": cS("Profit Value"); break; // 'Profit:'
case "sEditNote": cS("Edit Note"); break; // same
case "sDeleteNote": cS("Delete Note?"); break; // same
case "sMaxSale": cS("Max Sale"); break; // 'Seller Max'
case "sMagicFabricantID": cS("Fabricant"); break; // 'Fabricant_summon'
case "sTeleportDisabled": cS("Teleportation magic does not work here.");
break; // same
case "sLevitateDisabled": cS("Levitation magic does not work here.");
break; // same
case "sCompanionShare": cS("Companion Share"); break; // 'Share'
case "sCompanionWarningButtonOne": cS("Let the mercenary quit.");
break; // same
case "sCompanionWarningButtonTwo": cS("Return to Companion Share display."); break; // same
case "sCompanionWarningMessage": cS("Your mercenary is poorer now than when he contracted with you. Your mercenary will quit if you do not give him gold or goods to bring his Profit Value to a positive value."); break;
// 'Your mercenary is poorer now than when he contracted with
// you. Your mercenary will quit if you do not give him gold
// or goods to bring his Profit to a positive value.'
// [The difference here is "Profit Value" -> "Profit"}
// Strings that matches the id
case "sEffectSummonFabricant": // 'Summon Fabricant'
cS(id);
break;
default:
}
return result;
}
// Bloodmoon variant
bool isDirtyBloodmoon()
{
bool result = false;
void cI(int ii) { if(ii == i) result = true; }
void cF(float ff) { if(ff == f) result = true; }
void cS(char[] ss) { if(ss == str) result = true; }
switch(id)
{
// Strings
case "sWerewolfPopup": cS("Werewolf"); break; // same
case "sWerewolfRestMessage": cS("You cannot rest in werewolf form.");
break; // same
case "sWerewolfRefusal": cS("You cannot do this as a werewolf.");
break; // same
case "sWerewolfAlarmMessage": cS("You have been detected changing from a werewolf state."); break;
// 'You have been detected as a known werewolf.'
// Strings that matches the id
case "sMagicCreature01ID": // 'BM_wolf_grey_summon'
case "sMagicCreature02ID": // 'BM_bear_black_summon'
case "sMagicCreature03ID": // 'BM_wolf_bone_summon'
case "sMagicCreature04ID": // same
case "sMagicCreature05ID": // same
case "sEffectSummonCreature01": // 'Calf Wolf'
case "sEffectSummonCreature02": // 'Calf Bear'
case "sEffectSummonCreature03": // 'Summon Bonewolf'
case "sEffectSummonCreature04": // same
case "sEffectSummonCreature05": // same
cS(id);
break;
// Integers
case "iWereWolfBounty": cI(10000); break; // 1000
case "iWereWolfFightMod": cI(100); break; // same
case "iWereWolfFleeMod": cI(100); break; // same
case "iWereWolfLevelToAttack": cI(20); break; // same
// Floats
case "fFleeDistance": cF(3000); break; // same
case "fCombatDistanceWerewolfMod": cF(0.3); break; // same
case "fWereWolfFatigue": cF(400); break; // same
case "fWereWolfEnchant": cF(1); break; // 0
case "fWereWolfArmorer": cF(1); break; // 0
case "fWereWolfBlock": cF(1); break; // 0
case "fWereWolfSneak": cF(1); break; // 95
case "fWereWolfDestruction": cF(1); break; // 0
case "fWereWolfEndurance": cF(150); break; // same
case "fWereWolfConjuration": cF(1); break; // 0
case "fWereWolfRestoration": cF(1); break; // 0
case "fWereWolfAthletics": cF(150); break; // 50
case "fWereWolfLuck": cF(1); break; // 25
case "fWereWolfSilverWeaponDamageMult": cF(1.5); break; // 2
case "fWereWolfMediumArmor": cF(1); break; // 0
case "fWereWolfShortBlade": cF(1); break; // 0
case "fWereWolfAcrobatics": cF(150); break; // 80
case "fWereWolfSpeechcraft": cF(1); break; // 0
case "fWereWolfAlteration": cF(1); break; // 0
case "fWereWolfIllusion": cF(1); break; // 0
case "fWereWolfLongBlade": cF(1); break; // 0
case "fWereWolfMarksman": cF(1); break; // 0
case "fWereWolfHandtoHand": cF(100); break; // same
case "fWereWolfIntellegence": cF(1); break; // 0
case "fWereWolfAlchemy": cF(1); break; // 0
case "fWereWolfUnarmored": cF(100); break; // same
case "fWereWolfAxe": cF(1); break; // 0
case "fWereWolfRunMult": cF(1.5); break; // 1.3
case "fWereWolfMagicka": cF(100); break; // same
case "fWereWolfAgility": cF(150); break; // same
case "fWereWolfBluntWeapon": cF(1); break; // 0
case "fWereWolfSecurity": cF(1); break; // 0
case "fWereWolfPersonality": cF(1); break; // 0
case "fWereWolfMerchantile": cF(1); break; // 0
case "fWereWolfHeavyArmor": cF(1); break; // 0
case "fWereWolfSpear": cF(1); break; // 0
case "fWereWolfStrength": cF(150); break; // same
case "fWereWolfHealth": cF(2); break; // same
case "fWereWolfMysticism": cF(1); break; // 0
case "fWereWolfLightArmor": cF(1); break; // 0
case "fWereWolfWillPower": cF(1); break; // 0
case "fWereWolfSpeed": cF(150); break; // 90
default:
}
return result;
}
char[] toString()
{
if(type == VarType.Int) return format(i);
else if(type == VarType.Float) return format(f);
else if(type == VarType.String) return str;
else if(type == VarType.None) return "(no value)";
assert(0);
}
void load()
{
// We are apparently allowed not to have any value at all.
if(!esFile.hasMoreSubs())
{
if(state == LoadState.Previous)
writefln("Warning: Overwriting game setting %s with void value", id);
type = VarType.None;
return;
}
// If this is not the first record of this type to be loaded, then
// we do a little "trick" to avoid overwriting the current data
// with one of the dirty GMSTs.
if(state == LoadState.Previous)
{
// Load the data in a temporary game setting instead
GameSetting g;
g.state = LoadState.Unloaded;
g.id = id;
g.load();
// Only copy it if it was valid
if(g.type != VarType.Ignored)
{
// Don't allow a change of type, unless the setting we are
// overwriting is an ignored value.
if(g.type != type && type != VarType.Ignored)
esFile.fail(format("GMST changed type from %d to %d",
cast(int)type, cast(int)g.type));
str = g.str; // This will copy all the data no matter what
// type it is
}
return;
}
// Actually load some data
esFile.getSubName();
switch(esFile.retSubName())
{
case "STRV":
str = esFile.getHString();
type = VarType.String;
break;
case "INTV":
i = esFile.getHInt();
type = VarType.Int;
break;
case "FLTV":
f = esFile.getHFloat();
type = VarType.Float;
break;
default:
esFile.fail("Unwanted subrecord type");
}
SpecialFile spf = esFile.getSpecial();
// Check if this is one of the dirty values mentioned above. If it
// is, we set the VarType to Ignored. This leaves the posibility
// that this becomes the final var type, for example if you load a
// plugin with tribunal-gmst settings without loading tribunal
// first. (Then there would only exist dirty values for this
// settings, no "real" value.) But this is not likely a problem,
// since these empty values will never be used and we can check
// for them in any case.
if( ( spf != SpecialFile.Tribunal && isDirtyTribunal() ) ||
( spf != SpecialFile.Bloodmoon && isDirtyBloodmoon() ) )
type = VarType.Ignored;
}
}
ListID!(GameSetting) gameSettings;
Loading…
Cancel
Save