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; }
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); } }
bool BuildingWithPorchFilter::Accept(Chit* chit) { MapSpatialComponent* msc = GET_SUB_COMPONENT(chit, SpatialComponent, MapSpatialComponent); if (msc && msc->HasPorch()) { return true; } return false; }
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; }
// 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; }
/*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); } }
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 ); }
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; }
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; } } } }
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); }
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; }
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); } } } }
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); } } } } }
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; }
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; }
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; }