void CoreScript::AddTech() { tech += TECH_ADDED_BY_VISITOR; tech = Clamp( tech, 0.0, Min( double(TECH_MAX), double( MaxTech() ) - 0.01 )); achievement.techLevel = Max(achievement.techLevel, (int)tech); int team = ParentChit()->Team(); int superTeam = Team::Instance()->SuperTeam(team); if (team != superTeam) { CoreScript* super = CoreScript::GetCoreFromTeam(superTeam); if (super) { super->AddTech(); // EXPERIMENTAL: also transfer Au Wallet* wallet = parentChit->GetWallet(); GLASSERT(wallet); if (wallet && super->ParentChit()->GetWallet() && (wallet->Gold() > (GOLD_XFER_TAKEOVER *3/2))) { int gold = GOLD_XFER_TAKEOVER / 10; super->ParentChit()->GetWallet()->Deposit(wallet, gold); } } } }
void CoreScript::UpdateScore(int n) { static const double FACTOR = 0.002; if (n) { // Tuned - somewhat sleazily - that basic 4 unit, 0 temple is one point. double score = CivTech() * double(n) * double(scoreTicker.Period()) * FACTOR; achievement.civTechScore += Min(1, int(score)); int team = ParentChit()->Team(); CDynArray<int> subTeams; if (Team::Instance()->IsController(team, &subTeams)) { for (int i = 0; i < subTeams.Size(); ++i) { CoreScript* subCore = CoreScript::GetCoreFromTeam(subTeams[i]); if (subCore) { score += 0.5 * subCore->CivTech() * double(n) * double(scoreTicker.Period()) * FACTOR; } } } // Push this to the GameItem, so it can be recorded in the history + census. GameItem* gi = ParentChit()->GetItem(); if (gi) { // Score is a 16 bit quantity... int s = achievement.civTechScore; if (s > 65535) s = 65535; gi->keyValues.Set(ISC::score, s); gi->UpdateHistory(); } } if (ParentChit()->GetItem()) { achievement.gold = Max(achievement.gold, ParentChit()->GetItem()->wallet.Gold()); achievement.population = Max(achievement.population, citizens.Size()); } }
CoreScript* CoreScript::GetCoreFromTeam(int team) { if (!teamToCoreInfo) return 0; // happens on tear down if (team == 0) return 0; int index = 0; if (teamToCoreInfo->Query(team, &index)) { // Make sure it is current: GLASSERT(index >= 0 && index < NUM_SECTORS*NUM_SECTORS); CoreScript* cs = coreInfoArr[index].coreScript; // GLASSERT(cs); // cores get destroyed at odd times. if (cs && cs->ParentChit()->Team() == team) { return cs; } } for (int i = 0; i < NUM_SECTORS*NUM_SECTORS; ++i) { if (coreInfoArr[i].coreScript && coreInfoArr[i].coreScript->ParentChit()->Team() == team) { teamToCoreInfo->Add(team, i); return coreInfoArr[i].coreScript; } } return 0; }
void MapScene::EnableButtons() { Vector2I v = data->destSector; CoreScript* cs = CoreScript::GetCore(v); CoreScript* homeCore = lumosChitBag->GetHomeCore(); if (!homeCore) { warButton.SetEnabled(false); peaceButton.SetEnabled(false); peaceButton.SetText("Peace Treaty"); return; } warButton.SetEnabled(Team::Instance()->War(cs, homeCore, false, &lumosChitBag->GetSim()->GetCachedWeb())); int peace = Team::Instance()->Peace(cs, homeCore, false, &lumosChitBag->GetSim()->GetCachedWeb()); const Wallet* wallet = homeCore->ParentChit()->GetWallet(); if (peace > 0 && wallet->HasGold(peace)) { CStr<64> str; str.Format("Peace Treaty %d Au", peace); peaceButton.SetEnabled(true); peaceButton.SetText(str.c_str()); } else { peaceButton.SetEnabled(false); peaceButton.SetText("Peace Treaty"); } }
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; }
Chit* LumosChitBag::NewVisitor( int visitorIndex, const Web& web) { const ChitContext* context = Context(); if (web.Empty()) return 0; Vector2I startSector = { NUM_SECTORS / 2, NUM_SECTORS / 2 }; CoreScript* cs = CoreScript::GetCore(startSector); if (!cs) return 0; // cores get deleted, web is cached, etc. Vector3F pos = cs->ParentChit()->Position(); Chit* chit = NewChit(); const GameItem& rootItem = ItemDefDB::Instance()->Get( "visitor" ); chit->Add( new RenderComponent( rootItem.ResourceName() )); AIComponent* ai = new AIComponent(); chit->Add( ai ); GLASSERT( visitorIndex >= 0 && visitorIndex < Visitors::NUM_VISITORS ); ai->SetVisitorIndex( visitorIndex ); Visitors::Instance()->visitorData[visitorIndex].Connect(); // initialize. // Visitors start at world center, with gridMove, and go from there. chit->SetPosition( pos ); PathMoveComponent* pmc = new PathMoveComponent(); chit->Add(pmc); AddItem( rootItem.Name(), chit, context->engine, TEAM_VISITOR, 0 ); chit->Add( new HealthComponent()); //chit->Add( new VisitorStateComponent()); return chit; }
void LumosChitBag::DoTick(U32 delta) { // From the CHIT_DESTROYED_START we have a list of // Cores that will be going away...check them here, // so that we replace as soon as possible. while (!coreCreateList.Empty()) { CreateCoreData data = coreCreateList.Pop(); CoreScript* sc = CoreScript::GetCore(data.sector); Vector2I sector = data.sector; CoreScript* conqueringCore = CoreScript::GetCoreFromTeam(data.conqueringTeam); if (!sc) { // FIXME: last criteria "must be super team" isn't correct. Sub-teams // should be able to conquor for super teams. if (data.wantsTakeover && data.conqueringTeam && conqueringCore && Team::IsDenizen(data.conqueringTeam) && (Team::Instance()->SuperTeam(data.conqueringTeam) == data.conqueringTeam)) { TakeOverCore(sector, conqueringCore->ParentChit()); } else { CoreScript::CreateCore(sector, TEAM_NEUTRAL, Context()); } } } super::DoTick(delta); }
void CoreScript::OnChitMsg(Chit* chit, const ChitMsg& msg) { // Logic split between Sim::OnChitMsg and CoreScript::OnChitMsg if (msg.ID() == ChitMsg::CHIT_DESTROYED && (chit == parentChit)) { while (!citizens.Empty()) { int citizenID = citizens.Pop(); Chit* citizen = Context()->chitBag->GetChit(citizenID); if (citizen && citizen->GetItem()) { // Set to rogue team. citizen->GetItem()->SetRogue(); } } Vector2I pos2i = ToWorld2I(chit->Position()); Vector2I sector = ToSector(pos2i); if (Team::Instance()->IsController(chit->Team())) { NewsEvent news(NewsEvent::SUPERTEAM_DELETED, ToWorld2F(pos2i), chit->GetItemID(), 0); Context()->chitBag->GetNewsHistory()->Add(news); } int controllerTeam = 0; if (Team::Instance()->IsControlled(chit->Team(), &controllerTeam)) { CoreScript* controller = CoreScript::GetCoreFromTeam(controllerTeam); GLASSERT(controller); if (controller) { NewsEvent news(NewsEvent::SUBTEAM_DELETED, ToWorld2F(pos2i), chit->GetItemID(), controller->ParentChit()->GetItemID()); Context()->chitBag->GetNewsHistory()->Add(news); } } int deleterID = chit->GetItemComponent() ? chit->GetItemComponent()->LastDamageID() : 0; Chit* deleter = Context()->chitBag->GetChit(deleterID); int superTeam = 0; if (deleter && (deleter->Team() == Team::Instance()->SuperTeam(deleter->Team())) && Team::IsDenizen(deleter->Team()) && Team::IsDenizen(chit->Team())) { superTeam = deleter->Team(); } if (chit->Team() != TEAM_NEUTRAL) { if (superTeam) { LumosChitBag::CreateCoreData data = { sector, true, chit->Team(), deleter ? deleter->Team() : 0 }; Context()->chitBag->coreCreateList.Push(data); } else { LumosChitBag::CreateCoreData data = { sector, false, chit->Team(), deleter ? deleter->Team() : 0 }; Context()->chitBag->coreCreateList.Push(data); } } else { // Neutral cores are taken over by wandering over them // with enough friend units to have critical mass. LumosChitBag::CreateCoreData data = { sector, false, 0, 0 }; Context()->chitBag->coreCreateList.Push(data); } Team::Instance()->CoreDestroyed(parentChit->Team()); } }
Vector2I LumosChitBag::GetHomeSector() const { grinliz::Vector2I v = { 0, 0 }; CoreScript* home = GetHomeCore(); if (home && home->ParentChit() ) { v = ToSector(home->ParentChit()->Position()); } return v; }
Chit* LumosChitBag::GetAvatar() const { CoreScript* cs = GetHomeCore(); if (cs) { Chit* prime = cs->PrimeCitizen(); return prime; // The prime citizen of the home domain is the Avatar. } return 0; }
void MapScene::ItemTapped(const gamui::UIItem* item) { Vector2I sector = { 0, 0 }; Vector2I v = data->destSector; CoreScript* cs = CoreScript::GetCore(v); CoreScript* homeCore = lumosChitBag->GetHomeCore(); if (item == &okay) { data->destSector.Zero(); lumosGame->PopScene(); } else if (item == &gridTravel) { lumosGame->PopScene(); } else if (item == &viewButton) { data->view = true; lumosGame->PopScene(); } else if (item == &mapImage) { float x = 0, y = 0; gamui2D.GetRelativeTap(&x, &y); sector.x = int(x * float(NUM_SECTORS)); sector.y = int(y * float(NUM_SECTORS)); data->destSector = sector; DrawMap(); EnableButtons(); } else if (item == &mapImage2) { float x = 0, y = 0; gamui2D.GetRelativeTap(&x, &y); Rectangle2I b = MapBounds2(); sector.x = b.min.x + int(x * float(b.Width())); sector.y = b.min.y + int(y * float(b.Height())); data->destSector = sector; DrawMap(); EnableButtons(); } else if (item == &warButton) { Team::Instance()->War(cs, homeCore, true, &lumosChitBag->GetSim()->GetCachedWeb()); DrawMap(); EnableButtons(); } else if (item == &peaceButton) { int cost = Team::Instance()->Peace(cs, homeCore, false, &lumosChitBag->GetSim()->GetCachedWeb()); Wallet* wallet = homeCore->ParentChit()->GetWallet(); if (wallet->HasGold(cost)) { ReserveBank::Instance()->GetWallet()->Deposit(wallet, cost); Team::Instance()->Peace(cs, homeCore, true, &lumosChitBag->GetSim()->GetCachedWeb()); DrawMap(); EnableButtons(); } } }
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; }
CoreScript* LumosChitBag::TakeOverCore(const Vector2I& sector, Chit* conquerer) { int conqueringTeam = conquerer->Team(); int teamID = Team::Instance()->GenTeam(Team::Group(conqueringTeam)); bool isSubDomain = !Team::IsRogue(conqueringTeam); if (isSubDomain) { Team::Instance()->AddSubteam(conqueringTeam, teamID); } CoreScript* newCore = CoreScript::CreateCore(sector, teamID, Context()); GLASSERT(CoreScript::GetCore(sector) == newCore); newCore->ParentChit()->Add(DomainAI::Factory(teamID)); CDynArray<Chit*> arr; // After takeover, all the citizens are rogue and buildings are neutral. // As a first pass, simply takeover everything that meets that criteria. ChitAcceptAll filter; BuildingFilter buildingFilter; Context()->chitBag->QuerySpatialHash(&arr, InnerSectorBounds(sector), 0, &filter); for (Chit* c : arr) { if (c->PlayerControlled()) continue; GameItem* mainItem = c->GetItem(); if (!mainItem) continue; if (mainItem->IsDenizen() && Team::IsRogue(mainItem->Team())) { newCore->AddCitizen(c); } else if (buildingFilter.Accept(c) && mainItem->IName() != ISC::core) { c->GetItem()->SetTeam(teamID); } } // Finally, give this new core a chance. // Transfer money from Conquering domain. CoreScript* conqueringCore = CoreScript::GetCoreFromTeam(conquerer->Team()); if (conqueringCore) { int gold = conqueringCore->ParentChit()->GetWallet()->Gold() / 4; gold = Min(gold, GOLD_XFER_TAKEOVER); newCore->ParentChit()->GetWallet()->Deposit(conqueringCore->ParentChit()->GetWallet(), gold); } NewsEvent news(isSubDomain ? NewsEvent::DOMAIN_CONQUER : NewsEvent::DOMAIN_TAKEOVER, ToWorld2F(newCore->ParentChit()->Position()), newCore->ParentChit()->GetItemID(), conquerer->GetItemID()); Context()->chitBag->GetNewsHistory()->Add(news); return newCore; }
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(); }
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 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)); } } }
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; }
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); } }
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); } } } }
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; }