Lua help building requires forest

Hugh Heggen

Chieftain
Joined
Aug 31, 2014
Messages
7
Location
Australia
Hi
I wish to have a building in my mod require that the city be next to a forest.
I have fiddled with a lua file from a lighthouse mod written by SushiSquid but as I have really no idea what I'm doing I would like some help in finishing the lua.

Code:
local iCharcoal = GameInfoTypes.BUILDING_CHARCOAL_FURNACE
local iForest = GameInfoTypes.FEATURE_FOREST

directions = {DirectionTypes.DIRECTION_NORTHEAST, DirectionTypes.DIRECTION_EAST, DirectionTypes.DIRECTION_SOUTHEAST,
              DirectionTypes.DIRECTION_SOUTHWEST, DirectionTypes.DIRECTION_WEST, DirectionTypes.DIRECTION_NORTHWEST}

GameEvents.CityCanConstruct.Add(function(iPlayer, iCity, iBuilding) 
  if (iBuilding == iCharcoal) then
    local pCityPlot = Players[iPlayer]:GetCityByID(iCity):Plot()

    end

	  return false
  end

  return true
end)

function local iOwner = pLandPlot:GetOwner()

    for loop, direction in ipairs(directions) do
      local pPlot = Map.PlotDirection(pLandPlot:GetX(), pLandPlot:GetY(), direction)

      -- Is the adjacent plot owned by the same player as the land plot
      if (pPlot ~= nil and iOwner == pPlot:GetOwner()) then
	  -- Is the adjacent plot a forest
        if(pPlot:GetFeatureType() == iForest)) then
          return true
        end
      end
    end
  end

  return false
end

And lastly to make the lua active do I need to import it into VFS or add it in the actions or contents tabs?
 
InGameUIAddIn (I believe under actions) is most likely the way you'd want to go about implementing this. It's the most commonly used way to get simple code into the game. Since you aren't replacing existing Lua/XML or adding new assets to the game VFS should be false.

On to your code, you've made some novice mistakes here and there but the primary logic chunk looks like it should work if you just move some stuff around:
Spoiler :
Code:
local iCharcoal = GameInfoTypes.BUILDING_CHARCOAL_FURNACE
local iForest = GameInfoTypes.FEATURE_FOREST [COLOR="SeaGreen"]-- check this to make sure GameInfoTypes.FEATURE_FOREST is correct[/COLOR]

directions = {DirectionTypes.DIRECTION_NORTHEAST, DirectionTypes.DIRECTION_EAST, DirectionTypes.DIRECTION_SOUTHEAST,
              DirectionTypes.DIRECTION_SOUTHWEST, DirectionTypes.DIRECTION_WEST, DirectionTypes.DIRECTION_NORTHWEST}

GameEvents.CityCanConstruct.Add(function(iPlayer, iCity, iBuilding) 
  if (iBuilding == iCharcoal) then
    local pCityPlot = Players[iPlayer]:GetCityByID(iCity):Plot()

    end

	  return false
  end

  return true
end)

function local iOwner = pLandPlot:GetOwner() [COLOR="SeaGreen"]-- This is where things start getting hairy. "function" is an object and should be followed by a designation and parameters. "local" is also an object and you've designated the local variable it initiates as "iOwner". This local variable needs to be on it's own line. Additionally, you've assigned iOwner to the value returned by "pLandPlot:GetOwner()" which would work EXCEPT you haven't defined "pLandPlot".[/COLOR]

    for loop, direction in ipairs(directions) do [COLOR="SeaGreen"]-- Here you're using the "Lua General For Statement" with the ipairs function but the first variable "loop" is going unused. Even though Firaxis and even other modders do this quite often, It's best practice to rewrite this to only initialize one variable. I believe "for direction in directions do" would work as directions is a table. Alternatively, you could use the numeric for statement (that you'll find is present in most other coding languages where I believe Lua's General for statement is unique). that would look like so: 
"for k=1, #directions do
local direction = directions[k]"[/COLOR]
      local pPlot = Map.PlotDirection(pLandPlot:GetX(), pLandPlot:GetY(), direction)

      -- Is the adjacent plot owned by the same player as the land plot
      if (pPlot ~= nil and iOwner == pPlot:GetOwner()) then
	  -- Is the adjacent plot a forest
        if(pPlot:GetFeatureType() == iForest)) then
          return true
        end
      end
    end
  end [COLOR="SeaGreen"]-- this end closes out the function you started above and makes all code past it both null and a potential cause for errors.[/COLOR]

  return false
end

Additionally, you're never calling your unnamed function so it's code would currently never run. However, I would personally incorporate your code directly into the first function that's already attached to the CityCanConstruct event, instead of making a second function
.
Some general tips I can give you are A) get notepad++ or something similar if you don't already have it. It's extremely useful and has a slew of powerful tools that make coding easier. B) use a search function that can crossreference the contents of multiple files (such as the one available in notepad++). C) Enable logging and use lua.log to debug your code (alternatively firaxis's FireTuner lua console can be used in real time to debug Lua but it takes some getting used to).

Here's a rewritten version of your code though i haven't attempted testing it or anything yet so there may still be typos or invalid references (possibly forests as mentioned above), etc..
Spoiler :
Code:
GameEvents.CityCanConstruct.Add(function(iPlayer, iCity, iBuilding)
	local iCharcoal = GameInfoTypes.BUILDING_CHARCOAL_FURNACE
	if (iBuilding == iCharcoal) then
		local pCityPlot = Players[iPlayer]:GetCityByID(iCity):Plot()
		local iOwner = pCityPlot:GetOwner()
		local directions = {DirectionTypes.DIRECTION_NORTHEAST, DirectionTypes.DIRECTION_EAST, DirectionTypes.DIRECTION_SOUTHEAST, DirectionTypes.DIRECTION_SOUTHWEST, DirectionTypes.DIRECTION_WEST, DirectionTypes.DIRECTION_NORTHWEST}
		
		for direction in directions do
			local pPlot = Map.PlotDirection(pCityPlot:GetX(), pCityPlot:GetY(), direction)

				-- Is the adjacent plot owned by the same player as the land plot
			if (pPlot ~= nil and iOwner == pPlot:GetOwner()) then
				local iForest = GameInfoTypes.FEATURE_FOREST
				-- Is the adjacent plot a forest
				if(pPlot:GetFeatureType() == iForest) then
					return true
				end
			end
		end
		return false
	end
end)
 
Thanks for your reply and your nicer looking code.
According to modiki FeatureTypes should be used to retrieve the ID but I tried both and the result was the same, all other buildings were blocked except the charcoal furnace.
The script is doing something though so InGameUIAddIn is the way to go.
Why are all other buildings not available? Is "if (iBuilding == iCharcoal)" excluding all others or is it just a product of invalid references or somthing?
 
I think you should add
Code:
return true
before the last end in Bobert's code.
 
I think you should add
Code:
return true
before the last end in Bobert's code.
Yeah, I think otherwise the function is going to default to returning a 'nil' which I think will essentially be the same as return false for every building that is not a charcoal factory.

Though I tend to organize things a little differently in that I do this as the first or second line:
Code:
local result = true
Then I do all of my code checking for conditions, with updating local variable result as required.
Code:
	return result
is the last line of code in my functions right before the final end statement.

Like this for where I was limiting the construction of a building to 1 for every city state ally (but as structured I only have to reset the value of result for those cases where the building should be disabled). I also think this function as shown could be streamlined a little, but as it works :dunno: :
Spoiler :
Code:
function LeesBuildingConstructionOverride(iPlayer, iCity, iBuilding)
	local pPlayer = Players[iPlayer]
	[COLOR="blue"]local result = true[/COLOR]
	local pCity = pPlayer:GetCityByID(iCity)
	if iBuilding == gBuildingForCode then
		[COLOR="Red"]if pPlayer:GetCivilizationType() ~= GameInfoTypes[gRequiredCivName] then[/COLOR]
			[COLOR="blue"]result = false[/COLOR]
		[COLOR="red"]end[/COLOR]
		if pPlayer:GetCivilizationType() == GameInfoTypes[gRequiredCivName] then
			local sCityName = pCity:GetName()
			gNumberOfBuildings = 0
			for pCity in pPlayer:Cities() do
				if pCity:IsHasBuilding(iBuilding) then
					gNumberOfBuildings = gNumberOfBuildings + 1
				else if pCity:GetProductionBuilding() == iBuilding then
					gNumberOfBuildings = gNumberOfBuildings + 1
					end
				end
			end
			print("Number of Buildings made or making is " .. tostring(gNumberOfBuildings))
			if gNumberCSAllies <= gNumberOfBuildings then
				print("LeesBuildingConstructionOverride: disabling Queen's Estate construction in the city of " .. sCityName)
				[COLOR="blue"]result = false[/COLOR]
			else
				print("LeesBuildingConstructionOverride: enabling Queen's Estate construction in the city of " .. sCityName)
			end
		end
	end
	[COLOR="Blue"]return result[/COLOR]
end

GameEvents.CityCanConstruct.Add(LeesBuildingConstructionOverride)
Where I have the red lines I wanted do disallow any but a specific civilization from creating the building. But I think it still gives a basic conceptual structure of a slightly different way to handle the "default" value for the function.
 
Thanks lads for helping me out.
I had to fiddle with the code some more to get it to check for forests properly, in the end I replaced "for direction in directions do" with the original "for loop, direction in ipairs(directions) do" which worked for some reason.
Here is the functioning code for anyone who may be interested.
Code:
GameEvents.CityCanConstruct.Add(function(iPlayer, iCity, iBuilding)
        --add desired building name
	local iCharcoal = GameInfoTypes.BUILDING_CHARCOAL_FURNACE
	if (iBuilding == iCharcoal) then
		local pCityPlot = Players[iPlayer]:GetCityByID(iCity):Plot()
		local iOwner = pCityPlot:GetOwner()
		local directions = {DirectionTypes.DIRECTION_NORTHEAST, DirectionTypes.DIRECTION_EAST, DirectionTypes.DIRECTION_SOUTHEAST, DirectionTypes.DIRECTION_SOUTHWEST, DirectionTypes.DIRECTION_WEST, DirectionTypes.DIRECTION_NORTHWEST}
		
		 for loop, direction in ipairs(directions) do
			local pPlot = Map.PlotDirection(pCityPlot:GetX(), pCityPlot:GetY(), direction)

				-- Is the adjacent plot owned by the same player as the land plot
			if (pPlot ~= nil and iOwner == pPlot:GetOwner()) then
                               --add desired feature name
				local iForest = GameInfoTypes.FEATURE_FOREST
				-- Is the adjacent plot a forest
				if(pPlot:GetFeatureType() == iForest) then
					return true
				end
			end
		end
		return false
	end
	return true
end)
 
Thanks much for this post! I was looking for code to sort out how to do something very similar: prevent Solar Plants from being built in cities on or next to Snow or Tundra instead of requiring adjacent Desert.

It worked out great, although I think the game calls the CityCanConstruct method multiple times when a city would gain the ability to construct a building. It's calling my function 8-12 times and I have a safety built into it preventing it from running more than once when called. ::shrugs:: I would have preferred my function to run as few times as possible to not potentially slow the game down (not that it does of course).
 
Top Bottom