Thorvald of Lym

A Little Sketchy
Joined
Nov 21, 2005
Messages
8,808
Location
A Palace north of Oslo
I don't know how many people have played the Extended Original mod as the Centaurans, but poking through the events file I discovered that there isn't a reciprocal mission to Earth. Instead, the spaceship arrival script applies to all civs, so the aliens end up colonizing their own planet. :crazyeye:

The goal of this mod is to translate the original script to Lua, allowing for more robust code that can differentiate between human and Centauran landings without requiring fixed civ names. I'm also eyeing some cosmetic and minor gameplay tweaks, specifically a reshuffling of Engineers: only Unit Slot 2 has access to the Transform function, regardless of the Engineer flag added in ToT, so Ultrastring Theory prevents creation of new terraform-capable units*.

Attached is the current Events.lua for review; note that you may wish to rename Events.txt, as I've had issues with the game reading both files at once. All critical functions work, but I'm seeking input on potential optimizations and help with substituting the original map change script:

WHAT WORKS
  • Negotiation mask: Humans and Centaurans can't interact until discovery of Ultrastring Theory.
  • Tech permissions: Centaurus arrival enables research of Ultrastring Theory by all players.
  • Unit spawn: Earthlings colonize Centaurus, and Centaurans colonize Earth.
  • Endgame override: Spaceship arrival doesn't prompt score tally.
WHAT'S NEEDED
  • Map change: Five turns into the game, a rectangular zone at (65,35)–(80,50) is turned to Scrubland to guarantee a viable spawning zone for the colonists. TNO's original guide doesn't provide an equivalent script for the area selection used by the events macro, and I don't know a good scenario to mine for examples.
WHAT COULD BE OPTIMIZED
  • Text popups: The messages prompted by Reality Engineering and Transcendence trigger twice, despite using the JustOnce routine, and I don't know why.
 

Attachments

  • CentauranGambit.zip
    2.1 KB · Views: 8
Last edited:
WHAT COULD BE OPTIMIZED
  • Text popups: The messages prompted by Reality Engineering and Transcendence trigger twice, despite using the JustOnce routine, and I don't know why.
Did you start the game with an events.txt file that isn't empty? Maybe the macro events got registered or something.

WHAT'S NEEDED
  • Map change: Five turns into the game, a rectangular zone at (65,35)–(80,50) is turned to Scrubland to guarantee a viable spawning zone for the colonists. TNO's original guide doesn't provide an equivalent script for the area selection used by the events macro, and I don't know a good scenario to mine for examples.
Here's some (untested -- let me know if I made a mistake you can't easily fix) code to achieve this:

Code:
civ.scen.onTurn(function (turn)
  -- Warn when Reality Engineering (98) is researched.
  if civ.getTech(98).researched then
    justOnce("transcendWarn", function ()
      civ.ui.text(func.splitlines(transcendWarnText))
    end)
  end

  -- End game and play video when Transcendence (99) is discovered.
  if civ.getTech(99).researched then
    justOnce("transcendenceAchieved", function ()
      civ.ui.text(func.splitlines(transcendAchievedText))
      civ.playVideo("Scene6.avi")
      civ.endGame(true)
    end)
  end
  if turn == 5 then
      for x=65,80 do
          for y=35,50 do
              for z = 0,1 do
                  local newTerrain = civ.getBaseTerrain(z,1)
                  if civ.getTile(x,y,z) then
                      civ.getTile(x,y,z).baseTerrain = newTerrain
                  end
              end
          end
      end
  end
end)
Unit spawn: Earthlings colonize Centaurus, and Centaurans colonize Earth.
I happened to take a quick glance at your location tables, and many of them are for invalid coordinates. Civ II (x,y) map coordinates must be either both even or both odd. That's why the supplied change tile code has "if civ.getTile(x,y,z) then", to make sure the tile actually exists.
 
If you don't want to make a large rectangle of plains, here is some code to select a suitable landing location:
Code:
--  Returns a table of tiles around a center tile, the 
--  size of a city 'footprint'.  The indices are listed below
--  and are based on how city.workers determines which tiles
--  are worked.
--
--      #       #       #       #       #
--          #       #       #       #       #
--      #       #       #       #       #
--          #       20      13      #       #
--      #       12      8       9       #
--          19      7       1       14      #
--      #       6       21      2       #
--          18      5       3       15      #
--      #       11      4       10      #
--          #       17      16      #       #
--      #       #       #       #       #
--          #       #       #       #       #
--
-- If the center is at the edge of the map, absent tiles have nil values
---@param input cityObject|tileObject If table, the table must be tile coordinates.
---@return table<integer,tileObject>
local function cityRadiusTiles(input)
    if civ.isCity(input) then
        input = input.location
    end
    local tile = input
    local xVal = tile.x
    local yVal = tile.y
    local zVal = tile.z
    if civ.game.rules.flatWorld then
        return {
        [1] = civ.getTile(xVal+1,yVal-1,zVal),
        [2] = civ.getTile(xVal+2,yVal,zVal),
        [3] = civ.getTile(xVal+1,yVal+1,zVal),
        [4] = civ.getTile(xVal,yVal+2,zVal),
        [5] = civ.getTile(xVal-1,yVal+1,zVal),
        [6] = civ.getTile(xVal-2,yVal,zVal),
        [7] = civ.getTile(xVal-1,yVal-1,zVal),
        [8] = civ.getTile(xVal,yVal-2,zVal),
        [9] = civ.getTile(xVal+2,yVal-2,zVal),
        [10] = civ.getTile(xVal+2,yVal+2,zVal),
        [11] = civ.getTile(xVal-2,yVal+2,zVal),
        [12] = civ.getTile(xVal-2,yVal-2,zVal),
        [13] = civ.getTile(xVal+1,yVal-3,zVal),
        [14] = civ.getTile(xVal+3,yVal-1,zVal),
        [15] = civ.getTile(xVal+3,yVal+1,zVal),
        [16] = civ.getTile(xVal+1,yVal+3,zVal),
        [17] = civ.getTile(xVal-1,yVal+3,zVal),
        [18] = civ.getTile(xVal-3,yVal+1,zVal),
        [19] = civ.getTile(xVal-3,yVal-1,zVal),
        [20] = civ.getTile(xVal-1,yVal-3,zVal),
        [21] = civ.getTile(xVal,yVal,zVal),
        }
    else
        local width,height,maps = civ.getAtlasDimensions()
        return {
        [1] = civ.getTile((xVal+1)%width,yVal-1,zVal),
        [2] = civ.getTile((xVal+2)%width,yVal,zVal),
        [3] = civ.getTile((xVal+1)%width,yVal+1,zVal),
        [4] = civ.getTile((xVal)%width,yVal+2,zVal),
        [5] = civ.getTile((xVal-1)%width,yVal+1,zVal),
        [6] = civ.getTile((xVal-2)%width,yVal,zVal),
        [7] = civ.getTile((xVal-1)%width,yVal-1,zVal),
        [8] = civ.getTile((xVal)%width,yVal-2,zVal),
        [9] = civ.getTile((xVal+2)%width,yVal-2,zVal),
        [10] = civ.getTile((xVal+2)%width,yVal+2,zVal),
        [11] = civ.getTile((xVal-2)%width,yVal+2,zVal),
        [12] = civ.getTile((xVal-2)%width,yVal-2,zVal),
        [13] = civ.getTile((xVal+1)%width,yVal-3,zVal),
        [14] = civ.getTile((xVal+3)%width,yVal-1,zVal),
        [15] = civ.getTile((xVal+3)%width,yVal+1,zVal),
        [16] = civ.getTile((xVal+1)%width,yVal+3,zVal),
        [17] = civ.getTile((xVal-1)%width,yVal+3,zVal),
        [18] = civ.getTile((xVal-3)%width,yVal+1,zVal),
        [19] = civ.getTile((xVal-3)%width,yVal-1,zVal),
        [20] = civ.getTile((xVal-1)%width,yVal-3,zVal),
        [21] = civ.getTile((xVal)%width,yVal,zVal),
        }
    end
end


-- If the settlerTile and defenderTile are acceptable tiles
-- for the new arrivals to land on, return true.  If they
-- are not, return false
local function validTilePair(settlerTile,defenderTile)
    -- tiles can't be water, 
    if settlerTile.baseTerrain.type == 10 or
        defenderTile.baseTerrain.type == 10 then
        return false
    end
    -- no alien unit or city can be within a city radius
    -- of either tile
    for _,tile in pairs(cityRadiusTiles(settlerTile)) do
        if tile.defender or tile.city then
            return false
        end
    end
    for _,tile in pairs(cityRadiusTiles(defenderTile)) do
        if tile.defender or tile.city then
            return false
        end
    end
    return true
end

-- chooseLandingTiles(mapID,tribe) -> {{xS,yS,zS}}, {{xM,yM,zM}}
--   xS, yS, zS represent location to place settler unit (wrapped for use with civlua.createUnit)
--   xM, yM, zM represent location to place military unit
local function chooseLandingTiles(mapID,tribe)
    local xMax,yMax,zMax = civ.getAtlasDimensions()
    for i=1,200 do
        local x = math.random(6,xMax-6) -- these are interior enough to avoid map edges
        local y = math.random(6,yMax-6)
        if x%2 ~= y%2 then
            y = y - 1
        end
        local settlerTile = civ.getTile(x,y,mapID)
        local nearbyTiles = cityRadiusTiles(settlerTile)
        local defenderTile = nearbyTiles[math.random(1,20)]
        if defenderTile and validTilePair(settlerTile,defenderTile) then
            return {{settlerTile.x,settlerTile.y,mapID}}, {{defenderTile.x,defenderTile.y,mapID}}
        end
    end
    -- If we get here 200 random tile pairs didn't find a valid landing location
    -- Show a message and return an empty tile list, so nothing is built
    civ.ui.text("The "..tribe.adjective.." colonists failed to locate a suitable landing site.")
    return {},{}
end
 
Did you start the game with an events.txt file that isn't empty? Maybe the macro events got registered or something.
That was my thought, but even running with Events.txt removed it procs twice.

Here's some (untested -- let me know if I made a mistake you can't easily fix) code to achieve this:
Excellent; the basic function works, though it only applies on the Earth layer. I tried to convert it into a dynamic function for specific mapIDs, but so far I haven't figured out how to change the Z-axis.

I happened to take a quick glance at your location tables, and many of them are for invalid coordinates.
I just copied those from the original macro, but knowing MicroProse they may never have validated their own code. :crazyeye: My next step was figuring out random placement that scoped the whole map, so your second post is better anyway—I just need to figure out how to plug the return data into createunit.
 
Excellent; the basic function works, though it only applies on the Earth layer. I tried to convert it into a dynamic function for specific mapIDs, but so far I haven't figured out how to change the Z-axis.
Are you sure? I just tried it, and it changes both maps on turn 5. The loop
Code:
for z = 0,1 do
Makes sure stuff happens for the z coordinate, which is the map coordinate. So, z=0,1 means do something for maps 0 through 1 inclusive. (z=0,3 would mean maps 0,1,2 and 3.)

Just so that I'm not missing something obvious, the game we're playing is Extended Original, correct?
I just need to figure out how to plug the return data into createunit.
Here's an example:
Code:
    else
        local settleSquare, defendSquare = chooseLandingTiles(1,tribe)
        civlua.createUnit(civ.getUnitType(52), tribe, settleSquare, {count=1, randomize=true, veteran=false})
        civlua.createUnit(civ.getUnitType(12), tribe, defendSquare, {count=1, randomize=true, veteran=false})
 
That was my thought, but even running with Events.txt removed it procs twice.
I presume you kept the old events.txt file around. What did you name it?

Here's something that I've tried:
Events.txt -- Completely empty
Events (copy).txt -- Extended original events, with this event added:
Code:
@IF
TURN
turn=every
@THEN
TEXT
The Macro Events Exist
ENDTEXT
@ENDIF
Events.lua -- Yours, with a few changes, such as commenting out the tech 98/99 events.

When I started a new game, even with enabling Lua events right from the start, I got the "The Macro Events Exist" message. It seems Test of Time looks for files with "events" in the name, not just "events.txt". I haven't explored the full details, but this seems like the explanation.
 
Are you sure? I just tried it, and it changes both maps on turn 5.
Aye, though I've done some tests and it seems to be a glitch in how Lua reads the session data: loading from save it works as intended, but playing straight through it consistently skips the second layer, as though it doesn't know there's a second map. Similar problem with the spaceship arrival: Centaurans will land on Earth as normal, but Terran colonists proc a console error saying it can't find the tile(s).

I don't know if civlua.serialize/unserialize can solve for this, or if those functions are exclusive to save/load routines.

Just so that I'm not missing something obvious, the game we're playing is Extended Original, correct?
Well, carbon-copy run as a mod, but otherwise identical.

I presume you kept the old events.txt file around. What did you name it?
_events.txt

Did a clean run with the file moved out and it solved the problem, so it seems the game's too clever for its own good. :p
 
Aye, though I've done some tests and it seems to be a glitch in how Lua reads the session data: loading from save it works as intended, but playing straight through it consistently skips the second layer, as though it doesn't know there's a second map. Similar problem with the spaceship arrival: Centaurans will land on Earth as normal, but Terran colonists proc a console error saying it can't find the tile(s).
Can you please upload the events.lua file that causes these errors? This way, I can try to recreate the problem with the exact file you're using.

Just so we're clear:

If you start a new extended original game, and play until after turn 5, the "Earth" will have its terrain changed, but "Centaurus" will not. Playing (with cheating) to reach Centaurus will cause a console error.

If you start a new extended original game, save the game, load the game, and then play until after turn 5, both maps will have their terrain changed. Playing to reach Centaurus has a properly working event.

I don't know if civlua.serialize/unserialize can solve for this, or if those functions are exclusive to save/load routines.
That's not what those functions do. They convert the "state" table (which keeps track of justOnce) to and from text so that it can be saved at the end of the file and then recovered when the game is loaded again.
 
Just so we're clear:

If you start a new extended original game, and play until after turn 5, the "Earth" will have its terrain changed, but "Centaurus" will not. Playing (with cheating) to reach Centaurus will cause a console error.

If you start a new extended original game, save the game, load the game, and then play until after turn 5, both maps will have their terrain changed. Playing to reach Centaurus has a properly working event.
Yes.

Current file attached; it's tweaked your examples a bit (map changes translated to targetable function) but essentially resolves the same.
 

Attachments

  • CentauranGambit2.zip
    3.6 KB · Views: 5
Well, I can't recreate the problem with the tiles not changing. A minor point is that
Code:
civ.getTile(x,y,z).terrainType == 10
doesn't always register as true for oceans.

That's because the byte referenced is like this:

abcdXXXX

XXXX is 0-15, which give the terrain type. However, the "a" bit determines if there is a river on the tile. "b", "c", and "d" do two things, but I don't remember the order. One of them, I think "b" tells the game to suppress special resources if the sea tile is too far from land. The other two tell if the game is currently animating the resource on the tile.

Code:
civ.getTile(x,y,z).baseTerrain.type == 10
is more correct.

For some reason, the entire civ.scen.onCentauriArrival event is not registering at all. I even added a text box message to be sure. I don't know why it doesn't activate. I'll think about it for a bit and maybe I'll think of something to try.
 
Top Bottom