int MapSpatialComponent::DoTick(U32 delta) { if (slowTick.Delta(delta)) { CoreScript* cs = CoreScript::GetCore(ToSector(parentChit->Position())); glowTarget = 0; if (needsCorePower) { if (cs && cs->InUse()) { Rectangle2I porch = this->PorchPos(); if (porch.min.IsZero()) { // No porch. Just need core. glowTarget = 1; } else { Vector2F start = ToWorld2F(porch.min); Vector2F end = ToWorld2F(cs->ParentChit()->Position()); if (Context()->worldMap->CalcPath(start, end, 0, 0, false)) { glowTarget = 1; } } } } else { glowTarget = 1; glow = 1; } } if (glow != glowTarget) { glow = TravelTo(0.7f, delta, glow, glowTarget); } if (parentChit->GetItem() && parentChit->GetItem()->IName() == ISC::core) { glow = glowTarget = 1; } RenderComponent* rc = parentChit->GetRenderComponent(); if (rc) { const GameItem* gameItem = parentChit->GetItem(); if (gameItem && gameItem->keyValues.GetIString(ISC::procedural) == ISC::team) { // int team = parentChit->Team(); ProcRenderInfo info; AssignProcedural(parentChit->GetItem(), &info); info.color.X(Matrix4::M41) *= glow; info.color.X(Matrix4::M42) *= glow; info.color.X(Matrix4::M43) *= glow; rc->SetProcedural(0, info); rc->SetSaturation(0.5f + 0.5f*glow); } } return VERY_LONG_TICK; }
void Web::Calc(const Vector2I* exclude) { static Vector2I origin = { NUM_SECTORS / 2, NUM_SECTORS / 2 }; CArray<Vector2I, NUM_SECTORS * NUM_SECTORS> cores; cores.Push(origin); int n = 0; CoreScript** list = CoreScript::GetCoreList(&n); for (int i = 0; i < n; ++i) { CoreScript* cs = list[i]; Vector2I sector = ToSector(cs->ParentChit()->Position()); if ( (sector != origin) && cs && cs->InUse() && Team::Instance()->GetRelationship(cs->ParentChit()->Team(), TEAM_VISITOR) != ERelate::ENEMY) { GLASSERT(cores.HasCap()); if (!exclude || (*exclude != sector)) { cores.Push(sector); } } } tree.Calc(cores.Mem(), cores.Size()); }
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); } } } }
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)); } } }
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; }