Jump to content

ESP: The beginning and the modmerging system [S2G/CP1.6/EE2.1/SS,C]


Recommended Posts

I quickly threw together a tool for scaling models in the creatures.txt: Sacred 2 Modelscale.zip

Normal mobs scaling are defined by not being "monstertype = 1" || "monstertype = 2" || or have behaviour strings like "JOB" and "quest". Its hard to make a good barrier around normal mobs, as this also seems to affect player summons. So tell me if there are identifiable models which should be excluded.

Elites are defined as "monstertype = 1". Again, if you think something should be included or excluded give me a call.

Bosses are defined as "monstertype = 2". Same song here.

You use the tool by copying over the creatuers.txt you want to change into the exe folder, example file is provided, and then run the exe.

Dont know if the program works on other pcs.

Source Code is included.

Unbenannt2.thumb.png.c70654f632276b5b8473af90ec2f4f13.png

Edited by Charon117
  • Like! 1
Link to comment

Neat.  I'll test it tonight.  I should note there may be about a dozen or so elites who intentionally use a scale smaller than 1.0000 because they're simply supposed to be small.  Various rats, spiders, elementals (remember there are "small" versions of poison, earth, and fire), dracolins, probably others.

Link to comment

Update:

I finished working on the Eliza.txt and will start working on the environment.txt. Since the collision.txt does not have any unique identifier to merge or overwrite I made the whole file copyable only.

Code:

 

int MergeFileEliza(std::string InputDirA,std::string InputDirB, std::string FileNameA)

{

std::cout << "\"" << FileNameA << "\" is being scanned right now. ";



std::ifstream IStreamA (InputDirA);

std::vector <std::string> VFileA;

std::string SFileA;

while (std::getline(IStreamA, SFileA)){

VFileA.push_back(SFileA);

}

IStreamA.close();



std::ifstream IStreamB (InputDirB);

std::vector <std::string> VFileB;

std::string SFileB;

while (std::getline(IStreamB, SFileB)){

VFileB.push_back(SFileB);

}

IStreamB.close();



std::vector<std::string>::iterator iVA = VFileA.begin();

unsigned long long iVA2 = 0;

std::vector<std::string> VKeywords; //since the container wont change I will use array[] next time, I swear.

VKeywords.push_back("mgr.addDlgGroupDlg"); VKeywords.push_back("mgr.addDlgVoices"); VKeywords.push_back("mgr.addDlgAdvanceVoices"); VKeywords.push_back("mgr.addDlgFactionVoices"); VKeywords.push_back("mgr.addDlgCombatVoices");

std::vector<std::string> VKeywords2;

VKeywords2.push_back("group ="); VKeywords2.push_back("race =");

std::string SCurrentLine;

std::string SUniqueIdentifier;

std::vector<std::string>::iterator iVA3; // I will also improve my iterator naming for the next batch of code. Promise.

std::vector<std::string>::iterator iVA4;

std::vector<std::string>::iterator iVA5;

std::vector<std::string>::reverse_iterator iVA6;

std::vector<std::string>::iterator iVA7;

std::vector<std::string>::iterator iVA8;

std::vector<std::string>::iterator iVA9;



while(iVA != VFileA.end()){

iVA3 = VKeywords.begin();



while(iVA3 != VKeywords.end()){



if(VFileA[iVA2].find(VKeywords[std::distance(VKeywords.begin(), iVA3)]) != std::string::npos){



if(VFileA[iVA2].find(VKeywords[0]) != std::string::npos){

iVA7 = iVA;



while(iVA7 != VFileA.end()){

if(VFileA[std::distance(VFileA.begin(), iVA7)].find(";") != std::string::npos){

break;

}



++iVA7;

}



if(std::find(VFileB.begin(), VFileB.end(), VFileA[std::distance(VFileA.begin(), iVA)]) != VFileB.end()){

iVA8 = std::find(VFileB.begin(), VFileB.end(), VFileA[std::distance(VFileA.begin(), iVA)]);

iVA9 = iVA8;



while(iVA9 != VFileB.end()){

if(VFileB[std::distance(VFileB.begin(), iVA9)].find(";") != std::string::npos){

break;

}



++iVA9;

}



iVA8 = VFileB.erase(iVA8, iVA9 + 1);

VFileB.insert(iVA8, iVA, iVA7 + 1);



}

else{

iVA6 = std::find_if(VFileB.rbegin(), VFileB.rend(), [](const std::string str){return str.find(";") != std::string::npos;});

VFileB.insert(iVA6.base(), iVA, iVA7 + 1);

}



break;

}



iVA4 = VKeywords2.begin();



while(iVA4 != VKeywords2.end()){

if(VFileA[iVA2].find(VKeywords2[std::distance(VKeywords2.begin(), iVA4)]) != std::string::npos){

SCurrentLine = VFileA[iVA2];

std::istringstream SSCurrentLine2(SCurrentLine);

SSCurrentLine2.seekg(VFileA[iVA2].find(VKeywords2[std::distance(VKeywords2.begin(), iVA4)]));

SSCurrentLine2.ignore(100, '"');

std::getline(SSCurrentLine2, SUniqueIdentifier, '"');

SUniqueIdentifier.insert(0, " \"");

SUniqueIdentifier.insert(0, VKeywords2[std::distance(VKeywords2.begin(), iVA4)]);

SUniqueIdentifier.append("\"");



if(std::find_if(VFileB.begin(), VFileB.end(),

[&VKeywords, &SUniqueIdentifier, &iVA3](const std::string str) {return bool(bool(str.find(VKeywords[std::distance(VKeywords.begin(), iVA3)]) != std::string::npos))

&& bool(str.find(SUniqueIdentifier) != std::string::npos);}) != VFileB.end()){

iVA5 = std::find_if(VFileB.begin(), VFileB.end(),

[&VKeywords, &SUniqueIdentifier, &iVA3](const std::string str) {return bool(bool(str.find(VKeywords[std::distance(VKeywords.begin(), iVA3)]) != std::string::npos))

&& bool(str.find(SUniqueIdentifier) != std::string::npos);});

std::swap(VFileA[std::distance(VFileA.begin(), iVA)], VFileB[std::distance(VFileB.begin(), iVA5)]);

}

else{

iVA6 = std::find_if(VFileB.rbegin(), VFileB.rend(), [&VKeywords, &iVA3](const std::string str){return str.find(VKeywords[std::distance(VKeywords.begin(), iVA3)]) != std::string::npos;});

VFileB.insert(iVA6.base(), iVA, iVA + 1);

}

}



++iVA4;

}

}



++iVA3;

}



++iVA;

++iVA2;

}



//std::cout << "exited the loop" << std::endl;



std::ofstream OStream(InputDirB);

for (std::vector<std::string>::iterator I = VFileB.begin(); I != VFileB.end(); ++I){

//std::cout << *I << std::endl;

OStream << *I << std::endl;

}

OStream.close();

std::cout << "\"" << FileNameA << "\" has been scanned and merged for elements found." << std::endl;



return 0;

}

 

 

Edit: environment.txt has a working modmerge code now.

Code:

 

int MergeFileEnvironment(std::string InputDirA,std::string InputDirB, std::string FileNameA)

{

std::cout << "\"" << FileNameA << "\" is being scanned right now. ";



std::ifstream IStreamA (InputDirA);

std::vector <std::string> VFileA;

std::string SFileA;

while (std::getline(IStreamA, SFileA)){

VFileA.push_back(SFileA);

}

IStreamA.close();



std::ifstream IStreamB (InputDirB);

std::vector <std::string> VFileB;

std::string SFileB;

while (std::getline(IStreamB, SFileB)){

VFileB.push_back(SFileB);

}

IStreamB.close();



std::vector<std::string> VKeywords = {"local TYPE_WATER", "local TYPE_LAVA", "local TYPE_PLASMA", "local TYPE_TENERGY"};



for(auto itVK = VKeywords.begin(); itVK != VKeywords.end(); itVK++){

auto itVA2 = std::find_if(VFileA.begin(), VFileA.end(), [&VKeywords, &itVK](const std::string str){return str.find(VKeywords[std::distance(VKeywords.begin(), itVK)]) != std::string::npos;});



if(itVA2 != VFileA.end()){

auto itVB3 = std::find_if(VFileB.begin(), VFileB.end(), [&VKeywords, &itVK](const std::string str){return str.find(VKeywords[std::distance(VKeywords.begin(), itVK)]) != std::string::npos;});

if(itVB3 != VFileB.end()){

std::swap(VFileA[std::distance(VFileA.begin(), itVA2)], VFileB[std::distance(VFileB.begin(), itVB3)]);

}

}

}



for(auto itVA = VFileA.begin(); itVA != VFileA.end(); itVA++){



if(VFileA[std::distance(VFileA.begin(), itVA)].find("mgr.waterTypeCreate") != std::string::npos){



for(auto itVA2 = itVA; itVA2 != VFileA.begin(); --itVA2){



if(VFileA[std::distance(VFileA.begin(), itVA2)].find("newWaterType =") != std::string::npos){



if(std::find_if(VFileB.begin(), VFileB.end(), [&VFileA, &itVA](const std::string str){return str.find(VFileA[std::distance(VFileA.begin(), itVA)]) != std::string::npos;}) != VFileB.end()){

auto itVB3 = std::find_if(VFileB.begin(), VFileB.end(), [&VFileA, &itVA](const std::string str){return str.find(VFileA[std::distance(VFileA.begin(), itVA)]) != std::string::npos;});



auto itVB4 = itVB3;

for(; itVB4 != VFileB.begin(); --itVB4){

if(VFileB[std::distance(VFileB.begin(), itVB4)].find("newWaterType =") != std::string::npos){



break;

}

}

itVB4 = VFileB.erase(itVB4, itVB3 + 1);

VFileB.insert(itVB4, itVA2, itVA + 1);

}



else{

auto itVB5 = std::find_if(VFileB.rbegin(), VFileB.rend(), [](const std::string str){return str.find("mgr.waterTypeCreate") != std::string::npos;});

VFileB.insert(itVB5.base() + 1, itVA2, itVA + 1);

}



break;

}

}

}

}



std::ofstream OStream(InputDirB);

for (std::vector<std::string>::iterator I = VFileB.begin(); I != VFileB.end(); ++I){

//std::cout << *I << std::endl;

OStream << *I << std::endl;

}

OStream.close();



std::cout << "\"" << FileNameA << "\" has been scanned and merged for elements found." << std::endl;



return 0;

}

 

 

The next files are

  • keycodes.txt
  • minitype.txt
  • patches.txt
  • poidata.txt

If there is anything I need to know about these files than please inform me about it.

Edit 2: Keycodes Code has been written.

Code:

 

int MergeFileKeycodes(std::string InputDirA,std::string InputDirB, std::string FileNameA)

{

cout << "\"" << FileNameA << "\" is being scanned right now. ";



std::ifstream IStreamA(InputDirA); //This method is x7 times slower than a reasonable fast solution.

std::string SFileA((std::istreambuf_iterator<char>(IStreamA)),

std::istreambuf_iterator<char>());

IStreamA.close();



std::ifstream IStreamB(InputDirB);

std::string SFileB((std::istreambuf_iterator<char>(IStreamB)),

std::istreambuf_iterator<char>());

IStreamB.close();



std::istringstream SStreamFileA(SFileA); //naming them so SSFileA and SFileA so similar is a potential source for bugs ?

SStreamFileA.ignore(10000, '{');



for(std::string SFileASubstring = ""; SFileASubstring.find("}") == std::string::npos;){

SFileASubstring = "";

std::getline(SStreamFileA, SFileASubstring, ',');



if(SFileASubstring.find('}') != std::string::npos){

break;

}



if(SFileASubstring.find("=") != std::string::npos){



SFileASubstring.erase(std::remove_if(SFileASubstring.begin(), SFileASubstring.end(), isspace),SFileASubstring.end()); // dont understand why removing whitespaces is so difficult. Isnt std::replace() easier to use ?

std::istringstream SStreamFileASub(SFileASubstring);

std::string SFileAKeyword;

std::getline(SStreamFileASub, SFileAKeyword, '=');

std::string SFileAKeycode;

SStreamFileASub >> SFileAKeycode;



SFileAKeyword = ' ' + SFileAKeyword + ' ';

if(unsigned int ICounter = SFileB.find(SFileAKeyword) != std::string::npos){ //initiating ICounter in a for() statement doesnt work, it will always return 1. Propably because it thinks its a bool. Right ?

ICounter = SFileB.find(SFileAKeyword);



unsigned int ICounter2 = ICounter;

for(; SFileB[ICounter2] != ','; ++ICounter2){}



SFileB.replace(ICounter, ICounter2 - ICounter, SFileAKeyword + "= " + SFileAKeycode);



}

else{ //still better than regex

SFileAKeyword.erase(0,1);

SFileAKeyword = '\t' + SFileAKeyword;

if(unsigned int ICounter = SFileB.find(SFileAKeyword) != std::string::npos){

ICounter = SFileB.find(SFileAKeyword);



unsigned int ICounter2 = ICounter;

for(; SFileB[ICounter2] != ','; ++ICounter2){}



SFileB.replace(ICounter, ICounter2 - ICounter, SFileAKeyword + "= " + SFileAKeycode);

}

else{

ICounter = SFileB.find('}');

SFileB.insert(ICounter - 1, "\n" + SFileAKeyword + "= " + SFileAKeycode + ',' );

}

}

}

}



std::ofstream OStream(InputDirB);

OStream << SFileB;

OStream.close();



std::cout << "\"" << FileNameA << "\" has been scanned and merged for elements found." << std::endl;



return 0;

}

 

 

Edit 3: minitype.txt code has been finished.

Edit 4: patches.txt code has been finished.

Edited by Charon117
Link to comment

@Flix Does it work ?

 

Someone somewhere wrote that there might be an AI limitation, and that not more than 5 units can attack the player at teh same time. All units which are not allowed to attack the player would then stand and look around for the hanging gardens of babylon. I, however, didnt find this to be true. I can confirm that at least 40 elite stone throwing monsters can attack the player at the same time. However, melee units indeed show this kind of behaviour as soon as all attacking spots around the characaters model are filled up. Then they dont attack for a substantial amount of time, or until they get attacked.

On a side note this might be interesting for dimitrius to look into, as ranged AI indeed shows signs of stopping to attack the player if they attacked for a certain period of time. Examples of this would be the stone throwing monsters, which stop shooting after a certain amount of time to leave the player alone for some time, then reengage. Melee units show this behaviour when their pathfinding doesnt find a spot big enough for the unit to attack the player.

Im bringing this up because model size directly impacts how dangerous a unit can be. The bigger the model, the more difficulties an enemy melee unit has to find a valid spot to attack. The bigger the ranged model the fewer ranged units can stack up and attack the player. Etc ... . Making units bigger can and will make the game easier, while making models smaller will make every situation more dangerous because more units can potentially attack the player.

Apart from that the movement speed could also need some revamping, making less dangerous enemies faster than they currently are, while making other dangerous ranged enemies like the DOT poison spiting spider slower.

I will work on a "model size" and "movement speed" overhaul of the units in the game, and proper tools to port such changes across builds.

Questions for Flix:

1. Where do I find the speed of units ?
2. Is there any connection between what I read an enemys name as ingame, and game files, or do I need to search in good faith ?

 

What do you think ?

Link to comment
25 minutes ago, Charon117 said:

1. Where do I find the speed of units ?

creatureinfo.txt, speed is defined by itemtype, which means all creatures that use that itemtype would be affected.  The lines are:

  walkSpeed    = 100,
  runSpeed     = 200,

27 minutes ago, Charon117 said:

2. Is there any connection between what I read an enemys name as ingame, and game files, or do I need to search in good faith ?

Loosely.  If you want to be sure use S2rw and plug in the LokaID such as CREATURE_1980 and it will generate the hash which you can check in decoded global.res for the in-game name.

  • Thanks! 1
Link to comment

man Shirkas ending sucks.

 

 

Edit: The next files are:

  • relations.txt
  • soundcluster.txt
  • soundprofile.txt
  • soundresources.txt

If there is anything I need to know about these files please inform me about it.

 

Right at the beginning of relations.txt we have some double entries.

Quote

-- to Thief (thief/poacher)
relation = {
    job1 = "JOB_BARD",
    job2 = "JOB_THIEF",
    relation = "I"
}
mgr.addrelation(relation);

relation = {
    job1 = "JOB_BARD",
    job2 = "JOB_THIEF",
    relation = "S"
}
mgr.addrelation(relation);

Can anyone shed light on this ?

Edit 2: Same goes for the soundcluster.txt, there is one dublicate.

Quote

newClusterDesc = {
      clusterId    = CT_TENERGY,
      range        = 1000,
      namePattern  = "W14"
}
mgr.editorAddClusterDesc(newClusterDesc);

newClusterDesc = {
      clusterId    = CT_TENERGY,
      range        = 1000,
      namePattern  = "*T-Energie/*"
}
mgr.editorAddClusterDesc(newClusterDesc);

Edit 3: soundprofiles.txt looks standard, preceed as usual.

Edit 4: soundresources.txt has only a single code segment entry. Is this a file ála soundprofile.txt or poidata.txt ?

push @Flix @dimitrius154

Edited by Charon117
Link to comment

So, Dmitriy changed the way soundresources.txt works in CM 1.60.  The original "main" soundresources.txt resides within each language's "locale" folder.  Dmitriy made some coding changes to allow a second soundresources.txt to sit in the game's "client" folder. The extra file can be used to add new custom resources to the game, without having to edit 8 different text files within each locale folder.

Link to comment
42 minutes ago, Flix said:

So, Dmitriy changed the way soundresources.txt works in CM 1.60.  The original "main" soundresources.txt resides within each language's "locale" folder.  Dmitriy made some coding changes to allow a second soundresources.txt to sit in the game's "client" folder. The extra file can be used to add new custom resources to the game, without having to edit 8 different text files within each locale folder.

Which means its a code segment style file ála soundprofile.txt, where code segments get added or changed, I assume ?

 

What about my other questions ?

18 hours ago, Charon117 said:

Edit: The next files are:

  • relations.txt
  • soundcluster.txt
  • soundprofile.txt
  • soundresources.txt

If there is anything I need to know about these files please inform me about it.

 

Right at the beginning of relations.txt we have some double entries.

Quote

-- to Thief (thief/poacher)
relation = {
    job1 = "JOB_BARD",
    job2 = "JOB_THIEF",
    relation = "I"
}
mgr.addrelation(relation);

relation = {
    job1 = "JOB_BARD",
    job2 = "JOB_THIEF",
    relation = "S"
}
mgr.addrelation(relation);

Can anyone shed light on this ?

Edit 2: Same goes for the soundcluster.txt, there is one dublicate.

Quote

newClusterDesc = {
      clusterId    = CT_TENERGY,
      range        = 1000,
      namePattern  = "W14"
}
mgr.editorAddClusterDesc(newClusterDesc);

newClusterDesc = {
      clusterId    = CT_TENERGY,
      range        = 1000,
      namePattern  = "*T-Energie/*"
}
mgr.editorAddClusterDesc(newClusterDesc);

Edit 3: soundprofiles.txt looks standard, preceed as usual.

 

Link to comment
19 hours ago, Charon117 said:

Can anyone shed light on this ?

Hmm, I think, they forgot to mirror job1 and job2 for the second entry(entries). We'll see.

1 hour ago, Flix said:

Dmitriy made some coding changes

Didn't even have to. There's "scripts\autoexec.txt", that allows to do just that . There's also "scripts\optionsDefault.txt", that allows to introduce principal stability and visual default options.

Like:

 

manager = {
    animMem = 33554432,
    grannyMem = 67108864,
    pmsMem = 8388608,
    secMem = 33554432,
    sptMem = 8388608,
    texMem = 209715200,
}

render = {
    autoCollectRadius = 230, - correct size for the autocollection ring
    shadowMapDepthBias = 0.004, - a shadow application tweak, that allows to have a slew of doublesided surfaces without the "stripped" effect.
}

 

  • Thanks! 1
Link to comment
4 minutes ago, dimitrius154 said:

Hmm, I think, they forgot to mirror job1 and job2 for the second entry(entries). We'll see.

It looks like somebody copy and pasted the second entry, but then forgot to actually use it. Nevertheless I need consensus on (1) should dublicated entries be removed. Ready when you are.

Edited by Charon117
Link to comment
3 minutes ago, dimitrius154 said:

Nope, I think the file should be edited.

Thats what I am saying, no ? Remove the dublicates.

Somebody knows by any chance which of the dublicates are the valid ones ? Is it still the last one ?

Link to comment
16 hours ago, dimitrius154 said:

By editing I mean "replacing job1, job2 subentries of the duplicate entries with the presumably correct values". I would not recommend deleting anything for now.

Yes and no. Theorising which are the correct values is up to you and other interested modders. My job is just to know what to do with them. But if nobody knows what to do with them I will simply mark both files as copyable only, until we know more.

 

blueprint.txt merge code finished, in 2 versions:

 

int MergeFileBlueprint(std::string InputDirA,std::string InputDirB, std::string FileNameA)
{
    cout << "\"" << FileNameA << "\" is being scanned right now. ";

    std::ifstream IStreamA (InputDirA);
    std::vector <std::string> VFileA;
    std::string SFileA;
    while (std::getline(IStreamA, SFileA)){
        VFileA.push_back(SFileA);
    }
    IStreamA.close();

    std::ifstream IStreamB (InputDirB);
    std::vector <std::string> VFileB;
    std::string SFileB;
    while (std::getline(IStreamB, SFileB)){
        VFileB.push_back(SFileB);
    }
    IStreamB.close();

    for(auto itVALDelim = VFileA.begin(), itVAUDelim2 = itVALDelim; itVALDelim != VFileA.end(); itVALDelim++){

        if (((*itVALDelim).find("newBlueprint =") != std::string::npos) || ((*itVALDelim).find("newBonus =") != std::string::npos) || ((*itVALDelim).find("newBonusgroup =") != std::string::npos)
                || ((*itVALDelim).find("newBlueprintSet =") != std::string::npos)){
            itVAUDelim2 = itVALDelim;
        }

        if(((*itVALDelim).find("mgr.createBlueprint(") != std::string::npos) || ((*itVALDelim).find("mgr.createBonus(") != std::string::npos) ||
                ((*itVALDelim).find("mgr.createBonusgroup(") != std::string::npos) || ((*itVALDelim).find("mgr.createBlueprintset(") != std::string::npos)){

            std::string SLDelimIdentifier;
            if((*itVALDelim).find("mgr.createBlueprint(") != std::string::npos){
                SLDelimIdentifier = "mgr.createBlueprint(";
            }
            if((*itVALDelim).find("mgr.createBonus(") != std::string::npos){
                SLDelimIdentifier = "mgr.createBonus(";
            }
            if((*itVALDelim).find("mgr.createBonusgroup(") != std::string::npos){
                SLDelimIdentifier = "mgr.createBonusgroup(";
            }
            if((*itVALDelim).find("mgr.createBlueprintset(") != std::string::npos){
                SLDelimIdentifier = "mgr.createBlueprintset(";
            }

            if(std::find_if(VFileB.begin(), VFileB.end(), [&itVALDelim](const std::string str){return str.find(*itVALDelim) != std::string::npos;}) != VFileB.end()){
                auto itVBLDelim3 = std::find_if(VFileB.begin(), VFileB.end(), [&itVALDelim](const std::string str){return str.find(*itVALDelim) != std::string::npos;});

                auto itVBUDelim4 = itVBLDelim3;
                for(; itVBUDelim4 != VFileB.begin(); --itVBUDelim4){
                    if (((*itVBUDelim4).find("newBlueprint =") != std::string::npos) || ((*itVBUDelim4).find("newBonus =") != std::string::npos) ||
                            ((*itVBUDelim4).find("newBonusgroup =") != std::string::npos) || ((*itVBUDelim4).find("newBlueprintSet =") != std::string::npos)){
                        break;
                    }
                }

                itVBUDelim4 = VFileB.erase(itVBUDelim4, itVBLDelim3 + 1);
                VFileB.insert(itVBUDelim4, itVAUDelim2, itVALDelim + 1);
            }

            else{
                auto itVBLDelim5 = std::find_if(VFileB.rbegin(), VFileB.rend(), [&SLDelimIdentifier](const std::string str){return str.find(SLDelimIdentifier) != std::string::npos;});
                VFileB.insert(itVBLDelim5.base(), itVBLDelim5.base() - 1, itVBLDelim5.base()); //this is crazy
                itVBLDelim5 = std::find_if(VFileB.rbegin(), VFileB.rend(), [&SLDelimIdentifier](const std::string str){return str.find(SLDelimIdentifier) != std::string::npos;});
                *itVBLDelim5 = "";
                VFileB.insert(itVBLDelim5.base(), itVAUDelim2, itVALDelim + 1);
            }
        }
    }

    std::ofstream OStream(InputDirB);
    for (std::vector<std::string>::iterator I = VFileB.begin(); I != VFileB.end(); ++I){
         OStream << *I << std::endl;
        }
    OStream.close();

    cout << "\"" << FileNameA << "\" has been scanned and merged for elements found." << endl;

    return 0;
}

int MergeFileBlueprint(std::string InputDirA,std::string InputDirB, std::string FileNameA)
{
    cout << "\"" << FileNameA << "\" is being scanned right now. ";

    std::ifstream IStreamA (InputDirA);
    std::vector <std::string> VFileA;
    std::string SFileA;
    while (std::getline(IStreamA, SFileA)){
        VFileA.push_back(SFileA);
    }
    IStreamA.close();

    std::ifstream IStreamB (InputDirB);
    std::vector <std::string> VFileB;
    std::string SFileB;
    while (std::getline(IStreamB, SFileB)){
        VFileB.push_back(SFileB);
    }
    IStreamB.close();


    std::array<std::pair<std::string, std::string>, 4> APairDelim =
    {
        {{"newBlueprint =", "mgr.createBlueprint("}, {"newBonus =", "mgr.createBonus("}, {"newBonusgroup =", "mgr.createBonusgroup("}, {"newBlueprintSet =", "mgr.createBlueprintset("}}
    };

    for(auto itAPair = APairDelim.begin(); itAPair != APairDelim.end(); itAPair++){

        for(auto itVALDelim = VFileA.begin(), itVAUDelim2 = itVALDelim; itVALDelim != VFileA.end(); itVALDelim++){

            if ((*itVALDelim).find((*itAPair).first) != std::string::npos){
                itVAUDelim2 = itVALDelim;
            }

            if((*itVALDelim).find((*itAPair).second) != std::string::npos){

                if(std::find_if(VFileB.begin(), VFileB.end(), [&itVALDelim](const std::string str){return str.find(*itVALDelim) != std::string::npos;}) != VFileB.end()){
                    auto itVBLDelim3 = std::find_if(VFileB.begin(), VFileB.end(), [&itVALDelim](const std::string str){return str.find(*itVALDelim) != std::string::npos;});

                    auto itVBUDelim4 = itVBLDelim3;
                    for(; itVBUDelim4 != VFileB.begin(); --itVBUDelim4){
                        if((*itVBUDelim4).find((*itAPair).first) != std::string::npos){
                            break;
                        }
                    }

                    itVBUDelim4 = VFileB.erase(itVBUDelim4, itVBLDelim3 + 1);
                    VFileB.insert(itVBUDelim4, itVAUDelim2, itVALDelim + 1);
                }

                else{
                    VFileB.push_back("");
                    auto itVBLDelim5 = std::find_if(VFileB.rbegin(), VFileB.rend(), [&itAPair](const std::string str){return str.find((*itAPair).second) != std::string::npos;});
                    VFileB.insert(itVBLDelim5.base() + 1, itVAUDelim2, itVALDelim + 1);
                }
            }
        }
    }

    std::ofstream OStream(InputDirB);
    for (std::vector<std::string>::iterator I = VFileB.begin(); I != VFileB.end(); ++I){
         OStream << *I << std::endl;
        }
    OStream.close();

    cout << "\"" << FileNameA << "\" has been scanned and merged for elements found." << endl;

    return 0;
}

[\SPOILER]

 

 

Going on from this and continuing with the surface.txt, are the unique identifiers for that file the line above "diffusePnt    =", and "name         =" ? Quick test results say they are indeed  unique, but "tempelguardian-battery01-te_t".  EEs enhanced Spells has this file which is why I think @Flix will be able to enlighten us about it.

Edited by Charon117
Link to comment

Yes and yes.  Surface entry names are indeed unique.  Surfaces are assigned to the model itself, and the model is "looking" for a certain surface name to be present in surface.txt.  Otherwise it display a black texture with a red X through it.

The duplicate you mention, I did not know about until now. I believe this originated with Silver Fox endeavoring to texture Dmitriy's elite mounts pre-CM 1.60.  Much of the Mobiculum textures drew on TG armors. It seems he accidentally created a duplicate, with textures in different places.  I don't know which one controls, but going by the behavior of other files in the client folder, the latter one would override.

Link to comment
19 hours ago, Flix said:

The duplicate you mention, I did not know about until now. I believe this originated with Silver Fox endeavoring to texture Dmitriy's elite mounts pre-CM 1.60.  Much of the Mobiculum textures drew on TG armors. It seems he accidentally created a duplicate, with textures in different places.  I don't know which one controls, but going by the behavior of other files in the client folder, the latter one would override.

So this throws up some problems.

(1) Should we continue under the pretense that every file potentially needs a dublicate cleanup ? This would cost a lot of resources, as the cleanup would happen every time the program is started.

(2) Continue under the pretense that my CP1.6 EE2.1 build contains all dublicated entries if they exist. And add a cleaning tool for each file which has one.

(3) There would also be solution number 3. Which is that the program instead of modifying the first entry it finds, modifies the last entry it finds. Even if a file has dublicates the relevant entry will be modified. This will cost the least amount of resources, but also needs the confirmation that later entries ALWAYS overwrite earlier ones, otherwise we are just running in circles.

@Flix @dimitrius154

Edited by Charon117
Link to comment

The next files are

  • creatures.txt
  • drop.txt
  • equipsets.txt
  • faction.txt

For creatures.txt the plan is to straightforwardly make mgr.createCreature entries mergeable, for which I assume the "id =" is the unique identifier. For mgr.addCreatureBonus I will delete entries based on the first number, and then reinsert them based on the mergable content. mgr.createSkill seems a but more complicated. Can you insert the comments into the code block ?

Quote

mgr.createSkill {
--
--Advanced:
--optimale Aspektregeneration
    skill_name = "skill_DR_voodoo_focus",
    advance_level = 75,
    min_level = 1,
    mean_value = 200,
    advance_mean_value = 300,
    skillgroup = "SKG_ASPECTS",
    adv_skill_name = "skill_DR_voodoo_adavanced_focus",
}

mgr.addCreatureSkill is a mystery to me, everything is dublicates, no value is different from the other, and skill_name is mostly generic. Enlighten me.
Is the file getting dublicated starting from line 89247 ?

Let me wrap my head around this. These are the code blocks:

  • mgr.createCreature, unique identifier: id = NUMBER
  • mgr.addCreatureBonus, unique identifier: mgr.addCreatureBonus( NUMBER
  • mgr.createSkill, unique identifier: skill_name = "STRING
  • mgr.addCreatureSkill, unique identifier: mgr.addCreatureSkill( NUMBER
  • mgr.addCreatureBpRelation, unique identifier: creature_id = NUMBER
  • mgr.addMapPos, doesnt have an unique identifier

So the file looks very messy, code sections repeat, code blocks are inserted in other code blocks, random stuff is thrown around. Do you want me to make a tool that automatically detangles all of that, and gives you a proper file, with the same code and relative order to each other ? Optionally I could also group things together based on the unique id, but that would mess with the order. Thats your call.

 

drop.txt has

  • mgr.createDroplist, unique identifier id = NUMBER
  • mgr.createDroppattern, unique identifier: id = NUMBER
  • local shrinkheadDropMap, is table

A quick test run shows that all id = are unique, just that be kept ?

 

equipsets.txt has

  • mgr.reserveEquipsets, unique
  • local EQUIPSLOT, unique
  • mgr.createEquipset, unique identifier: id = NUMBER

The dragonmage doesnt have starting equipment ?

 

faction.txt has

  • mgr.addFaction, unique identifier: id = NUMBER
  • mgr.addFactionRelation, doesnt have a unique id, or rather, the unique id would be id1 and id2 together.

 

 

I appreciate every input :).

Edited by Charon117
Link to comment

mgr.addCreatureBonus( 1187, { -- determines the creature ID that the bonus is added to
    intensity = 200,
    bonus = 54, -- determines the bonus ID as defined in blueprint.txt
So it takes two lines to make a creature bonus entry unique.

Duplicates in mgr.addCreatureBonus will have their intensity values added together, this is why some mods have had unintentionally overpowered enemies.  I would love to have all the bonuses sorted by creature ID, then bonus ID, then intensity, but the devs decided to script intensity in the middle so it's hard to sort.

On 11/22/2019 at 12:21 AM, Charon117 said:

mgr.addCreatureSkill is a mystery to me, everything is dublicates,

mgr.addCreatureSkill( 1182, -- this line determines the creature ID that the skill is to be added to
    skill_id = 62, -- this line determines the skill, but the numerical ID's aren't listed in any scripts. They are somewhere in the binaries.
    advanced = 0, -- determines if the skill is on the right hand column (requiring 5 points spent in the left hand column). Should always be "0" for NPC's.
    skill_name = "skill__enemy_focus", -- this line also defines the skill, I've never experimented to see what happens if this is missing or mismatched with the skill_id line above. These names ARE defined in creatures.txt, in the mgr.createSkill entries.  The name is definitely important though because spells.txt draws on them (and not the numerical ID).

So as you can see at least 2-3 lines are necessary to make a creature skill entry unique.

There is a kink when it comes to Addendum. You can see that in the skills "skill__enemy_focus" and "skill__enemy_lore" there is an extra _ between skill and enemy.  Dmitriy removed this extra character throughout creatures.txt and also spells.txt.  No one else done this in any mods to my knowledge.

On 11/22/2019 at 12:21 AM, Charon117 said:

mgr.reserveEquipsets, unique

Yes and the numbers next to mgr.reserveEquipsets must match the number of equipset ID's and the number of entries within the "set" strings, respectively.

On 11/22/2019 at 12:21 AM, Charon117 said:

For creatures.txt the plan is to straightforwardly make mgr.createCreature entries mergeable, for which I assume the "id =" is the unique identifier.

It is, but the "name =" is also important and should not be changed as questscripts.txt draws on them at times (and not the ID's at all).

On 11/22/2019 at 12:21 AM, Charon117 said:

mgr.addFactionRelation, doesnt have a unique id, or rather, the unique id would be id1 and id2 together.

Yes, and there are mirror versions of each of these relations, where id1 and id2 are swapped.

Link to comment
3 hours ago, Flix said:

this line determines the skill, but the numerical ID's aren't listed in any scripts. They are somewhere in the binaries.

Yes and every skill name is also present in s2logic.dll and sacred2.exe in reference tables.

3 hours ago, Flix said:

Dmitriy removed this extra character throughout creatures.txt and also spells.txt.  No one else done this in any mods to my knowledge.

They couldn't, wthout editing binaries references would break. The extra "_" was an obvious typo the original developers had never deigned to correct.

Link to comment
6 hours ago, Flix said:

mgr.addCreatureBonus( 1187, { -- determines the creature ID that the bonus is added to
    intensity = 200,
    bonus = 54, -- determines the bonus ID as defined in blueprint.txt
So it takes two lines to make a creature bonus entry unique.

That is correct, but I was rather thinking along different lines, namely that if somebody adds lets say creature 1187 to the mgr.addCreatureBonus, than the script will draw all mgr.addCreatureBonus( 1187 lines, deletes them, and then draws all mgr.addCreatureBonus( 1187 lines from the merge file, and inserts them. This way modders can add "dublicates" or whatever they want, as long as they know that all mgr.addCreatureBonus( NUMBER form a set. Most importantly it keeps backwards compability.

So mgr.addCreatureSkill( NUMBER is the creature id, while skill_id or skill_name might be what it does ? Nevertheles it will get the same treatment as mgr.addCreatureBonus, all creature ids form a set.

With that, this wont be a problem as well:

6 hours ago, Flix said:

There is a kink when it comes to Addendum. You can see that in the skills "skill__enemy_focus" and "skill__enemy_lore" there is an extra _ between skill and enemy.  Dmitriy removed this extra character throughout creatures.txt and also spells.txt.  No one else done this in any mods to my knowledge.

 

6 hours ago, Flix said:

It is, but the "name =" is also important and should not be changed as questscripts.txt draws on them at times (and not the ID's at all).

That will be up to the mod authors to insert the correct value.

 

6 hours ago, Flix said:

Yes, and there are mirror versions of each of these relations, where id1 and id2 are swapped.

No problem, the core id will be id1, and it then checks what id2 is.

 

6 hours ago, Flix said:

Duplicates in mgr.addCreatureBonus will have their intensity values added together, this is why some mods have had unintentionally overpowered enemies.  I would love to have all the bonuses sorted by creature ID, then bonus ID, then intensity, but the devs decided to script intensity in the middle so it's hard to sort.

I think you missed this question:

15 hours ago, Charon117 said:

So the file looks very messy, code sections repeat, code blocks are inserted in other code blocks, random stuff is thrown around. Do you want me to make a tool that automatically detangles all of that, and gives you a proper file, with the same code and relative order to each other ?

 

2 hours ago, dimitrius154 said:

They couldn't, wthout editing binaries references would break. The extra "_" was an obvious typo the original developers had never deigned to correct.

Dont worry, the merge system doesnt break on that. As long as mod authors supply the correct values the merge system takes everything.

 

Missed questions:

15 hours ago, Charon117 said:

drop.txt has

  • mgr.createDroplist, unique identifier id = NUMBER
  • mgr.createDroppattern, unique identifier: id = NUMBER
  • local shrinkheadDropMap, is table

A quick test run shows that all id = are unique, just that be kept ?

15 hours ago, Charon117 said:

The dragonmage doesnt have starting equipment ?

 

Edited by Charon117
Link to comment
34 minutes ago, Charon117 said:

The dragonmage doesnt have starting equipment ?

He does:

-- HERO_INITIAL_DRAGONMAGE
mgr.createEquipset {
    id = 475,
    set = {
        { 1126, EQUIPSLOT.AUTO, 0},
        { 1138, EQUIPSLOT.AUTO, 0},
        { 167, EQUIPSLOT.AUTO, 0},
        { 167, EQUIPSLOT.AUTO, 0},
        { 167, EQUIPSLOT.AUTO, 0},
    }
}

35 minutes ago, Charon117 said:

A quick test run shows that all id = are unique, just that be kept ?

Looks good.

35 minutes ago, Charon117 said:

I think you missed this question:

I didn't really want to answer with a yes or no, rather than just describe how I wish the block of creature bonuses looked.  Part of why it probably looks so messy is that most of my additions look like this:

mgr.addCreatureBonus( 2919, {intensity = 1650, bonus = 20,})

mixed in with the vanilla convention of:

mgr.addCreatureBonus( 2918, {
    intensity = 500,
    bonus = 263,
})

Which is more proper but takes up too much space and it's harder to compare entries at a glance.  If all entries were on a single line, and bonus and intensity were swapped places, then I could do simple line sorting operations in Notepad++ to get all creature bonuses grouped together, and I could also spot duplicates easily, and even combine the duplicates where appropriate. 

Link to comment
1 hour ago, Flix said:

I didn't really want to answer with a yes or no, rather than just describe how I wish the block of creature bonuses looked.

Ok, than let me tell you my idea of sorting the file:

mgr.createCreature block. All together.
mgr.addCreatureBonus block. Sorted by CREATURE NUMBER. I dont really care about single or multi line. Tell me if you prefer either of them.
mgr.createSkill block. Hopefully I will be able to preserve  comments above, but I would rather want to add them into the code segment.
mgr.addCreatureSkill block. Same as mgr.addCreatureBonus.
mgr.addCreatureBpRelation block. Same as mgr.addCreatureBonus. Dont miss that the question for the format repeats here. Seriously, the more you tell me how you want to have it, the easier of a job I have, because guessing is the hardest thing to do.
mgr.addMapPos block.

What do you think ?

 

Edit:

1 hour ago, Flix said:

Which is more proper but takes up too much space and it's harder to compare entries at a glance.  If all entries were on a single line, and bonus and intensity were swapped places, then I could do simple line sorting operations in Notepad++ to get all creature bonuses grouped together, and I could also spot duplicates easily, and even combine the duplicates where appropriate. 

Dont worry. Once my program runs over it all entries will be cleaned, formatted, and sorted by id in a single block for each category, you will literally be able to look at the structure.

 

@FlixAlso, can they even be swapped ? I mean would the code still work ?

Edited by Charon117
  • Like! 1
Link to comment
2 hours ago, Charon117 said:

Also, can they even be swapped ? I mean would the code still work ?

I don't know.  Probably.  A lot of scripts in the server folder don't care what order the various parameters within an entry come in.  

You could swap around each line except the first and last line within a creature entry and it would all still work.  Bonuses probably work the same.

2 hours ago, Charon117 said:

What do you think ?

Single line. I'm fine with the rest being like it is.  It's really just the presentation of bonuses I'm interested in seeing improved.  Anything else is just icing on the cake.

Link to comment
2 hours ago, Charon117 said:

Also, can they even be swapped ? I mean would the code still work ?

I... assume, they can and it will. Judging by the code in question, the actual stings are being compared vs. a mask, as in  if "string" = "intensity".

  • Thanks! 1
Link to comment
  • The title was changed to ESP: The beginning and the modmerging system [S2G/CP1.6/EE2.1/SS,C]

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...
Please Sign In or Sign Up