void VillageControl::update(bool currentlyActive) { considerWelcomeMessage(); considerCancellingAttack(); checkEntries(); if (Collective* enemy = getEnemyCollective()) maxEnemyPower = max(maxEnemyPower, enemy->getDangerLevel()); vector<Creature*> allMembers = getCollective()->getCreatures(); for (auto team : getCollective()->getTeams().getAll()) { for (const Creature* c : getCollective()->getTeams().getMembers(team)) if (!getCollective()->hasTask(c)) { getCollective()->getTeams().cancel(team); break; } return; } double updateFreq = 0.1; if (canPerformAttack(currentlyActive) && Random.roll(1 / updateFreq)) if (villain) { double prob = villain->getAttackProbability(this) / updateFreq; if (prob > 0 && Random.roll(1 / prob)) { vector<Creature*> fighters; fighters = getCollective()->getCreatures({MinionTrait::FIGHTER}, {MinionTrait::SUMMONED}); if (getCollective()->getGame()->isSingleModel()) fighters = filter(fighters, [this] (const Creature* c) { return contains(getCollective()->getTerritory().getAll(), c->getPosition()); }); Debug() << getCollective()->getName().getShort() << " fighters: " << int(fighters.size()) << (!getCollective()->getTeams().getAll().empty() ? " attacking " : ""); if (fighters.size() >= villain->minTeamSize && allMembers.size() >= villain->minPopulation + villain->minTeamSize) launchAttack(getPrefix(Random.permutation(fighters), Random.get(villain->minTeamSize, min(fighters.size(), allMembers.size() - villain->minPopulation) + 1))); } } }
void VillageControl::onRansomPaid() { for (auto team : getCollective()->getTeams().getAll()) { vector<Creature*> members = getCollective()->getTeams().getMembers(team); for (Creature* c : members) getCollective()->cancelTask(c); getCollective()->getTeams().cancel(team); } }
void VillageControl::checkEntries() { if (villain) for (auto& trigger : villain->triggers) if (trigger.getId() == AttackTriggerId::ENTRY) for (Position pos : getCollective()->getTerritory().getAll()) if (Creature* c = pos.getCreature()) if (getCollective()->getTribe()->isEnemy(c)) entries = true; }
void VillageControl::checkEntries() { for (auto& villain : villains) for (auto& trigger : villain.triggers) if (trigger.getId() == AttackTriggerId::ENTRY) for (Position pos : getCollective()->getTerritory().getAll()) if (Creature* c = pos.getCreature()) if (getCollective()->getTribe()->isEnemy(c)) if (auto villain = getVillain(c)) entries.insert(villain->collective); }
void VillageControl::considerCancellingAttack() { for (auto team : getCollective()->getTeams().getAll()) { vector<Creature*> members = getCollective()->getTeams().getMembers(team); if (members.size() < (attackSizes[team] + 1) / 2 || (members.size() == 1 && members[0]->getHealth() < 0.5)) { for (Creature* c : members) getCollective()->cancelTask(c); getCollective()->getTeams().cancel(team); } } }
void VillageControl::tick(double time) { considerWelcomeMessage(); considerCancellingAttack(); checkEntries(); vector<Creature*> allMembers = getCollective()->getCreatures(); for (auto team : getCollective()->getTeams().getAll()) { for (const Creature* c : getCollective()->getTeams().getMembers(team)) if (!getCollective()->hasTask(c)) { getCollective()->getTeams().cancel(team); break; } return; } double updateFreq = 0.1; if (Random.roll(1 / updateFreq)) for (auto& villain : villains) { double prob = villain.getAttackProbability(this) / updateFreq; if (prob > 0 && Random.roll(1 / prob)) { vector<Creature*> fighters; fighters = getCollective()->getCreatures({MinionTrait::FIGHTER}, {MinionTrait::SUMMONED}); fighters = filter(fighters, [this] (const Creature* c) { return contains(getCollective()->getTerritory().getAll(), c->getPosition()); }); Debug() << getCollective()->getShortName() << " fighters: " << int(fighters.size()) << (!getCollective()->getTeams().getAll().empty() ? " attacking " : ""); if (fighters.size() < villain.minTeamSize || allMembers.size() < villain.minPopulation + villain.minTeamSize) continue; launchAttack(villain, getPrefix(Random.permutation(fighters), Random.get(villain.minTeamSize, min(fighters.size(), allMembers.size() - villain.minPopulation) + 1))); break; } } }
void VillageControl::launchAttack(Villain& villain, vector<Creature*> attackers) { optional<int> ransom; int hisGold = villain.collective->numResource(CollectiveResourceId::GOLD); if (villain.ransom && hisGold >= villain.ransom->second) ransom = max<int>(villain.ransom->second, (Random.getDouble(villain.ransom->first * 0.6, villain.ransom->first * 1.5)) * hisGold); villain.collective->addAttack(CollectiveAttack(getCollective(), attackers, ransom)); TeamId team = getCollective()->getTeams().createPersistent(attackers); getCollective()->getTeams().activate(team); getCollective()->freeTeamMembers(team); for (Creature* c : attackers) getCollective()->setTask(c, villain.getAttackTask(this)); attackSizes[team] = attackers.size(); }
void VillageControl::onPickupEvent(const Creature* who, const vector<Item*>& items) { if (getCollective()->getTerritory().contains(who->getPosition())) if (isEnemy(who) && villain) if (contains(villain->triggers, AttackTriggerId::STOLEN_ITEMS)) { bool wasTheft = false; for (const Item* it : items) if (myItems.contains(it)) { wasTheft = true; ++stolenItemCount; myItems.erase(it); } if (getCollective()->hasLeader() && wasTheft) { who->playerMessage(PlayerMessage("You are going to regret this", MessagePriority::HIGH)); } } }
void VillageControl::considerWelcomeMessage() { if (!getCollective()->hasLeader()) return; if (villain) if (villain->welcomeMessage) switch (*villain->welcomeMessage) { case DRAGON_WELCOME: for (Position pos : getCollective()->getTerritory().getAll()) if (Creature* c = pos.getCreature()) if (c->isAffected(LastingEffect::INVISIBLE) && isEnemy(c) && c->isPlayer() && getCollective()->getLeader()->canSee(c->getPosition())) { c->playerMessage(PlayerMessage("\"Well thief! I smell you and I feel your air. " "I hear your breath. Come along!\"", MessagePriority::CRITICAL)); villain->welcomeMessage.reset(); } break; } }
virtual MoveInfo getMove(Creature* c) override { CHECK(!pickedUp); if (c->getPosition() == getPosition()) { vector<Item*> hereItems; for (Item* it : c->getPickUpOptions()) if (items.contains(it)) { hereItems.push_back(it); items.erase(it); } getCollective()->onCantPickItem(items); if (hereItems.empty()) { setDone(); return NoMove; } items = hereItems; if (c->canPickUp(hereItems)) return {1.0, [=] { for (auto elem : Item::stackItems(hereItems)) c->globalMessage(c->getTheName() + " picks up " + elem.first, ""); c->pickUp(hereItems); pickedUp = true; onPickedUp(); getCollective()->onPickedUp(getPosition(), hereItems); }}; else { getCollective()->onCantPickItem(items); setDone(); return NoMove; } } if (MoveInfo move = getMoveToPosition(c, true)) return move; else if (--tries == 0) { getCollective()->onCantPickItem(items); setDone(); } return NoMove; }
virtual MoveInfo getMove(Creature* c) override { if (!c->hasSkill(Skill::get(SkillId::CONSTRUCTION))) return NoMove; Vec2 dir = getPosition() - c->getPosition(); if (dir.length8() == 1) { if (c->canConstruct(dir, type)) return {1.0, [this, c, dir] { c->construct(dir, type); if (!c->canConstruct(dir, type)) { setDone(); getCollective()->onConstructed(getPosition(), type); } }}; else { setDone(); return NoMove; } } else return getMoveToPosition(c); }
void VillageControl::launchAttack(vector<Creature*> attackers) { if (Collective* enemy = getEnemyCollective()) { for (Creature* c : attackers) // if (getCollective()->getGame()->canTransferCreature(c, enemy->getLevel()->getModel())) getCollective()->getGame()->transferCreature(c, enemy->getLevel()->getModel()); optional<int> ransom; int hisGold = enemy->numResource(CollectiveResourceId::GOLD); if (villain->ransom && hisGold >= villain->ransom->second) ransom = max<int>(villain->ransom->second, (Random.getDouble(villain->ransom->first * 0.6, villain->ransom->first * 1.5)) * hisGold); enemy->addAttack(CollectiveAttack(getCollective(), attackers, ransom)); TeamId team = getCollective()->getTeams().createPersistent(attackers); getCollective()->getTeams().activate(team); getCollective()->freeTeamMembers(team); for (Creature* c : attackers) getCollective()->setTask(c, villain->getAttackTask(this)); attackSizes[team] = attackers.size(); } }
void VillageControl::onOtherKilled(const Creature* victim, const Creature* killer) { if (victim->getTribe() == getCollective()->getTribe()) if (isEnemy(killer)) victims += 0.15; // small increase for same tribe but different village }
Collective* VillageControl::getEnemyCollective() const { return getCollective()->getGame()->getPlayerCollective(); }
bool VillageControl::canPerformAttack(bool currentlyActive) { return !currentlyActive || getCollective()->getGame()->isSingleModel() || getCollective()->getLevel()->getModel() == getCollective()->getGame()->getMainModel().get(); }