예제 #1
0
bool CoreScript::RecruitNeutral()
{
	Vector2I sector = ToSector(parentChit->Position());
	Rectangle2I inner = InnerSectorBounds(sector);

	MOBKeyFilter filter;
	filter.value = ISC::denizen;
	CChitArray arr;
	Context()->chitBag->QuerySpatialHash(&arr, ToWorld2F(inner), 0, &filter);

	for (int i = 0; i < arr.Size(); ++i) {
		Chit* chit = arr[i];
		if (Team::Instance()->GetRelationship(chit, parentChit) != ERelate::ENEMY) {
			if (this->IsCitizen(chit)) continue;
			if (!chit->GetItem()) continue;

			if (Team::IsRogue(chit->Team())) {
				// ronin! denizen without a core.
				this->AddCitizen( chit );
				GLASSERT(chit->GetItem()->Significant());

				NewsEvent news(NewsEvent::ROGUE_DENIZEN_JOINS_TEAM, ToWorld2F(chit->Position()), chit->GetItemID(), 0);
				Context()->chitBag->GetNewsHistory()->Add(news);
				return true;
			}
		}
	}
	return false;
}
예제 #2
0
int CoreScript::MaxTech()
{
	Vector2I sector = ToSector(parentChit->Position());
	CChitArray chitArr;
	Context()->chitBag->FindBuildingCC(ISC::temple, sector, 0, LumosChitBag::EFindMode::NEAREST, &chitArr, 0);
	return Min(chitArr.Size() + 1, TECH_MAX);	// get one power for core
}
예제 #3
0
int CoreScript::NumTemples()
{
	CChitArray chitArr;
	Context()->chitBag->FindBuildingCC( ISC::temple, sector, 0, LumosChitBag::EFindMode::NEAREST, &chitArr, 0 );
	int nTemples = chitArr.Size();
	return nTemples;
}
예제 #4
0
Chit* LumosChitBag::QueryRemovable( const grinliz::Vector2I& pos2i )
{
	Vector2F pos2 = { (float)pos2i.x+0.5f, (float)pos2i.y+0.5f };

	CChitArray		array;
	RemovableFilter removableFilter;

	Chit* core = 0;
	CoreScript* cs = CoreScript::GetCore(ToSector(pos2i));
	if (cs) core = cs->ParentChit();


	// Big enough to snag buildings:
	QuerySpatialHash(&array, pos2, 0.8f, core, &removableFilter);

	Chit* found = 0;
	// 1x1 buildings.
	for( int i=0; i<array.Size(); ++i ) {
		// We are casting a wide net to get buildings.
		if (array[i]->Bounds().Contains(pos2i)) {
			found = array[i];
			break;
		}
	}
	return found;
}
예제 #5
0
void CoreScript::SetWaypoints(int squadID, const grinliz::Vector2I& dest)
{
	GLASSERT(squadID >= 0 && squadID < MAX_SQUADS);
	waypoints[squadID].Clear();
	if (dest.IsZero()) {
		return;
	}

	// Use the first chit to choose the starting location:
	bool startInSameSector = true;
	Vector2I startSector = { 0, 0 };
	CChitArray chitArr;
	Squaddies(squadID, &chitArr);
	if (chitArr.Empty()) return;

	for (int i = 0; i<chitArr.Size(); ++i) {
		Chit* chit = chitArr[i];
		if (startSector.IsZero())
			startSector = ToSector(chit->Position());
		else
			if (startSector != ToSector(chit->Position()))
				startInSameSector = false;
	}

	Vector2I destSector = ToSector(dest);

	// - Current port
	// - grid travel (implies both sector and target port)
	// - dest port (regroup)
	// - destination

	GLOUTPUT(("SetWaypoints: #chits=%d squadID=%d:", chitArr.Size(), squadID));

	if (startSector != destSector) {
		Chit* chit = chitArr[0];
		SectorPort sectorPort;

		if (startInSameSector) {
			// All together, so meet at the STARTING port.
			sectorPort = Context()->worldMap->NearestPort(ToWorld2F(chit->Position()), &ToWorld2F(dest));
		}
		else {
			// Dispersed; meet at the DESTINATION port.
			sectorPort = Context()->worldMap->NearestPort(ToWorld2F(dest), &ToWorld2F(chit->Position()));
		}
		if (!sectorPort.IsValid()) {
			GLOUTPUT(("SetWaypoints: no path.\n"));
			return;
		}
		SectorData sectorData = Context()->worldMap->GetSectorData(sectorPort.sector);
		Vector2I p0 = sectorData.GetPortLoc(sectorPort.port).Center();
		waypoints[squadID].Push(p0);
		GLOUTPUT(("%d,%d [s%c%d]  ", p0.x, p0.y, 'A' + p0.x / SECTOR_SIZE, 1 + p0.y / SECTOR_SIZE));
	}
	GLOUTPUT(("%d,%d [s%c%d]\n", dest.x, dest.y, 'A' + dest.x/SECTOR_SIZE, 1 + dest.y/SECTOR_SIZE));
	waypoints[squadID].Push(dest);
	NewWaypointChits(squadID);
}
예제 #6
0
int CoreScript::MaxCitizens()
{
	CChitArray chitArr;
	Context()->chitBag->FindBuildingCC( ISC::bed, sector, 0, LumosChitBag::EFindMode::NEAREST, &chitArr, 0 );
	int nBeds = chitArr.Size();
	int nTemples = NumTemples();
	GLASSERT(nTemples >= 0);

	int n = CoreScript::MaxCitizens(nTemples);
	return Min(n, nBeds);
}
예제 #7
0
int RebuildAIComponent::DoTick(U32 delta)
{
	GameItem* mainItem = parentChit->GetItem();
	Vector2I sector = ToSector(parentChit->Position());
	CoreScript* cs = CoreScript::GetCore(sector);

	if (ticker.Delta(delta) && mainItem && cs) {

		// Create workers, if needed.
		Rectangle2F b = ToWorld2F(InnerSectorBounds(sector));
		CChitArray arr;
		ItemNameFilter workerFilter(ISC::worker);
		Context()->chitBag->QuerySpatialHash(&arr, b, 0, &workerFilter);
		if (arr.Empty()) {
			if (mainItem->wallet.Gold() >= WORKER_BOT_COST) {
				ReserveBank::GetWallet()->Deposit(&mainItem->wallet, WORKER_BOT_COST);
				Context()->chitBag->NewWorkerChit(cs->ParentChit()->Position(), parentChit->Team());
			}
		}

		// Pull a task off the queue and send a worker out.
		if (workItems.Size()) {
			WorkQueue* workQueue = cs->GetWorkQueue();
			if (workQueue) {
				WorkItem wi = workItems.Pop();
				BuildScript buildScript;
				int id = 0;
				buildScript.GetDataFromStructure(wi.structure, &id);
				if (workQueue->AddAction(wi.pos, id, float(wi.rot), 0)) {
					GLOUTPUT(("ReBuild: Structure %s at %d,%d r=%d POP to work queue.\n", wi.structure.safe_str(), wi.pos.x, wi.pos.y, int(wi.rot)));
				}
				else {
					GLOUTPUT(("ReBuild: Re-Push structure %s at %d,%d to work queue.\n", wi.structure.safe_str(), wi.pos.x, wi.pos.y));
					workItems.Push(wi);
				}
			}
		}
	}
	return ticker.Next();
}
예제 #8
0
void CoreScript::AssignToSquads()
{
	// First, how many do we actually have?
	// Filter out everyone that has gone away.
	for (int i = 0; i < MAX_SQUADS; ++i) {
		//GL_ARRAY_FILTER(squads[i], (this->IsCitizen(ele)));
		squads[i].Filter(this, [](CoreScript* cs, int id) {
			return cs->IsCitizen(id);
		});
		if (squads[i].Empty()) {
			// Flush out dead squads so they don't have 
			// control flags laying around.
			waypoints[i].Clear();
		}
	}
	int nSquaddies = 0;
	for (int i = 0; i < MAX_SQUADS; ++i) {
		nSquaddies += squads[i].Size();
	}

	CChitArray recruits;
	int nCitizens = Citizens(&recruits);
	int nExpected = nCitizens - CITIZEN_BASE;
	if (nSquaddies >= nExpected) return;

	struct FilterData {
		CoreScript* cs;
		Chit* avatar;;
	};
	FilterData fd = { this, Context()->chitBag->GetAvatar() };

	// Filter to: not in squad AND not player controlled
	recruits.Filter(fd, [](const FilterData& ffd, Chit* chit) {
		return (ffd.cs->SquadID(chit->ID()) < 0 && chit != ffd.avatar);
	});

	// Sort the best recruits to the end.
	// FIXME: better if there was a (working) power approximation
	recruits.Sort([](Chit* a, Chit* b) {
		const GameItem* itemA = a->GetItem();
		const GameItem* itemB = b->GetItem();
		int scoreA = itemA->Traits().Level() * (itemA->GetPersonality().Fighting() == Personality::LIKES ? 2 : 1);
		int scoreB = itemB->Traits().Level() * (itemB->GetPersonality().Fighting() == Personality::LIKES ? 2 : 1);
		
		// Reverse order. Best at end.
		return scoreA > scoreB;
	});

	for (int n = nExpected - nSquaddies; n > 0; --n) {
		for (int i = 0; i < MAX_SQUADS; ++i) {
			if (   (squads[i].Size() < SQUAD_SIZE)
				&& SquadAtRest(i))
			{
				Chit* chit = recruits.Pop();
				squads[i].Push(chit->ID());
				++nSquaddies;
				break;
			}
		}
	}
}
예제 #9
0
void CoreScript::DoStrategicTick()
{
	// Look around for someone to attack. They should be:
	//	- someone we have a negative attitude about (accounts for lots of things)
	//	- weaker overall
	//
	// We should:
	//	- have a reasonably armed squad

	bool squadReady[MAX_SQUADS] = { false };
	CChitArray squaddies;
	
	// Check for ready squads.
	for (int i = 0; i < MAX_SQUADS; ++i) {
		this->Squaddies(i, &squaddies);
		if (squaddies.Size() < SQUAD_SIZE) 
			continue;
		if (!SquadAtRest(i))
			continue;

		// Do we have enough guns? Seems to be
		// the best / easiest metric.

		int nGuns = 0;
		for (int k = 0; k < squaddies.Size(); ++k) {
			Chit* chit = squaddies[k];
			ItemComponent* ic = chit->GetItemComponent();
			if (   chit->GetItem() 
				&& chit->GetItem()->HPFraction() > 0.75f 
				&& ic 
				&& ic->QuerySelectRanged()) 
			{
				nGuns++;
			}
		}
		squadReady[i] = nGuns >= SQUAD_SIZE - 1;
	}

	int nReady = 0;
	for (int i = 0; i < MAX_SQUADS; ++i) {
		if (squadReady[i])
			++nReady;
	}

	Sim* sim = Context()->chitBag->GetSim();
	GLASSERT(sim);
	if (!sim) return;

	Vector2I sector = ToSector(ParentChit()->Position());
	CCoreArray stateArr;
	sim->CalcStrategicRelationships(sector, 3, &stateArr);

	// The strategic relationships need to be calculated, but after that,
	// there's no point in computing further if we don't have a squad to 
	// send into action.
	if (nReady == 0) 
		return;

	int myPower = this->CorePower();

	CoreScript* target = 0;
	for (int i = 0; i < stateArr.Size(); ++i) {
		CoreScript* cs = stateArr[i];
		if (cs->NumTemples() == 0)		// Ignore starting out domains so it isn't a complete wasteland out there.
			continue;

		if (   Team::Instance()->GetRelationship(cs->ParentChit(), this->ParentChit()) == ERelate::ENEMY		// Are we enemies at the diplomatic level
			&& Team::Instance()->Attitude(this, cs) < 0)														// Does 'this' have a negative attitude to the other?
		{
			int power = cs->CorePower();
			if (power < myPower * 0.75f) {
				// Assuming this is actually so rare that it doesn't matter to select the best.
				target = cs;
				break;
			}
		}
	}

	if (!target) return;

	// Attack!!!
	bool first = true;
	Vector2F targetCorePos2 = ToWorld2F(target->ParentChit()->Position());
	Vector2I targetCorePos = ToWorld2I(target->ParentChit()->Position());;
	Vector2I targetSector = ToSector(targetCorePos);

	for (int i = 0; i < MAX_SQUADS; ++i) {
		if (!squadReady[i]) continue;

		Vector2I pos = { 0, 0 };
		pos = targetCorePos;
		if (first) {
			first = false;
		}
		else {
			BuildingWithPorchFilter filter;
			Chit* building = Context()->chitBag->FindBuilding(IString(), targetSector, &targetCorePos2, LumosChitBag::EFindMode::RANDOM_NEAR, 0, &filter);
			if (building) {
				pos = ToWorld2I(building->Position());
			}
		}
		GLASSERT(!pos.IsZero());
		this->SetWaypoints(i, pos);
	}
}
예제 #10
0
void MapScene::DrawMap()
{
	CDynArray<Chit*> query;

	const ChitContext* context = lumosChitBag->Context();
	const Web& web = lumosChitBag->GetSim()->CalcWeb();

	Rectangle2I subBounds = MapBounds2();
	float map2X = float(subBounds.min.x) / float(NUM_SECTORS);
	float map2Y = float(subBounds.min.y) / float(NUM_SECTORS);
	RenderAtom subAtom = mapImage.GetRenderAtom();
	subAtom.tx0 = map2X;
	subAtom.ty1 = map2Y;
	subAtom.tx1 = map2X + float(MAP2_SIZE) / float(NUM_SECTORS);
	subAtom.ty0 = map2Y + float(MAP2_SIZE) / float(NUM_SECTORS);
	mapImage2.SetAtom(subAtom);

	for (Rectangle2IIterator it(subBounds); !it.Done(); it.Next()) {
		Vector2I sector = it.Pos();
		const SectorData& sd = worldMap->GetSectorData( sector );

		int i = (sector.x - subBounds.min.x);
		int j = (sector.y - subBounds.min.y);
		int index = j * MAP2_SIZE + i;

		CoreScript* coreScript = CoreScript::GetCore(sector);

		CStr<64> str = "";
		const char* owner = "";
		if (coreScript) {
			if ( coreScript->InUse() ) {
				owner = Team::Instance()->TeamName( coreScript->ParentChit()->Team() ).safe_str();
			}
		}
		str.Format( "%s\n%s", sd.name.safe_str(), owner );

		Rectangle2F r = GridBounds2(i, j, true);
		gridWidget[index].SetPos(r.min.x, r.min.y);
		gridWidget[index].SetSize(r.Width(), r.Height());
		gridWidget[index].Set(context, coreScript, lumosChitBag->GetHomeCore(), &web);
	}

	Vector2I homeSector = lumosChitBag->GetHomeSector();
	if ( !data->destSector.IsZero() && data->destSector != homeSector ) {
		const SectorData& sd = worldMap->GetSectorData( data->destSector );
		CStr<64> str;
		str.Format( "Grid Travel\n%s", sd.name.c_str() );
		gridTravel.SetText(  str.c_str() );
		gridTravel.SetEnabled( true );
	}
	else {
		gridTravel.SetText( "Grid Travel" );
		gridTravel.SetEnabled( false );
	}

	// --- MAIN ---
	Rectangle2I mapBounds = data->worldMap->Bounds();
	Rectangle2I map2Bounds;
	map2Bounds.Set(subBounds.min.x*SECTOR_SIZE, subBounds.min.y*SECTOR_SIZE, 
				   subBounds.max.x*SECTOR_SIZE + SECTOR_SIZE-1, subBounds.max.y*SECTOR_SIZE + SECTOR_SIZE-1);

	Vector2F playerPos = { 0, 0 };
	Chit* player = data->player;
	if ( player ) {
		playerPos = ToWorld2F(player->Position());
	}

	const float dx = mapImage.Width() / float(NUM_SECTORS);
	const float dy = mapImage.Height() / float(NUM_SECTORS);
	for (int j = 0; j < NUM_SECTORS; ++j) {
		for (int i = 0; i < NUM_SECTORS; ++i) {
			diplomacy[j*NUM_SECTORS + i].SetSize(dx, dy);
			diplomacy[j*NUM_SECTORS + i].SetPos(mapImage.X() + dx * float(i), mapImage.Y() + dy * float(j));
		}
	}

	bool inBounds = true;
	Vector2F v;

	for (int i = 0; i < 2; ++i) {
		const Rectangle2I& b = (i == 0) ? mapBounds : map2Bounds;

		v = ToUI(i, playerPos, b, &inBounds);
		playerMark[i].SetCenterPos(v.x, v.y);
		playerMark[i].SetVisible(inBounds);

		Vector2F pos = { float(homeSector.x * SECTOR_SIZE), float(homeSector.y * SECTOR_SIZE) };
		v = ToUI(i,pos, b, &inBounds);
		homeMark[i].SetPos(v.x, v.y);
		homeMark[i].SetVisible(inBounds && !homeSector.IsZero());

		pos.Set(float(data->destSector.x * SECTOR_SIZE), float(data->destSector.y * SECTOR_SIZE));
		v = ToUI(i,pos, b, &inBounds);
//		if (i == 0) {
//			travelMark.SetPos(v.x, v.y);
//			travelMark.SetVisible(inBounds && !data->destSector.IsZero());
//		}
		for (int k = 0; k < MAX_SQUADS; ++k) {
			v = ToUI(i, ToWorld2F(data->squadDest[k]), b, &inBounds);
			squadMark[i][k].SetCenterPos(v.x, v.y);
			squadMark[i][k].SetVisible(!data->squadDest[k].IsZero() && inBounds);
		}
	}
	{
		Vector2F world = { (float)map2Bounds.min.x, (float)map2Bounds.min.y };
		Vector2F pos = ToUI(0, world, mapBounds, 0);
		selectionMark.SetPos(pos.x, pos.y);
	}

	float scale = float(mapImage.Width()) / float(NUM_SECTORS);
	{
		webCanvas.Clear();

		for (int i = 1; i < web.NumNodes(); i++) {
			const MinSpanTree::Node& node = web.NodeAt(i);
			Vector2I s0 = node.parentPos;
			Vector2I s1 = node.pos;
			Vector2F p0 = { (float(s0.x) + 0.5f) * scale, (float(s0.y) + 0.5f) * scale };
			Vector2F p1 = { (float(s1.x) + 0.5f) * scale, (float(s1.y) + 0.5f) * scale };
			webCanvas.DrawLine(p0.x, p0.y, p1.x, p1.y, 1.0f + node.strength * 2.0f);
		}
	}

	CoreScript* homeCore = context->chitBag->GetHomeCore();
	CChitArray citizens;
	if (homeCore) {
		homeCore->Citizens(&citizens);
	}
	for (int i = 0; i < MAX_CITIZENS; ++i) {
		if (i < citizens.Size()) {
			Vector2F cPos = ToWorld2F(citizens[i]->Position());
			Vector2F pos = ToUI(0, cPos, mapBounds, 0);
			unitMarker[i].SetSize(8, 8);
			unitMarker[i].SetCenterPos(pos.x, pos.y);
			unitMarker[i].SetVisible(true);
		}
		else {
			unitMarker[i].SetVisible(false);
		}
	}

	for (int j = 0; j < NUM_SECTORS; ++j) {
		for (int i = 0; i < NUM_SECTORS; ++i) {
			diplomacy[i].SetAtom(RenderAtom());

			Vector2I sector = { i, j };
			CoreScript* core = CoreScript::GetCore(sector);
			RenderAtom atom;

			if (core && homeCore && homeCore->InUse() && core->InUse()) {
				ERelate relate = Team::Instance()->GetRelationship(core->ParentChit(), homeCore->ParentChit());
				if (relate == ERelate::FRIEND) atom = LumosGame::CalcUIIconAtom("friend");
				else if (relate == ERelate::NEUTRAL) atom = LumosGame::CalcUIIconAtom("neutral");
				else if (relate == ERelate::ENEMY) atom = LumosGame::CalcUIIconAtom("enemy");
				
				diplomacy[j*NUM_SECTORS + i].SetSize(scale*0.8f, scale*0.8f);
			}

			if (core && core->InUse() && Team::IsDeity(core->ParentChit()->Team())) {
				atom = LumosGame::CalcDeityAtom(core->ParentChit()->Team());
				diplomacy[j*NUM_SECTORS + i].SetSize(scale, scale);
			}

			diplomacy[j*NUM_SECTORS + i].SetAtom(atom);
			diplomacy[j*NUM_SECTORS + i].SetCenterPos(mapImage.X() + scale * (float(i) + 0.5f), mapImage.Y() + scale * (float(j) + 0.5f));
		}
	}
}
예제 #11
0
double EvalBuildingScript::EvalIndustrial( bool debugLog )
{
	Chit* building = ParentChit();

	int hitB = 0, hitIBuilding = 0, hitNBuilding = 0, hitWater = 0, hitPlant = 0, hitRock = 0, hitWaterfall = 0;
	int hitShrub = 0;	// doesn't terminate a ray.


	if (lastEval == 0 || (Context()->chitBag->AbsTime() - lastEval) > 2000) {
		lastEval = Context()->chitBag->AbsTime();

		GameItem* item = building->GetItem();
		GLASSERT(item);
		reachable = true;

		IString consume = item->keyValues.GetIString(ISC::zone);
		if (consume.empty()) {
			eval = 0;
			return eval;
		}
		MapSpatialComponent* msc = GET_SUB_COMPONENT(building, SpatialComponent, MapSpatialComponent);
		GLASSERT(msc);
		if (!msc) {
			eval = 0;
			return eval;
		}
		Rectangle2I porch = msc->PorchPos();

		static const int RAD = 4;

		Rectangle2I bounds = porch;
		if (porch.min.x == 0) {	// shouldn't happen, but be sure.
			GLASSERT(0);
			eval = 0;
			return eval;	
		}

		bounds.Outset(RAD);
		Vector2I sector = ToSector(building->Position());

		WorldMap* worldMap = Context()->worldMap;
		Rectangle2I mapBounds = worldMap->Bounds();
		if (!mapBounds.Contains(bounds)) {
			eval = 0;
			return eval;	// not worth dealing with edge of world
		}

		// Check if we can go from the core to the porch.
		// And make sure the core is inUse!
		CoreScript* cs = CoreScript::GetCore(ToSector(porch.min));
		if (!cs || !cs->InUse())
			reachable = false;
		if (reachable) {
			const SectorData& sd = worldMap->GetSectorData(ToSector(porch.min));
			reachable = worldMap->CalcPath(ToWorld2F(sd.core), ToWorld2F(porch.min), 0, 0, false);
		}

		CChitArray arr;
		BuildingFilter buildingFilter;

		const FluidSim* fluidSim = Context()->physicsSims->GetFluidSim(sector);
		bool hasWaterfalls = fluidSim->NumWaterfalls() > 0;

		LumosChitBag* chitBag = Context()->chitBag;
		Rectangle2IEdgeIterator it(bounds);

		while (!it.Done()) {
			Vector2I pos = { it.Pos().x >= porch.max.x ? porch.max.x : porch.min.x,
							 it.Pos().y >= porch.max.y ? porch.max.y : porch.min.y };

			LineWalk walk(pos.x, pos.y, it.Pos().x, it.Pos().y);
			walk.Step();	// ignore where we are standing.

			while ( !walk.Done() ) {	// non-intuitive iterator. See linewalk docs.
				// - building
				// - plant
				// - ice
				// - rock
				// - waterfall
				// - water

				// Buildings. Can be 2x2. Extend out beyond current check.
				bool hitBuilding = false;
				Vector2I p = walk.P();
				// Don't count self as a hit, but stops the ray cast.
				// Also, use a larger radius because buildings can be 2x2
				chitBag->QuerySpatialHash(&arr, ToWorld2F(p), 0.8f, 0, &buildingFilter);
				for (int i = 0; i < arr.Size(); ++i) {
					if (arr[i] != building) {
						MapSpatialComponent* buildingMSC = GET_SUB_COMPONENT(arr[i], SpatialComponent, MapSpatialComponent);
						GLASSERT(buildingMSC);
						if (buildingMSC->Bounds().Contains(p)) {
							hitBuilding = true;
							double thisSys = arr[i]->GetItem()->GetBuildingIndustrial();

							hitB++;
							if (thisSys <= -0.5) hitNBuilding++;
							if (thisSys >= 0.5)  hitIBuilding++;

							break;
						}
					}
				}
				if (hitBuilding) break;
				
				const WorldGrid& wg = worldMap->GetWorldGrid(p.x, p.y);
				if (wg.Plant()) {
//					int type = wg.Plant() - 1;
					int stage = wg.PlantStage();

					if (stage >= 2) {
						++hitPlant;
						break;
					}
					else {
						hitShrub++;
					}
				}

				if (wg.RockHeight()) {
					++hitRock;
					break;
				}
				if (wg.IsWater()) {
					++hitWater;
					break;
				}

				Rectangle2I wb;
				wb.min = wb.max = p;
				if (hasWaterfalls && fluidSim->ContainsWaterfalls(wb)) {
					++hitWaterfall;
					break;
				}
				walk.Step();
			}
			it.Next();
		}

		// Note rock/ice isn't counted either way.
		int natural = hitNBuilding
			+ hitWater
			+ hitPlant
			+ 10 * hitWaterfall
			+ hitShrub / 4;				// small plants don't add to rRays, so divide is okay.
		int industrial = hitIBuilding;
		int nRays = hitNBuilding + hitWater + hitPlant + hitWaterfall + hitIBuilding;

		eval = 0;
		if (nRays) {
			// With this system, that one ray (say from a distillery to plant) can be
			// hugely impactful. This may need tweaking:
			if (nRays < 2)
				nRays = 2;
			eval = double(industrial - natural) / double(nRays);
		}
		eval = Clamp(eval, -1.0, 1.0);

		if (debugLog) {
			Vector2I pos = ToWorld2I(building->Position());
			GLOUTPUT(("Building %s at %d,%d eval=%.2f nRays=%d \n  hit: Build=%d (I=%d N=%d) water=%d plant=%d rock=%d\n",
				building->GetItem()->Name(),
				pos.x, pos.y,
				eval,
				nRays,
				hitB, hitIBuilding, hitNBuilding, hitWater, hitPlant, hitRock));
			(void)pos;
		}
	}
	return eval;
}