Vincentz Infinite Projects [VIP MOD]

Atm there is only 1 "battleship" as the naval carrier is made into a powerful rangestriking/carrier unit.
So both the Missile Cruiser, Attack Submarine and Stealth Destroyer upgrades into the Spaceship-Battleship. I might change that later adding a Spaceship-Destroyer class which helicopters also would upgrade into.

The land unit Artillery and Mobile AA is combined into a powerful land unit, able to launch artillery strikes as well as defend against air units.
The infantry AT and AA are combined into a rocket shooting inf (still missing a CIS-missile droid)

***** LASER UNITS *****
RIFLE 35 45 +50 Gun UNIT_OFFICERARMORED UNIT_REBELSOLDIER UNIT_BATTLE_DROID
MARINE 40 50 +50 Siege, Att. from sea UNIT_STORMTROOPER UNIT_REBELSOLDIERFOREST UNIT_SUPER_BATTLE_DROID
PARA 42 55 +25 City, Paradrop UNIT_DARKTROOPER UNIT_SMUGGLER UNIT_DROIDEKA
SNIPER 26 35 +100 Gun + Laser UNIT_SCOUTTROOPER UNIT_MARKSMAN UNIT_MAGNAGUARD
AT/SAM 33 40 +100 Air + Heli + Armor UNIT_SHOCKTROOPER UNIT_VANGUARD

***** SIEGE UNITS *****
ARTY+AA 45 55 +50 Air + Heli, 5 Range Arty UNIT_ATAA UNIT_HOVERTANK UNIT_DROID_AA

***** ARMORED UNITS *****
INF MECH 58 70 +50 Gun + Laser, CanHeal&Move UNIT_ATST UNIT_ATTACKSPEEDER UNIT_AAT
TANK 66 80 +50 Armor + Siege, CanAttMultiple UNIT_ATAT UNIT_IFTX UNIT_NRN99

***** AIR UNITS ******
FIGHTER 55 80 +25 Air -25 Land UNIT_TIEFIGHTER UNIT_AWING UNIT_DROID_TRIFIGHTER
ATTACK 60 90 +25 Space UNIT_TIEINTERCEPTOR UNIT_XWING UNIT_DROID_FIGHTER
BOMBER 65 100 UNIT_TIEBOMBER UNIT_YWING UNIT_DROID_BOMBER

***** SPACE UNITS *****
TRANPORT 33 100 UNITCLASS_SHUTTLE UNIT_REBELTRANSPORT UNIT_DROID_LANDER
CARRIER 60 300 UNIT_STARDESTROYER UNIT_MONCALAMARI UNIT_DROID_CRUISER
CRUISER 100 200 UNIT_TARTANCRUISER UNIT_BLOCKADERUNNER UNIT_DROID_BATTLESHIP

 
Donno if there is anyone out there who will actually read this, now civ6 have been out for a while, but posting anyway ;)

Of course (mature) players will read it. Civ5 and Civ6 is for kids. Not for real (war or builder) game players who want diversity and a challenge.
 
Nice:) Thank you!
One request if you dont mind:rolleyes: Those droideka graphics are really nice but theres no gunfire/laser animations, I was wondering that maybe you know how to add them?
 
I'll have a look at it. There are several other graphical issues, especially with the shadows. I think I was a bit surprised with the amount of work needed to put these units into place. The UnitInfosXML for the Star Wars Units alone is 8000+ lines iirc.

EDIT: This last version is an "alpha", and the idea is to catch every little detail that needs to be fixed/polished with "basegame" as well as providing the star wars units.

If there is ANY bug, unbalance, graphical error, a wtf.moment, anything odd, PLEASE let me know, so I can fix it and get a nice clean 1.3 version that plays all the way through the ages perfectly.
 
Last edited:
I'll have a look at it.
I JUST had a CTD myself.Strange part was that when I created a Debug gamecore.dll and used that one instead, the crash was gone.
I did see a bug though, and donno if it have anything to do with the crashes. I removed a unit named Bothan (a star wars alien spy) but forgot to remove it in UnitClassInfo.xml. Donno if that makes it crash.
 
Next round CTD, why?
I didnt get a CTD, so I need a bit more info:

Language used (English/German/Spanis/Italian/French)
Operating System (XP, Vista, Win7, Win10, Linux)
Game Version (Vanilla, Warlords, BtS+Warlods, BtS, Gold, Steam, GOTY)
and if you have a low end system, the specs. But honestly, my system is really, really old and I'm currently playing around the Transhuman era on a Gigantic map with 30 AI without any problems (well... except that one crash that kinda disappeared by itself when I wanted to inspect it...)

I attach savegame from the next turn, but I went on and played around 10-15 turns just to make sure there wasnt an eminent crash coming :D

As a sidenote, Im prolly gonna upload more frequent in the future as I have just upgraded from (up to) 30/2 mb/s to (guaranteed) 100/20 mb/s (same company but for a mere $3 more a month. def. an offer I couldnt refuse ;))
 

Attachments

  • Rauriker AD-1015-January.CivBeyondSwordSave
    958.9 KB · Views: 311
I finally cracked an issue that had bothered me since time beginning of this mod.
When building more than one unit of the same time I made it more expensive (2% irrc), to get players and especially AI to not just build the same type of unitx1000
However, when upgrading units, the cost of the upgrade takes into account both the amount of units being upgraded from and being upgraded too.
This could result in an upgrade price that was pretty much zero (it didnt get into negatives though).

Now its using the amount it upgrades INTO (keeping the +2% extra per unit in place) but not the amount of units it upgrades from (it just uses the standard production cost).

Code:
//Vincentz Upgrade cost per unit amount fixed
    iPrice += (std::max(0, (GET_PLAYER(getOwnerINLINE()).getProductionNeeded(eUnit) - GC.getUnitInfo(getUnitType()).getProductionCost())) * GC.getDefineINT("UNIT_UPGRADE_COST_PER_PRODUCTION"));
//    iPrice += (std::max(0, (GET_PLAYER(getOwnerINLINE()).getProductionNeeded(eUnit) - GET_PLAYER(getOwnerINLINE()).getProductionNeeded(getUnitType()))) * GC.getDefineINT("UNIT_UPGRADE_COST_PER_PRODUCTION"));
//Vincentz end

EDIT: Added gameoption "Healing Cost Gold". Selfexplaining really ;)

edit edit: Fixing multiple "Failed Asserts" with DEBUG dll. Some unimportant, some important, and some critical:

1) Negative movepoints commented out as, well, VIP works with negative movepoints.
2) VIP doesnt have civ specific intro movies, so removed.
3) The code for AI Civic Specialist Commerce Change Value was wrong (it used number of specialist where it should have used number of commerces).
4) Wrong amount of Gameoptions
5) Code in python that contracted display of promotions if same line (gave a Failed Assert)

editeditedit
Removed the Negative Happiness on Spy specialist. It was really annoying
Added Happiness and Health on Priest, but removed production

4xedit
...and just to throw some numbers out there, which shows the amount of data being crunched:
On my Gigantic, 30 civ, 2019 AD map I have just saved as a scenario, and then loaded in the newest version, the MPlog which is the main log showing info such as Randomizers, Units moving, UNITAI etc, is more than a million lines long.... in ONE turn! This is more than 55 mb!
of those lines there are
21 Unit stuck in loop. (Almost all is ships, with only a single land unit, and 2 planes)
24091 Times a unit is moving from a tile to another
309 times AI Defend

247 AI Air Carrier Move RANDOMIZER
8 Global Warming RANDOMIZER
40 Feature Growth RANDOMIZER
447 Project Tech Sharing RANDOMIZER
9 AI Make Peace RANDOMIZER
72 AI Target City RANDOMIZER
2465 Bonus Discovery RANDOMIZER

the Randomizer means that it was a dice roll, not that something absolutely happened.

oh, forgot the last one. As this was first turn this might be a bit more than normal, but...:
946421 AI explore RANDOMIZER.....
Maybe something can be done about this one, to increase turnspeed....


E E E E Edit:
Just uploaded changed source code in case my computer blows up and all the changes are lost forever.
http://www.moddb.com/mods/vincentz-infinite-projects/downloads/gamecore-1-3b-star-wars#downloadsform

E6dit:
Changed rangestriking, so its attacking best defender (not necessarily a rangestrike defender) BUT if there is a rangestriker that can hit back, it will.
Before it was only selected units that would rangestrike eachother, defined in unitInfoXml, meaning ship/land and land/ship units wouldnt return fire if they were not BestDefender.

77d77it
Crushed another thing that was annoying me. Improvements with a NEGATIVE pillagegold value CANNOT be pillaged. (It was so silly when the barbs came in and pillaged something, just to have the ruins being pillaged too.

88888888
NewIcons.png


New Action Icons. From left to right : Delete, Fortify, Skip Turn, Sentry, GoTo, Explore, Plunder

Edit 999999999

Changed a bit, and added some more. Goal is every Action icon
Left to right : Dispand (Was Delete before, but since player get money for dispanding inside borders, I thought I'd change it.Fortify, Sentry (Have switched places with Skip turn. Skip Turn, GoTo, Explore, Plunder, Gift
NewIcons.png


edit x10
lol. I wanted to get new icons for plane actions, and thinking a ww2 bomber bombing mission would be a good icon, so I type in "Bomb Camera Plane", and just as I hit enter I think to myself : "frak! I just got myself on a no-fly list"...
Anyway, its going to be difficult to get some good pictures for:
Airpatrol, Air Recon, Airstrike, Airbomb and Rebase, without they look similar....

Added a couple more. The old Barricade is now Range Strike, and Pirate flag is Barricade (Plunder)
Besides that there is Heal, Sleep, Wake Up, Espionage and Pillage
new icons for actions.png
 
Last edited:
Awesome. Can't wait for beta release :)
Great work Vinz.
 
Nice:) Thank you!
One request if you dont mind:rolleyes: Those droideka graphics are really nice but theres no gunfire/laser animations, I was wondering that maybe you know how to add them?

Nope, unfortunately I have no clue how to fix animations. I'm gonna put it in unit creation, and hopefully someone will take a look at it.
Spoiler NifSkope :



edit: Uploading new version 1.3B. Not because its a beta, but because the A version had so many horrible errors in it, and I'm going away a couple of days.
Link should be the same (too lazy to set up a new one in modDB)


Uploading the gamecore files as well. Took a damn long time to find a solution (which is not perfect though) for the rangestrikers

http://www.moddb.com/mods/vincentz-infinite-projects/downloads/gamecore-1-3b-star-wars
 
Last edited:
Had a CTD on a AI autorun at 1640AD on a normal size map. Donno if its graphical (I think so), code or Civ4's memory issue (I doubt it, as it wasnt using a lot of memory on normal sized map).
I'm going to look into it when I return home, but I have added a DEBUG CVGameCoreDLL.dll to the source code files in above link, and if you have a crash, replace the one in the vip/assets with the DEBUG one, and see what error (if any) that comes up.
Thanks in advance :D

Edit. Did another AI autorun and this one crashed in 1742. Something is running badly in the code me thinks, or its somewhat related to a unit or improvement that have bad graphics.
I cant seem to locate any issues in the code, and it doesnt give me any error when debugging so I'm guessing its a bad graphics implementation.... :(

Edit 3. Thinking out loud here. Sometimes it helps and of these 76 pages in this thread much is of this. But if anyone with more knowledge wants to interrupt me and tell me I'm wrong or better tell e how to do it right, I would be very happy ;)

Now... I seem to have problem with
bool CvSelectionGroupAI::AI_update() which gives a failed assert at line 168:

last MP log shows
Player 3 Unit 5111844 (TXT_KEY_LEADER_HUAYNA_CAPAC's Frigate) moving from 0:26 to 79:27

My guess is prolly a looping unit, but why crashing?

Its running through this while, but getting to 101, so FAssert(false); becomes true and creates the assert, then it will take the unit causing this and set it as pHeadUnit and if there isnt any it will break from this code and continue whatever it was doing before AI_update() was called.
If there IS a pHeadunit, then it should be logged as a looping unit (which it usually does fine), and just finish the moves on the unit.

So where is the crash?....

Spoiler Code :
Code:
    int iTempHack = 0; // XXX

    bDead = false;
 
    bool bFailedAlreadyFighting = false;
    while ((m_bGroupAttack && !bFailedAlreadyFighting) || readyToMove())
    {
        iTempHack++;
        if (iTempHack > 100)
        {
            FAssert(false);
            CvUnit* pHeadUnit = getHeadUnit();
            if (NULL != pHeadUnit)
            {
                if (GC.getLogging())
                {
                    TCHAR szOut[1024];
                    CvWString szTempString;
                    getUnitAIString(szTempString, pHeadUnit->AI_getUnitAIType());
                    sprintf(szOut, "Unit stuck in loop: %S(%S)[%d, %d] (%S)\n", pHeadUnit->getName().GetCString(), GET_PLAYER(pHeadUnit->getOwnerINLINE()).getName(),
                        pHeadUnit->getX_INLINE(), pHeadUnit->getY_INLINE(), szTempString.GetCString());
                    gDLL->messageControlLog(szOut);
                }
        
                pHeadUnit->finishMoves();
            }
            break;
        }

        // if we want to force the group to attack, force another attack
        if (m_bGroupAttack)
        {   
            m_bGroupAttack = false;

            groupAttack(m_iGroupAttackX, m_iGroupAttackY, MOVE_DIRECT_ATTACK, bFailedAlreadyFighting);
        }
        // else pick AI action
        else
        {
            CvUnit* pHeadUnit = getHeadUnit();

            if (pHeadUnit == NULL || pHeadUnit->isDelayedDeath())
            {
                break;
            }

            resetPath();

            if (pHeadUnit->AI_update())
            {
                // AI_update returns true when we should abort the loop and wait until next slice
                break;
            }
        }

        if (doDelayedDeath())
        {
            bDead = true;
            break;
        }

        // if no longer group attacking, and force separate is true, then bail, decide what to do after group is split up
        // (UnitAI of head unit may have changed)
        if (!m_bGroupAttack && AI_isForceSeparate())
        {
            AI_separate();    // pointers could become invalid...
            return true;
        }
    }

EDIT:

Digging a bit deeper it seems the problem comes a bit later than above code :
Unhandled exception at 0x040ad4e1 (CvGameCoreDLL.dll) in Civ4BeyondSword.exe: 0xC0000005: Access violation reading location 0x00000028.

It starts in CvPlot.h
Code:
    DllExport PlayerTypes getOwner() const; // Exposed to Python
#ifdef _USRDLL
    inline PlayerTypes getOwnerINLINE() const
    {
040AD4D0  push        ebp
040AD4D1  mov         ebp,esp
040AD4D3  push        ecx
040AD4D4  mov         dword ptr [ebp-4],0CCCCCCCCh
040AD4DB  mov         dword ptr [ebp-4],ecx
        return (PlayerTypes)m_eOwner;
040AD4DE  mov         eax,dword ptr [this]
040AD4E1  movsx       eax,byte ptr [eax+28h]
    }
040AD4E5  mov         esp,ebp
040AD4E7  pop         ebp
040AD4E8  ret
Debugger points at 040AD4E1 movsx eax,byte ptr [eax+28h] though I have honestly no idea what this means. :(

----------------------------

The thing is that the following is 4 times in the code (CvCity.h, CvPlotGroup.h, CvUnit.h and CvSelectionGroup.h), but in CvPlot.h it is "return (PlayerTypes)m_eOwner;" seems strange, but changing it to "return m_eOwner;" creates an error when compiling.

Code:
    DllExport PlayerTypes getOwner() const;                                                                // Exposed to Python
#ifdef _USRDLL
    inline PlayerTypes getOwnerINLINE() const
    {
        return m_eOwner;
    }
#endif

----------------------------------------------------

New day, new eyes....
I replaced the code yesterday, from char to int. I didnt make any sense why playertypes should be in char (character) when all the other places it was in int (integer), but I guess the OC (original coders) meant for it to be that way.
Problem is though, that by changing this, the whole code "changes" and savegames and even scenarioes wont work anymore :(
But if it is indeed the culprit of the crash, then its worth it imho.

Now, while debugging I did found a bad pattern with especially on of the AI-types : AI-Sea-explore.
The easiest would be to just dispand units instead of skip turn on them, though the best would be to find out why they cant find something to do in the first place.
It is especially the early boats, but I have seen ships standing still later in game too.

Alternatively it might be an option to ask for an AI-type change (AI-Sea-reserve or something)

-----------------------

While running around in circles, I might have fixed another issue I had created accidentally.
The Heal promotions havent really been used lately by the AI. It should be fixed now.

----------------------

Good news... I fixed the CTD. Not really sure what caused it though, but the problem was it returning a null pointer.
The bad news is I had to remove some code that I actually liked, though tbh it wasnt perfect as it was a bit silly that (before the option to sign open borders) the AI would greet you, make peace with you, and then kill your unit. (The Always Hostile before Open Borders).
Maybe I can perfect it later, but for know I just removed it and replaced it with the original code.

The best news have to be that all the crashes I had (1640, 1742, 1230, etc etc) was because of this, so I can basically say the mod is Crash-Free again :D
And while digging in the code I have made some changes for the better imho. (Looping ships with UNITAI_EXPLORE_SEA are being dispanded, so the AI doesnt pay for a useless ship, as well as the turn time should be reduced from all those Randomizers regarding the ExploreAI), along with a couple other changes. Gonna reverse the Char/Int thing back to standard, as that wasnt the cause of the CTD, but just a symptom.
 
Last edited:
Uploading 1.3C because C is for Crash-Free :D

I also included a DEBUG CvGameCoreDLL.dll, if anyone wants to debug it. (Just rename the normal CvGameCoreDLL.dll to something else and rename DEBUG CvGameCoreDLL.dll to, CvGameCoreDLL.dll)
Besides that I included the changed SDK source code in Custom VIP folder inside the VIP folder.

Dont forget to rate the mod if you like it ;)

https://forums.civfanatics.com/resources/vincentz-infinite-projects-vip-mod.25053/


edit: Sometimes testing is needed....:o The Healing Cost Gold gameoption is reversed, meaning ticking it off cost gold when healing. Is fixed in next version

EDIT: Currently running a 30civ gigantic ai autorun, just as a stress test. this is how the first few years went (10.000BC to 1525AD)
 
Last edited:
Added some new icons. Cant make screenies of icons, so I simply recommend installing them and trying them out :)

Install by dragging the Cursors folder from zip and drop it into the VIP/Assets/res folder

Uninstall by deleting the Cursors folder in the VIP/Assets/res folder

edit. it is new cursors. not icons....
Spoiler :
 

Attachments

  • VIP Cursors.zip
    567.1 KB · Views: 295
Last edited:
p.s. you should be able to put that char back into an int or what ever change you reverted, it will not make a difference, and it will restore the old save game compatibility etc. For the future you can fix save breaking DLL changes by writing patches in your code. i.e. from now on have a variable in the DLL which indicates the VIP version number and write that out to the savegames, when you read it back in you are then able to determine what version you need to be compatible with so that you can choose to assign defaults to variables that aren't present in the save game, or make sure you read the file in a slightly different way if an old version required it (which is exactly the problem you ran into this time). It's a bit of work to get going, but will save your players a lot of hassle in the future. I believe Firaxis started actually doing this about half way through civ5 development to stop all the savebreaking patches from happening
 
I reversed the int/char before I uploaded, and generally I try to find a solution inside existing functions to avoid savegame incompatibility, but what you wrote about version number sounds very interesting. How would something like that be implemented? Any code examples? And would it be able to load savegame when f.ex a new unit was added?

On other news : I changed the rangestriking code a bit, and is very happy with the result. Question is if rangestriking return fire should be a Game option, or if anyone would actually turn it off (I'm not going to make rangestriking into an option as it would require waaaay to much work.) I might isolate the code and update the rangestriking mod I uploaded earlier, but TBH it would mean a lot of work that maybe noone would use.

I have also updated upgrades to starwars units as well as finished the infantry and Starfighter units, but still missing the tanks and capital ships.

edit: I went through all my code , to comment and clean, and all was nice and easy and clean, until I came to the CvUnit.cpp.... (for those who doesnt know winmerge; it shows the difference between my changed one and the original (BtS3.19) file, where each yellow line is a difference in code).

editedit: I forgot I had taken the code into VS2015 and formatted it which cleans up spaces and line endings. So had to undo it by hand....

holymerging.png
abitbetter.png
 
Last edited:
Found another CTD. This time I was better prepared, learning the way of the debugger in my last attempt :)
And this one wasnt even mine. It was from Aircombat experience mod, so a heads up to anyone using it:

The part that gives experience to units when they airbomb a tile:
Problem is that missiles doesnt have attackXPValue, so it will end up with a :devil: Divide by Zero :devil:
Code:
                // < Air Combat Experience Start >
                if (GC.getGameINLINE().isExperienceGainByDestroyingImprovements())
                {
                    int iExperience = attackXPValue();
                    iExperience = (GC.getImprovementInfo(pPlot->getImprovementType()).getAirBombDefense() / iExperience) / 2;
                    iExperience = range(iExperience, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
                    changeExperience(iExperience, maxXPValue(), false, pPlot->getOwnerINLINE() == getOwnerINLINE(), !isBarbarian());
                }
                // < Air Combat Experience End   >

edit: it should actually be * instead of / as the higher the number the lower the xp is atm.
editedit: but honestly its kinda silly to use attaxkXPValue as it is only 4 or 0. So I'm removing attackXPValue() and dividing with 10 instead of 2
 
Last edited:
So there are only certain things that affect savegame compatibility, I'll do my best to list them but I'm a little rusty;
  • data present on the map
  • values from classes
Basically, you can find what is saved in a classes write and read methods. So what doesn't break compatibility? Adding new units won't break compatibility, suppose the following;

VIP 1 has 3 units; archer, swordsman, horseman
VIP 2 has 3 units; archer, swordsman, boat

VIP 3 has 3 units; archer, swordsman, boat, horseman

Under what conditions might a save for one be compatible with another?

Well, VIP 1 saves and VIP 2 saves are probably not compatible, it's worth trying first (on some toy mod like the ones above), but I believe if a VIP 1 save has no horseman units in it, it would be compatible with VIP2, but I could be wrong, it's been a while. Certainly if there were horseman in a VIP 1 save, it shouldn't load (I'm pretty positive on this one). However, VIP 2 saves should be loadable in VIP 3, there are no removals, only additions (again, worth verifying for a simple example). VIP 1 and VIP 3 are probably incompatible, the ordering of the unit ID's changed, it might mess stuff up.

But certainly, if we add a new field to the CvUnit class, of say m_iVipSomething, and we want it to persist over savegame (so it is in write and read), then any old saves won't work any more. Why? Here's a simple example from CvMap;
Spoiler :
Code:
void CvMap::write(FDataStreamBase* pStream)
{
    uint uiFlag=0;
    pStream->Write(uiFlag);        // flag for expansion

    pStream->Write(m_iGridWidth);
    pStream->Write(m_iGridHeight);
    pStream->Write(m_iLandPlots);
    pStream->Write(m_iOwnedPlots);
    pStream->Write(m_iTopLatitude);
    pStream->Write(m_iBottomLatitude);
    pStream->Write(m_iNextRiverID);

    pStream->Write(m_bWrapX);
    pStream->Write(m_bWrapY);

    FAssertMsg((0 < GC.getNumBonusInfos()), "GC.getNumBonusInfos() is not greater than zero but an array is being allocated");
    pStream->Write(GC.getNumBonusInfos(), m_paiNumBonus);
    pStream->Write(GC.getNumBonusInfos(), m_paiNumBonusOnLand);

    int iI;   
    for (iI = 0; iI < numPlotsINLINE(); iI++)
    {
        m_pMapPlots[iI].write(pStream);
    }

    // call the read of the free list CvArea class allocations
    WriteStreamableFFreeListTrashArray(m_areas, pStream);
}

void CvMap::read(FDataStreamBase* pStream)
{
    CvMapInitData defaultMapData;

    // Init data before load
    reset(&defaultMapData);

    uint uiFlag=0;
    pStream->Read(&uiFlag);    // flags for expansion

    pStream->Read(&m_iGridWidth);
    pStream->Read(&m_iGridHeight);
    pStream->Read(&m_iLandPlots);
    pStream->Read(&m_iOwnedPlots);
    pStream->Read(&m_iTopLatitude);
    pStream->Read(&m_iBottomLatitude);
    pStream->Read(&m_iNextRiverID);

    pStream->Read(&m_bWrapX);
    pStream->Read(&m_bWrapY);

    FAssertMsg((0 < GC.getNumBonusInfos()), "GC.getNumBonusInfos() is not greater than zero but an array is being allocated");
    pStream->Read(GC.getNumBonusInfos(), m_paiNumBonus);
    pStream->Read(GC.getNumBonusInfos(), m_paiNumBonusOnLand);

    if (numPlotsINLINE() > 0)
    {
        m_pMapPlots = new CvPlot[numPlotsINLINE()];
        int iI;
        for (iI = 0; iI < numPlotsINLINE(); iI++)
        {
            m_pMapPlots[iI].read(pStream);
        }
    }

    // call the read of the free list CvArea class allocations
    ReadStreamableFFreeListTrashArray(m_areas, pStream);

    setup();
}

Notice how everything is written out in the same order it's read in? That's the important part right, we need everything to be exactly where we expect it or there will be problems! If we add in a new variable to the output and input streams, and that changes the format, it's not compatible. So how do we combat this? Here's an example using standard C++ stuff (not in the game's context, but allows me to massively simplify!

To start with let's look at what you currently have;

Spoiler :
Code:
#include <iostream>
#include <fstream>
#include <vector>

class Unit
{
private:
    int m_iHealth;

public:
    Unit()
    {
        m_iHealth = 0;
    }

    Unit(int health)
    {
        m_iHealth = health;
    }

    void write(std::ofstream& output)
    {
        // We need to write a space as well, just to separate the contents of the file
        // The << operator is a writing operator to a stream, which output is
        output << m_iHealth << " ";
    }

    void read(std::ifstream& input)
    {
        // Reads the next token in the input stream that is an int and puts it into m_iHealth;
        input >> m_iHealth;
    }
};

// The game simply maintains a list of units, bit boring but hey-ho
class Game
{
private:
    std::vector<Unit> m_aUnits;

public:
    void addUnit(Unit unit)
    {
        m_aUnits.push_back(unit);
    }

    void write(std::ofstream& output)
    {
        output << m_aUnits.size() << " ";
        for (int i = 0; i < m_aUnits.size(); i++)
        {
            m_aUnits[i].write(output);
        }
    }

    void read(std::ifstream& input)
    {
        int size;
        input >> size;
        m_aUnits.resize(size);
        for (int i = 0; i < m_aUnits.size(); i++)
        {
            m_aUnits[i].read(input);
        }
    }
};

// If a save exists, we will load it, add a new unit and write it back out again
int main()
{
    std::ifstream input("savegame.txt");
    Game game = Game();
    if (input.is_open())
    {
        // the file exists, we'll read it in, do stuff and write it back out
        std::cout << "Reading savegame!" << std::endl;
        game.read(input);
        input.close();
    }

    game.addUnit(Unit(100));

    std::ofstream output("savegame.txt");
    if (output.is_open())
    {
        std::cout << "Saving the game" << std::endl;
        game.write(output);
        output.close();
    }

    return 0;
}
[/FONT]

As the comment in main suggests, if we were to change what we write and read during saving/loading, this will break. When we run this program, we will generate a file with the following contents

Code:
1 100

if we run it again we get
Code:
2 100 100

Now with that file in mind, let's add another field to unit;

Spoiler :
Code:
class Unit
{
private:
    int m_iHealth;
    int m_iStrength;

public:
    Unit()
    {
        m_iHealth = 0;
        m_iStrength = 0;
    }

    Unit(int health, int strength)
    {
        m_iHealth = health;
        m_iStrength = strength;
    }

    void write(std::ofstream& output)
    {
        // We need to write a space as well, just to separate the contents of the file
        // The << operator is a writing operator to a stream, which output is
        output << m_iHealth << " ";
        output << m_iStrength << " ";
    }

    void read(std::ifstream& input)
    {
        // Reads the next token in the input stream that is an int and puts it into m_iHealth;
        input >> m_iHealth;
        input >> m_iStrength;
    }
};

(in main we also change to game.addUnit(Unit(100, 50));).

Now let's run this again and see what the new save file is;

Code:
3 100 100 0 0 100 50

This doesn't make any sense! We have the first unit with health and strength 100, the second unit with 0 health and 0 strength and the third unit is ok. This is precisely the sort of stuff that goes on to corrupt your game. So what happened? Well, each unit now reads in 2 ints from the file, the first unit read two ints which were the two healths of the units in the previous version and then the second unit couldn't read anything, as such C++ silently failed and 0 was left in both fields. Right, so now let's ammend this. Unfortunately there is no way of recovering our original save so it works, so I'll create a save file manually after this code change;

Spoiler :
Code:
#include <iostream>
#include <fstream>
#include <vector>

const int VERSION_NUMBER = 2; //CHANGED

class Unit
{
private:
    int m_iHealth;
    int m_iStrength;

public:
    Unit()
    {
        m_iHealth = 0;
        m_iStrength = 0;
    }

    Unit(int health, int strength)
    {
        m_iHealth = health;
        m_iStrength = strength;
    }

    void write(std::ofstream& output)
    {
        // We need to write a space as well, just to separate the contents of the file
        // The << operator is a writing operator to a stream, which output is
        output << m_iHealth << " ";
        output << m_iStrength << " ";
    }

    void read(std::ifstream& input, int saveVersion) //CHANGED
    {
        // Reads the next token in the input stream that is an int and puts it into m_iHealth;
        input >> m_iHealth;
        // Here's the real money maker
        // We only read in the strength, if we are at least version two!
        if (saveVersion >= 2)
        {
            input >> m_iStrength;
        }
        else
        {
            // Some sensible default value, I'll set it to something distinctive
            m_iStrength = 15;
        }
    }
};

// The game simply maintains a list of units, bit boring but hey-ho
class Game
{
private:
    std::vector<Unit> m_aUnits;

public:
    void addUnit(Unit unit)
    {
        m_aUnits.push_back(unit);
    }

    void write(std::ofstream& output)
    {
        output << m_aUnits.size() << " ";
        for (int i = 0; i < m_aUnits.size(); i++)
        {
            m_aUnits[i].write(output);
        }
    }

    void read(std::ifstream& input, int saveVersion) //CHANGED
    {
        int size;
        input >> size;
        m_aUnits.resize(size);
        for (int i = 0; i < m_aUnits.size(); i++)
        {
            m_aUnits[i].read(input, saveVersion); //CHANGED
        }
    }
};

// If a save exists, we will load it, add a new unit and write it back out again
int main()
{
    std::ifstream input("savegame.txt");
    Game game = Game();
    if (input.is_open())
    {
        // the file exists, we'll read it in, do stuff and write it back out
        std::cout << "Reading savegame!" << std::endl; //CHANGED
        int saveVersion; //CHANGED
        input >> saveVersion; //CHANGED
        game.read(input, saveVersion); //CHANGED
        input.close();
    }

    game.addUnit(Unit(100, 50));

    std::ofstream output("savegame.txt");
    if (output.is_open())
    {
        std::cout << "Saving the game" << std::endl;
        output << VERSION_NUMBER << " "; //CHANGED
        game.write(output);
        output.close();
    }

    return 0;
}

What you can see in there is versioning of the saves, here's an save analogous to the original example;

Code:
1 2 100 100

That 1 at the start is the version number of that save. Right, so now let's execute the program and see what the save file looks like after;

Code:
 2 3 100 15 100 15 100 50

This looks more promising! The units imported from version one were given a default value of 15 for their strength then another version 2 unit was added, with the expected strength of 50. Let's run it again to confirm everything still works;

Code:
2 4 100 15 100 15 100 50 100 50

Great! Nothing seems to be going wrong here! That, in essence, is what you need to do to get your save compatibility. the last thing I'll cover is what if we wanted to remove the health field and just wanted an alive flag instead. Let's amend the unit class to see how we maintain savegame compatibility in this case and add some intelligent recovery of the state.

Spoiler :
Code:
const int VERSION_NUMBER = 3; //CHANGED

class Unit
{
private:
    bool m_bAlive;
    int m_iStrength;

public:
    Unit()
    {
        m_bAlive = false;
        m_iStrength = 0;
    }

    Unit(bool alive, int strength)
    {
        m_bAlive = alive;
        m_iStrength = strength;
    }

    void write(std::ofstream& output)
    {
        // We need to write a space as well, just to separate the contents of the file
        // The << operator is a writing operator to a stream, which output is
        output << m_bAlive << " ";
        output << m_iStrength << " ";
    }

    void read(std::ifstream& input, int saveVersion)
    {
        // This time we need to be more careful we are now version 3
        // For anything less than version 3 there is an int in the stream
        // Otherwise there is the bool we want
        if (saveVersion < 3)
        {
            int health;
            input >> health;
            // if the health is above 0 it's alive!
            m_bAlive = health > 0;
        }
        else input >> m_bAlive;
        // Here's the real money maker
        // We only read in the strength, if we are at least version two!
        if (saveVersion >= 2)
        {
            input >> m_iStrength;
        }
        else
        {
            // Some sensible default value, I'll set it to something distinctive
            m_iStrength = 15;
        }
    }
};

This is all we needed, now let's load the last save we made which had 4 alive unit's in version 2 and see what comes out;

Code:
3 5 1 15 1 15 1 50 1 50 1 50

Right, we have version 3, 5 units, good. Now the first unit is alive (boolean true false is 1 0 in the file) with 15 strength, ditto for the second and then the final 3 are all alive with 50 strength, exactly what we wanted!

Hopefully that gives you a good indicator of how you should proceed. Bear in mind in the last example we recovered by reading in the old value and adjusting it to our new needs. If we simply didn't need it anymore, we'd just have read it in from the stream but just not done anything with it!



 
Top Bottom