Civ3 Show-And-Tell

Heh, while playtesting I started jotting down stuff I'd like to add. In short, it's "almost everything CivAssist II does." :lol:

The actual list is already long. For my own purposes I really want the tech trade options, tech costs and corruption calculations. ("Will the next shield per turn be wasted or not?" "Does this city need a courthouse before getting serious about producing non-worker/settlers?")

Oh, and OCN which isn't in the list yet. And flip probability which also isn't on the list yet.

Corruption and flip probability are probably far future items even though I want them sooner. That info is far beyond simple lookups.

Items like civ attitude, cultural opinions, and best known enemy unit would seem to be low-hanging fruit that are probably located where I've recently been looking. They aren't that important to me, though, and they don't change very often, so finding them may be a bit more work. But I've got everything set up to watch the area where I think that info is. Hmm.

Spoiler wish list :

Stuff I'd like to find and add.

  • Civ list
    • Civ attitude (polite/cautious/annoyed/etc.)
    • Culture opinion both directions ("The Indian people are impressed with our culture")
    • Best known enemy unit
    • Trades
    • Techs (next priority)
    • Gold
    • Resources
    • Workers
    • Contacts (hide column until tech is known?)
    • Maps (hide column until tech is known?)
    • Embassy / spy
  • Alerts
    • Civ at war will talk
    • New contact
    • New tech trades available
    • Unhappiness
  • Generic / player info
    • Turn #
    • Year
    • Player leader, title, and civ
  • Far future
    • If spy planted, appropriate enemy info
      • Military info
    • If embassy, appropriate info
      • RoPs, alliances, embargoes, trades
    • Diplomatic action costs
    • City report
      • Improvements
      • Corruption & waste
  • Tech
    • Tech costs
  • Victory status
    • Especially tiles towards domination
    • Detect enabled victory conditions
    • Culture progress and projections
 
Hmm, "best known unit" doesn't seem to be in the LEAD section, assuming it's an index to PRTO. I just met a civ by seeing their warrior (index 6, and the trade adviser reports warrior) and didn't find a new 06 in the LEAD diff. Think I'll go demand stuff until I make 'em mad and see what changes.

Edit: Huh, between the changes I've seen and Antal1987's dumps, I think there is an array of arrays of int32s ([32][23]int32) tracking diplomatic attitudes. 23? Wonder if that's the number of actions possible to take against an opponent?

Edit 2: https://www.civfanatics.com/civ3/strategy/game-mechanics/ai-attitude-study/ Between this and the array I'm looking at, I think attitude is a calculation, and this array is all the attitude-affecting stuff observed in the article. In particular tribute demands increasing double attitude penalty at annoyed and furious would explain why I jumped from 2 to 7 when demanding a couple of times to get from annoyed to furious. Although clearly the numbers aren't exact. And declaring war didn't alter this table. So much for "low-hanging fruit."

Edit 3: I switched to looking for tech. And I'm not finding it. I'm probably going to have to set it up so I can hexDump the whole file (or maybe just the GAME part) and diff it between manual saves in which I do one simple thing like trade techs.
 
Last edited:
Saturday thoughts:

Figuring this stuff out is a circular process. I still maintain that there isn't a "save file format" per se, but it's just a serialized binary write of class object data. After finding that [32][23]int32 array, I must remind myself that this is probably class object member data, and there are likely class methods to go along with them, although of course they wouldn't be represented in the save file. So yeah, this collection of apparent diplomacy/attitude history probably had methods that converted that info into a single attitude. Like lead.attitude(opponent) or something.

In short, in the general case when an obvious single-spot value for something isn't apparent, that value is probably the output of a class method based on the class' object member data.

Or even shorter, I think it's safe to assume that all the component facts needed to determine attitude are within that array matrix.

Not that I'm eager to work on that anytime soon, but playing with those numbers and the attitude study linked in the last post would be the path to getting the attitude value.

But this applies to the wider game data, too: generally speaking, all the information needed to determine a game fact should be either encapsulated in one part of the save and/or cross-referenced by index (e.g. tech names and prerequisites).

Somewhat different topic: Does anyone know how to decode the gold amount? I think I know where it is, and it seems to be two int32s encrypted or otherwise obfuscated, but I'm clueless on how to read it. CAII could do it, so the knowhow is out there somewhere.

I'm liking my plan to just diff the entire game data part of the SAV file. There should be lots of things I can find locations for by making two saves in a turn while doing one thing between saves (trade for gold, trade for tech, declare war, gift tech, start a worker action, move a unit, rush production, etc.) Although come to think of it, it might make more sense to be able to have two saves loaded at once and then just diff various queries between them. That would let me focus down on a change from the big picture to a more specific query with appropriate array cadence and offsets.

Which means I'm starting to desire the ability to browse saves to open, so I might work on a UI for that soon.

Or I might accomplish nothing today. I've been focusing hard on this for a while and may need a break.

Edit: I almost gave up for the day. Techs had me stumped at first, but now I found them! They aren't a by-leader list of tech indexes; they're a by-tech list of bitmaps indicating which civ has them, and it's located early-ish in the game's GAME section.

Edit 2: Ok, I'm done for the day. The techs list is not in a consistent location, even between two games with the default biq and the same size and number of players. :P
 
Last edited:
Haven't read through all the latest updates, but did run Alpha 8, and sure enough there's an error:

errors.html said:
CreateFile D:\Civilization III\Conquests\saves\Auto\Conquests Autosave 4000 BC.SAV.SAV: The system cannot find the file specified.

But I may have added a .SAV to the file name a few days ago when trying to get it to work without the error log. I eventually convinced Civ to update the Latest Save by exiting to desktop, so that Conquests Autosave 3750 BC was the Latest Save, but even after restarting CIA3 it hasn't picked that one up, and doesn't have an error.

Either way, it seems like my Latest Save is not being updated except when I exit Civ. Alpha 1 seemed to pick things up despite that, maybe by monitoring the folder? I wonder if it's worth the trouble looking at the Latest Sav, when it seems to be inconsistent (space vs not space, not being updated).
 
Yeah, the ".SAV" isn't included in the .ini, at least on my system, but then again mine has a space at the end. And even if the latest save load fails, it should pick up the first sav written out. Does it not do that?

The latest save thing is just to give it something to load on its startup. Whether that fails or not shouldn't impact anything else it does.
 
Ok, I'm done for the day. The techs list is not in a consistent location, even between two games with the default biq and the same size and number of players. :p

I guess I was done for at least a couple of months. I got frustrated with tech and discouraged when I realized the "last save" problem likely meant only two people in the world ever tried my program, including me.

I've been playing Civ3 for a few days during shelter-at-home. I only just now fired up CIA3. It's only listing 3 opponents on a huge map with 16 civs, 9 of which I've met. So something is wrong there.

Oh, I already see what it is. (Yay dev console!) The era index is wrong. It's showing AA for my first 3 opponents when they're middle ages, and the next civ is showing era 6, so I'm grabbing the era index from the wrong place. (Edit: and when the nonexistent era index causes an error, the table stops populating at that point.)

Anyway, having the techs lists at a glance would have been really, really handy. So I'm at least thinking about poking at CIA3 again, but I'm not quite feeling motivated yet. On the other hand, it looks like my available activity options are limited for a few weeks.

If figuring out the entire GAME section isn't easily doable, I wonder if I can take a good guess at where it is and trial fit the techs list. Barbs are a civ, so their tech list will be the same for the default BIQ; I have no idea if custom BIQs give barbs different techs.

Then again, since the list is by tech and not by civ, that might not be the handiest pattern to look for.

Actually, for games with <30 opponent civs I should be able to find the pattern there.

Oh I just remembered it's a bitmap. Actually that works well, too. In fact it might be easier to bitwise-AND the tech list for such tests. Come to think of it, it's a lot like finding a character sequence, but it's an int32 sequence after featurizing with the bitwise AND. I could modify the Horspool search.

Edit: I figured out the era issue and have it fixed in my dev build. I'm still playing a game and just eliminated a rival. Oops, that's something I have to look for. The table still lists the opponent, and as "at war" but with 0 cities.
 
Last edited:
I think I got the techs figured out! My intuition was thinking it had to be map-related, and continents seemed to make sense in a weird way. But I started looking at Antal1987's dumps and it looked like it thought there might be a city-count-based list in front of techs, so I pursued that a bit. But no.

So I went back to my intuition and sure enough, the by-tech int32 list (of civ bitmask) is at offset 852 + 4*numContinents! (Of the GAME section.) I tried on a few different maps at different stages and different sizes, and that offset is working.

There is still a lot of work to do to simply present the information in a useful manner and to mask spoiling info (such as your not having the prereq tech to see the other civ has two techs or more beyond you) and to spot trade opportunities (which works very similar to spoiler masking but needs to be done for all civs). But at least I can reliably locate the info now.

(Side note: number of continents in the sav file is not fully intuitive; e.g oceans and inland seas are "continents", too. I'm curious why there's apparently an int32 array for continent info when there is also CONT sections; maybe it has to do with trade networks, but there can easily be more than 32 of those.)

Spoiler Current dev/debug output of tech list :

temp delete Cleopatra of the Egyptians, 510 AD.SAV

Bronze Working - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Masonry - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Alphabet - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Pottery - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
The Wheel - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Warrior Code - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Ceremonial Burial - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Iron Working - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Writing - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Mysticism - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Mathematics - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Philosophy - Egypt Maya Ottomans Russia Byzantines Arabia Germany
Code of Laws - Egypt Maya Russia Byzantines Arabia Germany
Literature - Maya Russia Byzantines Arabia
Map Making - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Horseback Riding - Egypt Maya Ottomans Russia Byzantines Arabia Germany
Polytheism - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Currency - Egypt Maya Russia Byzantines Arabia
The Republic - Egypt Maya Russia Byzantines Arabia
Monarchy - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Construction - Egypt Maya America Ottomans Russia Byzantines Arabia Germany
Monotheism - Egypt Russia Arabia
Feudalism - Egypt Maya Russia Byzantines Arabia
Engineering - Egypt Maya Russia Arabia
Chivalry - Egypt
Invention - Egypt
Gunpowder - Egypt


Edit: FYI, numContinents is an int16 at offset 4 of the first WRLD section. Although come to think of it, it must be elsewhere, too, as that is long after the tech data I'm looking at!

Edit 2: Maybe the continent thing has to do with resources. What is the maximum number of resources in the game? Is it 32? I could imagine an int32 array with a bitmask of resources available on that continent.
 
Last edited:
There's a good thread on the maximum number of resources here. The summary is the limit is 256 resources, of which 32 may be luxury/strategic (and they must be spaced correctly in the list of resources), and the rest must be bonus to avoid any bugs.
 
There's a good thread on the maximum number of resources here. The summary is the limit is 256 resources, of which 32 may be luxury/strategic (and they must be spaced correctly in the list of resources), and the rest must be bonus to avoid any bugs.

Ok cool, thanks! That actually makes some sense to me from a binary save-file format perspective and my observations so far. I can see how an int32 bitmask might be useful for tradable resources (meaning strat & luxury) combined with trade networks, but again I have an incomplete picture because numContinents != numTradeNetworks to my understanding, but I haven't looked at the trade network data in years, so maybe I'm wrong there.

So that "one per row" rule implies that the bitmask is performed on the numResources modulo 32. At least that is how I could imagine the problems described happening based on what I see in the sav.

Huh, out of curiosity, can there be sea- or ocean-placed strategic/luxury resources? In Civ3...I know they can be in Civ4.

---

I had a minor UI epiphany today (tonight? my hours are off): I had been wondering how to organize the data, especially since I want to pull tech info for different purposes. Aping the CAII general organization (or what I recall of it since I can't run it now) might have been a default option, but I was having trouble figuring out how to organize the web components. The epiphany was to ape the in-game advisor screens structure. For example I shouldn't have a tech window with both trade options and tech costs; instead I should have a trade screen that for now shows tech trade opportunities and in the future will show resources, contacts, workers, etc..

Because the aim of the tool is to collate data that is mostly available in-game but tedious to assemble. So the trade interface summarizes available trade items. The domestic (F1 advisor) interface would show city data, military (F3 advisor) would show unit info, etc.. Not everything fits into this paradigm like the forest-chop map, but it's a start. And it should make it a bit easier to use for a new CIA3 user.

Oh, and I just had another epiphany for sav file monitoring: So far I've only read "Latest Save" on CIA3 startup and then watch for new SAVs to appear. But that misses when you load a file. I could watch the conquests.ini file for changes, too, so when you load a save in-game the interface would update. Not sure why I didn't think of that before. It would have saved me a lot of "temp save"s to force a data refresh when loading another game.
 
out of curiosity, can there be sea- or ocean-placed strategic/luxury resources? In Civ3...I know they can be in Civ4.

Puppeteer, yes this is possible in C3C, but they must be connected with a road to a city, to achieve, that the luxury/strategic resource is in the resource box of that city - and this means that you need sea workers to construct those roads.

searoads1-jpg.397776


Here is a link to the post with my screenshot: https://forums.civfanatics.com/threads/civ-3-work-boats.551651/#post-13943564

The problem why the sea workers didn´t make it into CCM2 was the nasty attitude of the AI to kill all their sea workers from time to time without any recognizable reason.
 
can there be sea- or ocean-placed strategic/luxury resources? In Civ3...I know they can be in Civ4.
It's certainly possible to set Strat/Lux resources to appear on water tiles using even just the basic Firaxis Editor.

I did it in my possibly-never-to-be-finished Mod-in-progress, creating an 'Offshore Oil' resource in an attempt to limit the number of places where an Offshore Platform could be built (by also assigning the "Required resource must be in city radius" flag).

However, I never got as far as playtesting it, because I later noticed that this flag only seemed to work for land-based resources, when the resource-tile is also roaded (I was lucky enough to capture a town that could build the IronWorks! But I didn't get the pop-up until both the Iron and Coal were roaded). And since my mod was supposed to tweaks to the epic-game which didn't change things too much, I didn't really want to start messing around with adding a (sea-)unit with the 'build roads' ability, and/or allow roads to be built over water.

IIRC, @Kirejara has experimented with 'sea-roads', with some success (albeit possibly using Quintillus' or Steph's Editor: not sure if the default Firaxis Editor will allow the needed settings). But of course, the AI can't use this ability (either)...
 
... the AI can't use this ability (either)...

The AI uses this option. My screenshot was made in Debug mod about an AI sea-road action and shows the resource box of an AI city - but as said, from time to time the AI did kill all their sea-workers.
 
Thanks @Civinator & @tjs282 ! Sea workers, interesting concept. Too bad the AI is the AI. So I think I sort of understand how it's stored, and I can see it would be difficult or impossible to patch even using Antal1987's skills. (I mean patch the 32-lux+strat limit which is clearly related to an int32 bitmask referring to them. And yes it's somehow tangled with "continents" including oceans and trade networks.)


As for CIA3, I have it listing a simple table with the tech differences! Now I need to add the prerequisite check to declutter the right column and de-spoilify the left.

Edit: I have it only showing players with contact who will talk, but I may want to change the code to show warring civs who won't talk and indicate no trades available due to war.

Edit 2: I also need to remove eliminated civs from that list. Three civs in the screenshot are actually eliminated. (And funnily enough, neither of the two farthest-behind-in-tech civs are eliminated; they're just not keeping up.)
 

Attachments

  • 2020-03-31 00_14_37-Window.png
    2020-03-31 00_14_37-Window.png
    32.9 KB · Views: 123
Last edited:
@Quintillus have you tried alpha 8 again lately?

I just did a search through old backups for conquests.ini, and none of them have .SAV in the "Latest Save" value. Although none of them have a space after the save name, either, except for my current install which still has it after many different save file loads.

So I'm wondering if—assuming you've loaded a different game since you last checked—it's working now or still seeing the .SAV.SAV error.

I think this available tech trade feature I'm wrapping up will be really useful for others, so I'm hoping to be sure the save loading & watching is reliable across systems before I make a release thread with a somewhat cleaned-up beta release.

I currently have it showing the techs right. Was able to figure out the prerequisites, and I also had to add an era check since e.g. Monotheism has no tech prerequisites but can't be traded to an ancient age civ. I also have some behind-the-scenes cleanup to do as parts are currently hackish, and if a save with a huge number of continents and/or a much larger than the default bic tech count then something would fail.

Spoiler tech talk :
Since the tech mask offset is variable based on continents and I'm only doing one query call, I'm just grabbing an arbitrary number of integers hopefully enough to account for likely continent counts and the default biq tech count, and then after I have the data reading the right portion of ints. But for large counts of continents and/or techs my hard-coded hack will not grab enough data. To fix it I'll probably put a new request type in the Go code to return the proper mask list without requiring multiple GQL calls.


Random question: why do techs have X and Y values? What does that do? I can't imagine it's of any use in a random map game, so I guess it's for custom maps, but I have no idea what tying a tech to X+Y would do. Oh, is this X/Y for the F6 tech advisor screen layout?
 
CIA3 v0.4.1-alpha9 is here!

New killer feature since last version: available tech trades! It should work with all scenarios/conquests/BIQs and should show the same techs available as the F4 Foreign Advisor screen would show. But all in one table instead of slogging through every leader head.

Known issues with trade, to be fixed in the future:
  • Eliminated civs still show in table
  • Civ at war who wont talk won't have a row in the trade table

I'm going to go playtest this for a while, because the tech trade options is the biggest thing I miss about CAII and MapStat.
 
@Quintillus have you tried alpha 8 again lately?

I just did a search through old backups for conquests.ini, and none of them have .SAV in the "Latest Save" value. Although none of them have a space after the save name, either, except for my current install which still has it after many different save file loads.

So I'm wondering if—assuming you've loaded a different game since you last checked—it's working now or still seeing the .SAV.SAV error

It looks like I tried Alpha 8 when it was new... I just tried Alpha 9, and no dice. I started a new game for testing, and noticed it doesn't show up in the conquests.ini list, even a few turns in. But the entry is:

Latest Save=C:\Software\Games\Steam\steamapps\common\Sid Meier's Civilization III Complete\Conquests\saves\Osman of the Ottomans, 1782 AD

It occurs to me that I have a symbolic link from what was formerly my Steam install, to my CD-based install. This could be a tripping point - although I'm pretty sure when I last tested, I was launching the CD install directory, and the Latest Save was under D:\Civilization III.

I guess I'm a bit confused by the reliance on the Latest Save in conquests.ini, since it doesn't seem to update reliably. Is there a reason that's preferable to monitoring the \Conquests\Saves folder for new files?

I'm also curious which development environment you use. I checked out the code and am installing Go, to see if I can figure out what's going on, but as I've never used Go before, I'm not really familiar with the ecosystem. The thought being if I can get it up and running locally, it might be relatively easy to get an error message produced, which even if I couldn't fix it in Go, might still make it easier to triangulate what's going on than going back-and-forth on the thread.

---------

Edit: Got Go and TDM-GCC64 installed on my Windows 8.1 x64 box (my main desktop). I've tried a couple things:

- Running "go build" and "go build ." from the c3sat\cmd\cia3 folder (I left off the part that hides the console as I'd like to see the console). It seems to hang forever.
- Running "go build cia3" from the c3sat\cmd folder. It says "can't load package: package cia3 is not in GOROOT (C:\Programmign\Go\src\cia3)

I'm probably doing something wrong, but it is not yet clear what. Maybe pkger is required to compile it, not just for embedding the HTML?
 
Last edited:
I guess I'm a bit confused by the reliance on the Latest Save in conquests.ini, since it doesn't seem to update reliably. Is there a reason that's preferable to monitoring the \Conquests\Saves folder for new files?

It *does* monitor the folders for new save files. The .ini lookup is *only* when the program first starts. So I've kind of been confused on your focusing on that since it should have loaded the first new write into Saves or Saves\Auto after starting cia3.exe.

You're saying it doesn't pick up the saves when you take turns either? Ok, that's new info for me.

I'm also curious which development environment you use. I checked out the code and am installing Go, to see if I can figure out what's going on, but as I've never used Go before, I'm not really familiar with the ecosystem. The thought being if I can get it up and running locally, it might be relatively easy to get an error message produced, which even if I couldn't fix it in Go, might still make it easier to triangulate what's going on than going back-and-forth on the thread.

I use VSCode and the Go plugins it keeps recommending.

Got Go and TDM-GCC64 installed on my Windows 8.1 x64 box (my main desktop). I've tried a couple things:

- Running "go build" and "go build ." from the c3sat\cmd\cia3 folder (I left off the part that hides the console as I'd like to see the console). It seems to hang forever.
- Running "go build cia3" from the c3sat\cmd folder. It says "can't load package: package cia3 is not in GOROOT (C:\Programmign\Go\src\cia3)

I'm probably doing something wrong, but it is not yet clear what. Maybe pkger is required to compile it, not just for embedding the HTML?

Huh. `go build .` should pull all the required dependencies. Do you have the GOPATH environment variable set? ...

Oh, if you haven't used Go before, it's a bit opinionated on file structure. In a Go setup you'd have a Go home (defaults to %USERPROFILE%\go , but you can set %GOPATH% to any desired folder), with src, pkg, and bin folders under that, and then you would `go get github.com/myjimnelson/c3sat` and Go would put the source tree in %GOPATH%\src\github.com\myjimnelson\c3sat\ . Although since we're in the develop branch you'd then have to go there and `git checkout develop`.

Then when you `go build .` it should pull other required libraries into the src\* tree.

If I get a chance I'll try to set it up on a fresh system and see if I hit any snags.

Edit: GOPATH, not GOHOME. Don't mess with GOHOME; that can exist but refers to the install location.
 
Last edited:
It worked for me on a fresh system. I already had Go 1.13.5 installed (same as my main machine) and my Go path in the default location. I had to install the tdm-gcc on that box.

  • go get github.com/myjimnelson/c3sat - it throws an error about no file found, but "go get" tries to compile and install the exe in <go path>\bin\ , so it's complaining about no compile target there. Ignore that.
  • cd to the repo and `git checkout develop`
  • cd to cmd\cia3 and `go build .` - it pulls all required libraries and compiles
You should run the pkger command for distribution, but if you don't it will work if run from the cia3 folder but it pulls files from the hard drive html\ folder instead of being embedded in the executable. Actually quite useful for when I'm doing front-end only work so I don't have to constantly recompile. If the .exe is moved elsewhere it will fail to find the html files. pkger is a bit werid.

Edit: Actually I'm not sure my/the working repo needs to be in the "right" spot, but go home needs to exist, possibly with src, pkg, and bin folders in it, for Go to fetch the dependencies.

Edit 2: Changed "go home" & "gohome" to go path...it's path not home for the Go source/bin/pkg tree location.
 
Last edited:
Here is an executable that will just log all the file actions (and errors) to the console: watchercheck.exe . I just copied/pasted the code from cia3 and took out anything that doesn't involve file reading/watching into one file. It doesn't need gcc or fyne, just fsnotify, registry, and standard go libraries.

You can drop this and compile it anywhere as long as GOPATH is set up for the dependencies. (Or you can use the .exe I linked.)

Spoiler 177 lines of code for watchercheck.exe :

Code:
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "strings"
    "time"

    "github.com/fsnotify/fsnotify"
    "golang.org/x/sys/windows/registry"
)

var savWatcher *fsnotify.Watcher
var debounceTimer *time.Timer
var watchList = new(watchListType)

const debounceInterval = 300 * time.Millisecond

// really should be const, but can't have literal string arrays as const
var civInstallPathKeyTry = []string{
    `SOFTWARE\WOW6432Node\Infogrames\Conquests`,
    `SOFTWARE\Infogrames\Conquests`,
}

type watchListType struct {
    watches []string
}

func findWinCivInstall() (string, error) {
    var k registry.Key
    var err error
    for i := 0; i < len(civInstallPathKeyTry); i++ {
        k, err = registry.OpenKey(registry.LOCAL_MACHINE, civInstallPathKeyTry[i], registry.QUERY_VALUE)
        if err != nil {
            return "", err
        } else {
            break
        }
    }
    defer k.Close()
    s, _, err := k.GetStringValue("install_path")
    if err != nil {
        return "", err
    }
    return s, nil
}

// Look in conquests.ini in the Civ3 Conquests install path for the "Lastest Save=" value
func getLastSav(path string) (string, error) {
    const key = `Latest Save=`
    file, err := os.Open(path + `\conquests.ini`)
    if err != nil {
        return "", err
    }
    ini, err := ioutil.ReadAll(file)
    if err != nil {
        return "", err
    }
    var pathStart, pathEnd int
    for i := 0; i < (len(ini) - len(key)); i++ {
        if ini[i] == key[0] {
            if string(ini[i:i+len(key)]) == key {
                pathStart = i + len(key)
                break
            }
        }
    }
    for i := pathStart; i < (len(ini)); i++ {
        if ini[i] == '\r' || ini[i] == '\n' {
            pathEnd = i
            break
        }
    }
    if pathEnd <= pathStart {
        return "", fmt.Errorf("Failed to find Latest Save in conquests.ini")
    }
    s := string(ini[pathStart:pathEnd])
    // My .ini has a space after the filename, so trimming leading/trailing whitespace
    return strings.TrimSpace(s) + ".SAV", err
}

// Does not check to see if already added
func (w *watchListType) addWatch(path string) error {
    err := savWatcher.Add(path)
    if err != nil {
        return err
    }
    w.watches = append(w.watches, path)
    return nil
}

// Only deletes one
func (w *watchListType) removeWatch(path string) error {
    err := savWatcher.Remove(path)
    if err != nil {
        return err
    }
    for i := 0; i < len(w.watches); i++ {
        if w.watches[i] == path {
            // remove element from array by swapping last element and replacing with one-shorter array
            w.watches[i] = w.watches[len(w.watches)-1]
            w.watches[len(w.watches)-1] = ""
            w.watches = w.watches[:len(w.watches)-1]
            break
        }
    }
    return nil
}

func watchSavs() {
    var fn string
    for {
        select {
        case event, ok := <-savWatcher.Events:
            if !ok {
                return
            }
            fn = event.Name
            if event.Op&fsnotify.Write == fsnotify.Write {
                debounceTimer.Reset(debounceInterval)
            }
        case <-debounceTimer.C:
            // This will get called once debounceInterval after program start, and I'm going to live with that
            log.Println("New file in watched folders: ", fn)
        case err, ok := <-savWatcher.Errors:
            if !ok {
                return
            }
            log.Println("error:", err)
        }
    }
}

func loadStartupFiles(civPath string) {
    var err error
    lastSav, err := getLastSav(civPath)
    if err != nil {
        log.Println("error:", err)
    } else {
        log.Println("Latest Save: ", lastSav)
    }
}

func main() {

    var err error
    savWatcher, err = fsnotify.NewWatcher()
    if err != nil {
        log.Println("error:", err)
    }
    defer savWatcher.Close()

    // Read Win registry for Civ3 Conquests path
    civPath, err := findWinCivInstall()
    if err == nil {
        loadStartupFiles(civPath)
        // Add Saves and Saves\Auto folder watches
        err = watchList.addWatch(civPath + `\Saves`)
        if err != nil {
            log.Println("error:", err)
        }
        err = watchList.addWatch(civPath + `\Saves\Auto`)
        if err != nil {
            log.Println("error:", err)
        }

    } else {
        log.Println("error:", err)
    }

    // Set up file event handler
    debounceTimer = time.NewTimer(debounceInterval)
    watchSavs()

}


There will be one blank "New file in watched folders: " line on startup, and that's expected because of how I'm debouncing the write notifications.

The function that normally catches these notifications only takes action on .SAV files (not case-sensitive). So it ignores blanks and directory changes spit out by the watcher.

PS C:\Users\Jim\src\go\src\local\watchercheck> .\watchercheck.exe
2020/04/02 00:25:43 Latest Save: F:\SteamLibrary\steamapps\common\Sid Meier's Civilization III Complete\Conquests\saves\large Henry of the Portuguese, 2590 BC.SAV
2020/04/02 00:25:43 New file in watched folders:
2020/04/02 00:26:25 New file in watched folders: F:\SteamLibrary\steamapps\common\Sid Meier's Civilization III Complete\Conquests\Saves\Auto
2020/04/02 00:26:40 New file in watched folders: F:\SteamLibrary\steamapps\common\Sid Meier's Civilization III Complete\Conquests\Saves\Auto\Conquests Autosave 4000 BC.SAV
2020/04/02 00:27:32 New file in watched folders: F:\SteamLibrary\steamapps\common\Sid Meier's Civilization III Complete\Conquests\Saves\Auto\Conquests Autosave 3950 BC.SAV
2020/04/02 00:27:46 New file in watched folders: F:\SteamLibrary\steamapps\common\Sid Meier's Civilization III Complete\Conquests\Saves\test manual save Pachacuti of the Inca, 3950 BC.SAV
 
Last edited:
Back
Top Bottom