Пример #1
0
void PlantScript::DoTick(U32 delta)
{
	// We need process at a steady rate so that
	// the time between ticks is constant.
	// This is performance regressive, so something
	// to keep an eye on.
	static const int MAP2 = MAX_MAP_SIZE*MAX_MAP_SIZE;
	static const int DELTA = 100*1000;	// How frequenty to tick a given plant
	static const int N_PER_MSEC = MAP2 / DELTA;
	static const int GROWTH_CHANCE = 8;
	static const int PRIME = 1553;

	static const float SHADE_EFFECT = 0.7f;

	int n = N_PER_MSEC * delta;
	Weather* weather = Weather::Instance();

	WorldMap* worldMap = context->worldMap;
	Rectangle2I bounds = worldMap->Bounds();
	bounds.Outset(-1);	// edge of map: don't want to tap over the edge.

	const Vector3F& light = context->engine->lighting.direction;
	const float		norm = Max(fabs(light.x), fabs(light.z));
	Vector2I		lightTap = { int(LRintf(light.x / norm)), int(LRintf(light.z / norm)) };
	Census*			census = &context->chitBag->census;

	for (int i = 0; i < n; ++i) {
		index += PRIME;

		int x = IndexToMapX(index);
		int y = IndexToMapY(index);

		const WorldGrid& wg = worldMap->GetWorldGrid(x, y);
		if (!wg.Plant()) continue;

		Vector2I pos2i = { x, y };
		Vector2F pos2f = ToWorld2F(pos2i);

		// --- Light Tap --- //
		const float	height = PlantScript::PlantRes(wg.Plant() - 1, wg.PlantStage())->AABB().SizeY();
		const float	rainBase		= weather->RainFraction(pos2f.x, pos2f.y);
		const float sunBase			= (1.0f - rainBase);
		const float temperatureBase	= weather->Temperature(pos2f.x, pos2f.y);

		float rain			= rainBase;
		float sun			= sunBase;
		float temperature	= temperatureBase;
		float growth		= 1.0f;

		Vector2I tap = pos2i + lightTap;

		// Check for something between us and the light.
		const WorldGrid& wgTap = worldMap->GetWorldGrid(tap);
		float tapHeight = float(wgTap.RockHeight());
		if (wgTap.PlantStage()) {
			tapHeight = PlantScript::PlantRes(wgTap.Plant() - 1, wgTap.PlantStage())->AABB().SizeY();
		}

		if (tapHeight > (height + 0.1f)) {
			// in shade
			sun *= SHADE_EFFECT;
			temperature *= SHADE_EFFECT;
		}

		// ---- Adjacent --- //
		static const int NADJ = 4;
		static const Vector2I check[NADJ] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
		int same = 0;
		for (int i = 0; i<NADJ; ++i) {
			tap = pos2i + check[i];
			const WorldGrid& wgAdj = worldMap->GetWorldGrid(tap.x, tap.y);
			if (wgAdj.Plant() == wg.Plant()) {
				++same;
			}

			if (wgAdj.RockHeight()) {
				// Water or rock runoff increase water.
				rain += 0.25f * rainBase;
			}
			if (wgAdj.IsFluid()) {
				rain += 0.25f;	// just a lot of water.
			}
			if (wgAdj.IsWater()) {
				rain += 0.25f;	// more water
				temperature = Mean(0.5f, temperature); // moderate temperature
			}
		}
		// Nutrient depletion? Too packed in?
		if (same == NADJ) {
			growth *= 0.25f;
		}

		// Are we under water?
		float fluidHeight = wg.FluidHeight();
		if (fluidHeight > 0.01f) {
			// Any amount of water goes to rain 100%
			rain = 1.0f;
			// not sure what to do with temp...assume a little cooler?
			temperature *= 0.8f;

			// blocks light...
			float sizeY = PlantScript::PlantRes(wg.Plant() - 1, wg.PlantStage())->AABB().SizeY();
			if (fluidHeight > sizeY)
				sun = 0;
			else if (fluidHeight > sizeY * 0.5f)
				sun = sun * (1.0f - fluidHeight / sizeY);
		}

		rain = Clamp(rain, 0.0f, 1.0f);
		temperature = Clamp(temperature, 0.0f, 1.0f);
		sun = Clamp(sun, 0.0f, 1.0f);

		// ------- calc ------- //
		Vector3F actual = { sun, rain, temperature };
		Vector3F optimal = { 0.5f, 0.5f, 0.5f };

		const GameItem* item = PlantScript::PlantDef(wg.Plant() - 1);
		item->keyValues.Get(ISC::sun, &optimal.x);
		item->keyValues.Get(ISC::rain, &optimal.y);
		item->keyValues.Get(ISC::temp, &optimal.z);

		float distance = (optimal - actual).Length();
		distance = distance / growth;

		const float GROW = Lerp(0.2f, 0.1f, (float)wg.PlantStage() / (float)(MAX_PLANT_STAGES - 1));
		const float DIE = 0.4f;
		float seconds = float(DELTA) / 1000.0f;

		if (distance < GROW) {
			// Heal.
			float hp = HP_PER_SECOND*seconds;
			DamageDesc heal( -hp, 0 );
			worldMap->VoxelHit(pos2i, heal);

			// Grow
			int nStage = wg.IsFlower() ? PLANT_BLOCKING_STAGE : MAX_PLANT_STAGES;

			if (wg.HPFraction() > 0.8f) {
				if (wg.PlantStage() < (nStage - 1)) {
					int hp = wg.HP();
					worldMap->SetPlant(pos2i.x, pos2i.y, wg.Plant(), wg.PlantStage() + 1);
					worldMap->SetWorldGridHP(pos2i.x, pos2i.y, hp);
				}
				if (random.Rand(GROWTH_CHANCE) < wg.PlantStage()) {
					// Number range reflects wind direction.
					int dx = -1 + random.Rand(4);	// [-1,2]
					int dy = -1 + random.Rand(3);	// [-1,1]

					// Remember that create plant will favor creating
					// existing plants, so we don't need to specify
					// what to create.
					Sim* sim = context->chitBag->GetSim();
					GLASSERT(sim);
					sim->CreatePlant(pos2i.x + dx, pos2i.y + dy, -1);
				}
			}
			int stage = wg.PlantStage();	// 0-3
			CoreScript* cs = CoreScript::GetCore(ToSector(pos2i));
			// Totally "what feels right in world gen" constant in the random.Rand()
			if ((census->wildFruit < MAX_WILD_FRUIT) && cs && (!cs->InUse()) && int(random.Rand(200)) < (stage*stage)) {
				context->chitBag->NewWildFruit(pos2i);
			}
		}
		else if (distance > DIE) {
			DamageDesc dd(HP_PER_SECOND * seconds, 0);
			worldMap->VoxelHit(pos2i, dd);
			if (wg.HP() == 0) {
				worldMap->SetPlant(pos2i.x, pos2i.y, 0, 0);
			}
		}
	}
}
Пример #2
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;
}