Пример #1
0
Chit* LumosChitBag::FindBuilding(	const grinliz::IString&  name, 
									const grinliz::Vector2I& sector, 
									const grinliz::Vector2F* pos, 
									LumosChitBag::EFindMode flags,
									CDynArray<Chit*>* arr,
									IChitAccept* filter )
{
	CDynArray<Chit*>& match = arr ? *arr : findMatch;	// sleazy reference trick to point to either passed in or local.
	match.Clear();
	findWeight.Clear();

	for( MapSpatialComponent* it = mapSpatialHash[sector.y*NUM_SECTORS+sector.x]; it; it = it->nextBuilding ) {
		Chit* chit = it->ParentChit();
		GLASSERT( chit );
		if ( filter && !filter->Accept( chit )) {				// if a filter, check it.
			continue;
		}

		const GameItem* item = chit->GetItem();

		if ( item && ( name.empty() || item->IName() == name )) {	// name, if empty, matches everything
			match.Push( chit );
		}
	}

	// If we found nothing, or we don't care about the position, return early.
	// Else deal with choice / sorting / etc. below.
	if ( match.Empty() )
		return 0;
	if ( !pos )
		return match[0];

	// NEAREST scans and finds the closest one.
	// RANDOM_NEAR chooses one at random, but weighted by the (inverse) of the distance
	if ( flags == EFindMode::NEAREST ) {
		float closest = ( ToWorld2F(match[0]->Position()) - *pos ).LengthSquared();
		int   ci = 0;
		for( int i=1; i<match.Size(); ++i ) {
			float len2 = ( ToWorld2F(match[i]->Position()) - *pos ).LengthSquared();
			if ( len2 < closest ) {
				closest = len2;
				ci = i;
			}
		}
		return match[ci];
	}
	if ( flags == EFindMode::RANDOM_NEAR ) {
		for( int i=0; i<match.Size(); ++i ) {
			float len = ( ToWorld2F(match[i]->Position()) - *pos ).Length();
			if (len < 1) len = 1;
			findWeight.Push( 1.0f/len );
		}
		int index = random.Select( findWeight.Mem(), findWeight.Size() );
		return match[index];
	}

	// Bad flag? Something didn't return?
	GLASSERT( 0 );
	return 0;
}
Пример #2
0
void CoreScript::AddFlag(const Vector2I& _pos)
{
	Vector2I pos = _pos;
	// A little UI fixup: set the flag to a porch
	// if we click on the switch.
	Chit* building = Context()->chitBag->QueryBuilding(IString(), pos, 0);
	if (building && (building->GetItem()->IName() == ISC::switchOn || building->GetItem()->IName() == ISC::switchOff)) {
		MapSpatialComponent* msc = GET_SUB_COMPONENT(building, SpatialComponent, MapSpatialComponent);
		if (msc) {
			pos = msc->PorchPos().min;
		}
	}

	Flag f = { pos, 0 };
	if (flags.Find(f) < 0) {
		Chit* chit = Context()->chitBag->NewChit();
		RenderComponent* rc = new RenderComponent("flag");
		chit->Add(rc);
		ProcRenderInfo info;
		AssignProcedural(ISC::team,
							false,
							ParentChit()->GetItem()->ID(),
							ParentChit()->Team(),
							false,
							0,
							0,
							&info);
		rc->SetProcedural(0, info);
		chit->SetPosition(ToWorld3F(pos));
		f.chitID = chit->ID();
		flags.Push(f);
	}
}
Пример #3
0
bool BuildingWithPorchFilter::Accept(Chit* chit)
{
	MapSpatialComponent* msc = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent);
	if (msc && msc->HasPorch()) {
		return true;
	}
	return false;
}
Пример #4
0
Chit* LumosChitBag::QueryPorch( const grinliz::Vector2I& pos )
{
	Vector2I sector = ToSector(pos);
	for( MapSpatialComponent* it = mapSpatialHash[SectorIndex(sector)]; it; it = it->nextBuilding ) {
		if ( it->PorchPos().Contains( pos )) {
			return it->ParentChit();
		}
	}
	return 0;
}
Пример #5
0
// Only MapSpatialComponents block the grid, and there
// is a linked list for every sector.
bool LumosChitBag::MapGridBlocked(int x, int y)
{
	Vector2I sector = ToSector(x, y);
	MapSpatialComponent* mscIt = mapSpatialHash[sector.y*NUM_SECTORS + sector.x];
	for (; mscIt; mscIt = mscIt->nextBuilding) {
		if (mscIt->Blocks() && mscIt->Bounds().Contains(x, y)) {
			return true;
		}
	}
	return false;
}
Пример #6
0
/*static*/ void MapSpatialComponent::UpdateGridLayer(WorldMap* worldMap, LumosChitBag* chitBag, const Rectangle2I& rect)
{
	for (Rectangle2IIterator it(rect); !it.Done(); it.Next()) {
		int porchType = 0;
		Chit* porchChit = chitBag->QueryPorch(it.Pos());
		if (porchChit) {
			MapSpatialComponent* msc = GET_SUB_COMPONENT(porchChit, SpatialComponent, MapSpatialComponent);
			GLASSERT(msc);
			porchType = msc->PorchType();
		}
		worldMap->SetPorch(it.Pos().x, it.Pos().y, porchType);
	}
}
Пример #7
0
void MapSpatialComponent::SetMapPosition( Chit* chit, int x, int y )
{
	GLASSERT( chit);
	MapSpatialComponent* msc = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent);
	GLASSERT(msc);
	int size = msc->Size();

	GLASSERT( size <= MAX_BUILDING_SIZE );
	GLASSERT( size <= MAX_BUILDING_SIZE );
	
	float px = (float)x + float(size)*0.5f;
	float py = (float)y + float(size)*0.5f;

	chit->SetPosition( px, 0, py );
}
Пример #8
0
Chit* LumosChitBag::NewLawnOrnament(const Vector2I& pos, const char* name, int team)
{
	const ChitContext* context = Context();
	Chit* chit = NewChit();

	GameItem* rootItem = ItemDefDB::Instance()->Get(name).Clone();

	// Hack...how to do this better??
	if (rootItem->IResourceName() == "ruins1.0") {
		CStr<32> str;
		str.Format("ruins1.%d", random.Rand(2));
		rootItem->SetResource(str.c_str());
	}

	int size = 1;
	rootItem->keyValues.Get(ISC::size, &size);

	MapSpatialComponent* msc = new MapSpatialComponent();
	msc->SetBuilding(size, false, 0);
	msc->SetBlocks((rootItem->flags & GameItem::PATH_NON_BLOCKING) ? false : true);
	chit->Add(msc);
	MapSpatialComponent::SetMapPosition(chit, pos.x, pos.y);


	chit->Add(new RenderComponent(rootItem->ResourceName()));
	chit->Add(new HealthComponent());
	AddItem(rootItem, chit, context->engine, team, 0);

	IString proc = rootItem->keyValues.GetIString("procedural");
	if (!proc.empty()) {
		ProcRenderInfo info;
		AssignProcedural(chit->GetItem(), &info);
		chit->GetRenderComponent()->SetProcedural(0, info);
	}
	
	context->engine->particleSystem->EmitPD(ISC::constructiondone, ToWorld3F(pos), V3F_UP, 0);

	if (XenoAudio::Instance()) {
		Vector3F pos3 = ToWorld3F(pos);
		XenoAudio::Instance()->PlayVariation(ISC::rezWAV, random.Rand(), &pos3);
	}

	return chit;
}
Пример #9
0
void LumosChitBag::BuildingCounts(const Vector2I& sector, int* counts, int n)
{
	BuildScript buildScript;

	for( MapSpatialComponent* it = mapSpatialHash[sector.y*NUM_SECTORS+sector.x]; it; it = it->nextBuilding ) {
		Chit* chit = it->ParentChit();
		GLASSERT( chit );

		const GameItem* item = chit->GetItem();
		if (!item)
			continue;
		const IString& name = item->IName();

		int id = 0;
		if (!name.empty()) {
			buildScript.GetDataFromStructure(name, &id);
			if (id < n) {
				counts[id] += 1;
			}
		}
	}
}
Пример #10
0
void RebuildAIComponent::OnChitMsg(Chit* chit, const ChitMsg& msg)
{
	if (   msg.ID() == ChitMsg::CHIT_DESTROYED 
		&& (chit != ParentChit())
		&& ToSector(chit->Position()) == ToSector(ParentChit()->Position())
		&& chit->Team() == this->ParentChit()->Team())
	{
		BuildingFilter buildingFilter;
		if (buildingFilter.Accept(chit))
		{
			GameItem* gi = chit->GetItem();
			WorkItem* wi = workItems.PushArr(1);
			wi->structure = gi->IName();
			MapSpatialComponent* msc = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent);
			if (msc) {
				wi->pos = msc->Bounds().min;
				wi->rot = LRint(YRotation(chit->Rotation()));
				GLOUTPUT(("ReBuild: Structure %s at %d,%d r=%d to rebuild queue.\n", wi->structure.safe_str(), wi->pos.x, wi->pos.y, int(wi->rot)));
			}
		}
	}
	super::OnChitMsg(chit, msg);
}
Пример #11
0
Chit* LumosChitBag::QueryBuilding( const IString& name, const grinliz::Rectangle2I& bounds, CChitArray* arr )
{
	GLASSERT( MAX_BUILDING_SIZE == 2 );	// else adjust logic
	Vector2I sector = ToSector( bounds.min );

	for( MapSpatialComponent* it = mapSpatialHash[SectorIndex(sector)]; it; it = it->nextBuilding ) {
		if ( it->Bounds().Intersect( bounds )) {
			Chit* chit = it->ParentChit();
			if (name.empty() || (chit->GetItem() && chit->GetItem()->IName() == name)) {
				if (!arr) {
					return chit;
				}
				if (arr->HasCap()) {
					arr->Push(chit);
				}
			}
		}
	}
	if (arr && !arr->Empty()) {
		return (*arr)[0];
	}
	return 0;
}
Пример #12
0
void CoreScript::DoTickInUse(int /*delta*/, int nSpawnTicks)
{
	tech -= Lerp(TECH_DECAY_0, TECH_DECAY_1, tech / double(TECH_MAX));
	int maxTech = MaxTech();
	tech = Clamp(tech, 0.0, double(maxTech) - 0.01);

	MapSpatialComponent* ms = GET_SUB_COMPONENT(parentChit, SpatialComponent, MapSpatialComponent);
	GLASSERT(ms);
	Vector2I pos2i = ms->MapPosition();

	if (nSpawnTicks && Team::IsDenizen(parentChit->Team())) {
		// Warning: essentially caps the #citizens to the capacity of CChitArray (32)
		// Which just happend to work out with the game design. Citizen limits: 4, 8, 16, 32
		int nCitizens = this->Citizens(0);
		int maxCitizens = this->MaxCitizens();

		if (nCitizens < maxCitizens) {
			if (!RecruitNeutral()) {
				Chit* chit = Context()->chitBag->NewDenizen(pos2i, parentChit->Team());
				this->AddCitizen(chit);
			}
		}
	}
}
Пример #13
0
void FluidTestScene::Tap3D(const grinliz::Vector2F& view, const grinliz::Ray& world)
{
	Vector3F at = { 0, 0, 0 };
	float t = 0;
	int result = IntersectRayAAPlane(world.origin, world.direction, 1, 0, &at, &t);
	if (result == INTERSECT) {
		Vector2I pos2i = ToWorld2I(at);
		Vector2I sector = { 0, 0 };
		CircuitSim* circuitSim = context.physicsSims->GetCircuitSim(sector);

		if (context.worldMap->Bounds().Contains(pos2i)) {

			bool trigger = false;
			if (!buildButton[BUTTON_DELETE].Down() && !buildButton[BUTTON_ROTATE].Down()) {
				Chit* building = context.chitBag->QueryPorch(pos2i);
				if (!building) {
					building = context.chitBag->QueryBuilding(IString(), pos2i, 0);
				}
				if (building) {
					if (building->GetItem()->IName() == ISC::detector) {
						circuitSim->TriggerDetector(pos2i);
						trigger = true;
					}
					else if (building->GetItem()->IName() == ISC::switchOn 
							 || building->GetItem()->IName() == ISC::switchOff) 
					{
						circuitSim->TriggerSwitch(pos2i);
						trigger = true;
					}
				}
			}

			int id = -1;
			if (!trigger) {
				for (int i = 0; i < NUM_BUTTONS; ++i) {
					if (buildButton[i].Down()) {
						id = i;
						break;
					}
				}
			}
			if (id >= 0) {
				Chit* chit = 0;
				switch (id) {
					case BUTTON_ROCK0:
					case BUTTON_ROCK1:
					case BUTTON_ROCK2:
					case BUTTON_ROCK3:
					context.worldMap->SetRock(pos2i.x, pos2i.y, id - BUTTON_ROCK0, false, WorldGrid::ROCK);
					break;

					case BUTTON_SWITCH_ON:
					chit = context.chitBag->NewBuilding(pos2i, "switchOn", TEAM_HOUSE);
					break;

					case BUTTON_SWITCH_OFF:
					chit = context.chitBag->NewBuilding(pos2i, "switchOff", TEAM_HOUSE);
					break;

					case BUTTON_TEMPLE:
					chit = context.chitBag->NewBuilding(pos2i, "temple", TEAM_HOUSE);
					break;

					case BUTTON_GATE:
					chit = context.chitBag->NewBuilding(pos2i, "gate", TEAM_HOUSE);
					break;

					case BUTTON_TIMED_GATE:
					chit = context.chitBag->NewBuilding(pos2i, "timedGate", TEAM_HOUSE);
					break;

					case BUTTON_DETECTOR:
					chit = context.chitBag->NewBuilding(pos2i, "detector", TEAM_HOUSE);
					break;

					case BUTTON_TURRET:
					chit = context.chitBag->NewBuilding(pos2i, "turret", TEAM_HOUSE);
					break;

					case BUTTON_DELETE:
					{
						Chit* building = context.chitBag->QueryBuilding(IString(), pos2i, 0);
						if (building) {
							building->QueueDelete();
						}
					}
					break;

					case BUTTON_ROTATE:
					{
						Chit* building = context.chitBag->QueryBuilding(IString(), pos2i, 0);
						if (building) {
							Matrix4 m;
							building->Rotation().ToMatrix(&m);
							float r = m.CalcRotationAroundAxis(1);
							r = NormalizeAngleDegrees(r + 90.0f);
							building->SetRotation(Quaternion::MakeYRotation(r));
						}

					}
					break;
				}
				if (chit) {
					MapSpatialComponent* msc = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent);
					if (msc) msc->SetNeedsCorePower(false);
				}
			}
		}
	}
}
Пример #14
0
CoreScript* CoreScript::CreateCore( const Vector2I& sector, int team, const ChitContext* context)
{
	// Destroy the existing core.
	// Create a new core, attached to the player.
	CoreScript* cs = CoreScript::GetCore(sector);
	if (cs) {
		Chit* core = cs->ParentChit();
		GLASSERT(core);

		CDynArray< Chit* > queryArr;

		// Tell all the AIs the core is going away.
		ChitHasAIComponent filter;
		Rectangle2F b = ToWorld2F(InnerSectorBounds(sector));
		context->chitBag->QuerySpatialHash(&queryArr, b, 0, &filter);
		for (int i = 0; i < queryArr.Size(); ++i) {
			queryArr[i]->GetAIComponent()->ClearTaskList();
		}

		//context.chitBag->QueueDelete(core);
		// QueueDelete is safer, but all kinds of asserts fire (correctly)
		// if 2 cores are in the same place. This may cause an issue
		// if CreateCore is called during the DoTick()
		// Setting the hp to 0 and then calling DoTick()
		// is a sleazy trick to clean up.
		core->GetItem()->hp = 0;
		if (core->GetHealthComponent()) {
			core->GetHealthComponent()->DoTick(1);
		}
		context->chitBag->DeleteChit(core);
	}

	const SectorData& sd = context->worldMap->GetSectorData(sector);
	if (sd.HasCore()) {
		int group = 0, id = 0;
		Team::SplitID(team, &group, &id);

		// Lots of trouble with this code. Used to assert,
		// but always seemed to be another case. White list
		// groups that *can* take over a core.
		if (team == TEAM_NEUTRAL || team == TEAM_TROLL || Team::IsDeity(team) || Team::IsDenizen(team)) {
			// Okay! take over.
			GLASSERT(!Team::IsDenizen(team) || id);		// make sure rogues got filtered out.
		}
		else {
			team = group = id = 0;
		}
		Chit* chit = context->chitBag->NewBuilding(sd.core, "core", team);

		// 'in use' instead of blocking.
		MapSpatialComponent* ms = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent);
		GLASSERT(ms);
		ms->SetBlocks(false);
		
		CoreScript* cs = new CoreScript();
		chit->Add(cs);
		GLASSERT(CoreScript::GetCore(ToSector(sd.core)) == cs);

		if (Team::IsDeity(team))
			chit->GetItem()->SetProperName(Team::Instance()->TeamName(team));
		else
			chit->GetItem()->SetProperName(sd.name);

		if (team != TEAM_NEUTRAL) {
			chit->GetItem()->SetSignificant(context->chitBag->GetNewsHistory(), 
											ToWorld2F(chit->Position()), 
											NewsEvent::DOMAIN_CREATED, NewsEvent::DOMAIN_DESTROYED, 0);


			// Make the dwellers defend the core.
			chit->Add(new GuardScript());

			// Make all buildings to be this team.
			CDynArray<Chit*> buildings;
			Vector2I buildingSector = ToSector(chit->Position());
			context->chitBag->FindBuilding(IString(), buildingSector, 0, LumosChitBag::EFindMode::NEAREST, &buildings, 0);
			
			for (int i = 0; i < buildings.Size(); ++i) {
				Chit* c = buildings[i];
				if (c->GetItem() && c->GetItem()->IName() != ISC::core) {
					c->GetItem()->SetTeam(team);
				}
			}
		}
		return cs;
	}
	return 0;
}
Пример #15
0
Chit* LumosChitBag::NewBuilding(const Vector2I& pos, const char* name, int team)
{
	const ChitContext* context = Context();
	Chit* chit = NewChit();

	const GameItem& rootItem = ItemDefDB::Instance()->Get(name);
	GameItem* item = rootItem.Clone();

	// Hack...how to do this better??
	if (item->IResourceName() == "pyramid0") {
		CStr<32> str;
		str.Format("pyramid%d", random.Rand(3));
		item->SetResource(str.c_str());
	}
	if (item->IResourceName() == ISC::kiosk) {
		switch (random.Rand(4)) {
			case 0: item->SetResource("kiosk.n");	break;
			case 1: item->SetResource("kiosk.m");	break;
			case 2: item->SetResource("kiosk.s");	break;
			default:item->SetResource("kiosk.c");	break;
		}
	}

	int size = 1;
	rootItem.keyValues.Get(ISC::size, &size);
	int porch = 0;
	rootItem.keyValues.Get(ISC::porch, &porch);
	const int circuit = 0;

	// Should be pre-cleared. But recover from weird situations by clearing.
	// Note that water is a real problem.
	Rectangle2I r;
	r.Set(pos.x, pos.y, pos.x + size - 1, pos.y + size - 1);
	for (Rectangle2IIterator it(r); !it.Done(); it.Next()) {
		const WorldGrid& wg = context->worldMap->GetWorldGrid(it.Pos());
		GLASSERT(wg.IsLand());
		(void)wg;
		context->worldMap->SetRock(it.Pos().x, it.Pos().y, 0, false, 0);
		context->worldMap->SetPlant(it.Pos().x, it.Pos().y, 0, 0);
	}

	MapSpatialComponent* msc = new MapSpatialComponent();
	msc->SetBuilding(size, porch != 0, circuit);
	msc->SetBlocks((rootItem.flags & GameItem::PATH_NON_BLOCKING) ? false : true);
	chit->Add(msc);
	MapSpatialComponent::SetMapPosition(chit, pos.x, pos.y);

	chit->Add(new RenderComponent(item->ResourceName()));
	chit->Add(new HealthComponent());
	AddItem(item, chit, context->engine, team, 0);

	IString script = rootItem.keyValues.GetIString("script");
	if (!script.empty()) {
		Component* s = ComponentFactory::Factory(script.c_str(), &chitContext);
		GLASSERT(s);
		chit->Add(s);
	}

	IString proc = rootItem.keyValues.GetIString("procedural");
	if (!proc.empty()) {
		ProcRenderInfo info;
		AssignProcedural(chit->GetItem(), &info);
		chit->GetRenderComponent()->SetProcedural(0, info);
	}

	IString consumes = rootItem.keyValues.GetIString(ISC::zone);
	if (!consumes.empty()) {
		Component* s = ComponentFactory::Factory("EvalBuildingScript", &chitContext);
		GLASSERT(s);
		chit->Add(s);
	}

	IString nameGen = rootItem.keyValues.GetIString( "nameGen");
	if ( !nameGen.empty() ) {
		IString p = Context()->chitBag->NameGen(nameGen.c_str(), chit->random.Rand());
		chit->GetItem()->SetProperName( p );
	}

#if 0	// debugging
	SectorPort sp;
	sp.sector.Set( pos.x/SECTOR_SIZE, pos.y/SECTOR_SIZE );
	sp.port = 1;
	worldMap->SetRandomPort( sp );
#endif

	context->engine->particleSystem->EmitPD( ISC::constructiondone, ToWorld3F( pos ), V3F_UP, 0 );

	if (XenoAudio::Instance()) {
		Vector3F pos3 = ToWorld3F(pos);
		XenoAudio::Instance()->PlayVariation(ISC::rezWAV, random.Rand(), &pos3);
	}

	return chit;
}
Пример #16
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;
}