1 #include "stdafx.h"
2 #include "collective.h"
3 #include "collective_control.h"
4 #include "creature.h"
5 #include "effect.h"
6 #include "level.h"
7 #include "item.h"
8 #include "item_factory.h"
9 #include "statistics.h"
10 #include "technology.h"
11 #include "monster.h"
12 #include "options.h"
13 #include "model.h"
14 #include "game.h"
15 #include "spell.h"
16 #include "view_id.h"
17 #include "equipment.h"
18 #include "view_index.h"
19 #include "minion_equipment.h"
20 #include "task_map.h"
21 #include "collective_teams.h"
22 #include "known_tiles.h"
23 #include "construction_map.h"
24 #include "minion_task_map.h"
25 #include "tribe.h"
26 #include "collective_config.h"
27 #include "creature_name.h"
28 #include "cost_info.h"
29 #include "monster_ai.h"
30 #include "task.h"
31 #include "territory.h"
32 #include "collective_attack.h"
33 #include "gender.h"
34 #include "collective_name.h"
35 #include "creature_attributes.h"
36 #include "villain_type.h"
37 #include "workshops.h"
38 #include "attack_trigger.h"
39 #include "spell_map.h"
40 #include "body.h"
41 #include "furniture.h"
42 #include "furniture_factory.h"
43 #include "tile_efficiency.h"
44 #include "zones.h"
45 #include "experience_type.h"
46 #include "furniture_usage.h"
47 #include "collective_warning.h"
48 #include "immigration.h"
49 #include "trap_type.h"
50 #include "creature_factory.h"
51 #include "resource_info.h"
52 #include "workshop_item.h"
53 
54 
55 template <class Archive>
serialize(Archive & ar,const unsigned int version)56 void Collective::serialize(Archive& ar, const unsigned int version) {
57   ar(SUBCLASS(TaskCallback), SUBCLASS(UniqueEntity<Collective>), SUBCLASS(EventListener));
58   ar(creatures, leader, taskMap, tribe, control, byTrait, bySpawnType);
59   ar(territory, alarmInfo, markedItems, constructions, minionEquipment);
60   ar(surrendering, delayedPos, knownTiles, technologies, kills, points, currentTasks);
61   ar(credit, level, immigration, teams, name, conqueredVillains);
62   ar(config, warnings, knownVillains, knownVillainLocations, banished);
63   ar(villainType, enemyId, workshops, zones, tileEfficiency, discoverable);
64 }
65 
66 SERIALIZABLE(Collective)
67 
SERIALIZATION_CONSTRUCTOR_IMPL(Collective)68 SERIALIZATION_CONSTRUCTOR_IMPL(Collective)
69 
70 Collective::Collective(Private, WLevel l, TribeId t, const optional<CollectiveName>& n)
71     : tribe(t), level(NOTNULL(l)), name(n), villainType(VillainType::NONE) {
72 }
73 
create(WLevel level,TribeId tribe,const optional<CollectiveName> & name,bool discoverable)74 PCollective Collective::create(WLevel level, TribeId tribe, const optional<CollectiveName>& name, bool discoverable) {
75   auto ret = makeOwner<Collective>(Private {}, level, tribe, name);
76   ret->subscribeTo(level->getModel());
77   if (discoverable)
78     ret->setDiscoverable();
79   return ret;
80 }
81 
init(CollectiveConfig && cfg,Immigration && im)82 void Collective::init(CollectiveConfig&& cfg, Immigration&& im) {
83   config.reset(std::move(cfg));
84   immigration = makeOwner<Immigration>(std::move(im));
85   credit = cfg.getStartingResource();
86   workshops = config->getWorkshops();
87 }
88 
acquireInitialTech()89 void Collective::acquireInitialTech() {
90   for (auto tech : config->getInitialTech())
91     acquireTech(tech);
92 }
93 
getName() const94 const optional<CollectiveName>& Collective::getName() const {
95   return *name;
96 }
97 
setVillainType(VillainType t)98 void Collective::setVillainType(VillainType t) {
99   villainType = t;
100 }
101 
isDiscoverable() const102 bool Collective::isDiscoverable() const {
103   return discoverable;
104 }
105 
setDiscoverable()106 void Collective::setDiscoverable() {
107   discoverable = true;
108 }
109 
setEnemyId(EnemyId id)110 void Collective::setEnemyId(EnemyId id) {
111   enemyId = id;
112 }
113 
getVillainType() const114 VillainType Collective::getVillainType() const {
115   return villainType;
116 }
117 
getEnemyId() const118 optional<EnemyId> Collective::getEnemyId() const {
119   return enemyId;
120 }
121 
~Collective()122 Collective::~Collective() {
123 }
124 
addCreatureInTerritory(PCreature creature,EnumSet<MinionTrait> traits)125 void Collective::addCreatureInTerritory(PCreature creature, EnumSet<MinionTrait> traits) {
126   for (Position pos : Random.permutation(territory->getAll()))
127     if (pos.canEnter(creature.get())) {
128       addCreature(std::move(creature), pos, traits);
129       return;
130     }
131 }
132 
addCreature(PCreature creature,Position pos,EnumSet<MinionTrait> traits)133 void Collective::addCreature(PCreature creature, Position pos, EnumSet<MinionTrait> traits) {
134   if (config->getStripSpawns())
135     creature->getEquipment().removeAllItems(creature.get());
136   WCreature c = creature.get();
137   pos.addCreature(std::move(creature));
138   addCreature(c, traits);
139 }
140 
addCreature(WCreature c,EnumSet<MinionTrait> traits)141 void Collective::addCreature(WCreature c, EnumSet<MinionTrait> traits) {
142   if (!traits.contains(MinionTrait::FARM_ANIMAL) && !c->getController()->isCustomController())
143     c->setController(makeOwner<Monster>(c, MonsterAIFactory::collective(this)));
144   if (traits.contains(MinionTrait::LEADER))
145     leader = c;
146   if (c->getTribeId() != *tribe)
147     c->setTribe(*tribe);
148   if (WGame game = getGame())
149     for (WCollective col : getGame()->getCollectives())
150       if (col->getCreatures().contains(c))
151         col->removeCreature(c);
152   creatures.push_back(c);
153   for (MinionTrait t : traits)
154     byTrait[t].push_back(c);
155   if (auto spawnType = c->getAttributes().getSpawnType())
156     bySpawnType[*spawnType].push_back(c);
157   for (WItem item : c->getEquipment().getItems())
158     CHECK(minionEquipment->tryToOwn(c, item));
159   control->onMemberAdded(c);
160 }
161 
removeCreature(WCreature c)162 void Collective::removeCreature(WCreature c) {
163   creatures.removeElement(c);
164   returnResource(taskMap->freeFromTask(c));
165   if (auto spawnType = c->getAttributes().getSpawnType())
166     bySpawnType[*spawnType].removeElement(c);
167   for (auto team : teams->getContaining(c))
168     teams->remove(team, c);
169   for (MinionTrait t : ENUM_ALL(MinionTrait))
170     if (byTrait[t].contains(c))
171       byTrait[t].removeElement(c);
172 }
173 
banishCreature(WCreature c)174 void Collective::banishCreature(WCreature c) {
175   decreaseMoraleForBanishing(c);
176   removeCreature(c);
177   vector<Position> exitTiles = territory->getExtended(10, 20);
178   vector<PTask> tasks;
179   vector<WItem> items = c->getEquipment().getItems();
180   if (!items.empty())
181     tasks.push_back(Task::dropItems(items));
182   if (!exitTiles.empty())
183     tasks.push_back(Task::goToTryForever(Random.choose(exitTiles)));
184   tasks.push_back(Task::disappear());
185   c->setController(makeOwner<Monster>(c, MonsterAIFactory::singleTask(Task::chain(std::move(tasks)))));
186   banished.insert(c);
187 }
188 
wasBanished(WConstCreature c) const189 bool Collective::wasBanished(WConstCreature c) const {
190   return banished.contains(c);
191 }
192 
193 /*vector<WCreature> Collective::getRecruits() const {
194   vector<WCreature> ret;
195   vector<WCreature> possibleRecruits = filter(getCreatures(MinionTrait::FIGHTER),
196       [] (WConstCreature c) { return c->getAttributes().getRecruitmentCost() > 0; });
197   if (auto minPop = config->getRecruitingMinPopulation())
198     for (int i = *minPop; i < possibleRecruits.size(); ++i)
199       ret.push_back(possibleRecruits[i]);
200   return ret;
201 }*/
202 
hasTradeItems() const203 bool Collective::hasTradeItems() const {
204   for (Position pos : territory->getAll())
205     if (!pos.getItems(ItemIndex::FOR_SALE).empty())
206       return true;
207   return false;
208 }
209 
210 //kocham Cię
211 
getTradeItems() const212 vector<WItem> Collective::getTradeItems() const {
213   vector<WItem> ret;
214   for (Position pos : territory->getAll())
215     append(ret, pos.getItems(ItemIndex::FOR_SALE));
216   return ret;
217 }
218 
buyItem(WItem item)219 PItem Collective::buyItem(WItem item) {
220   for (Position pos : territory->getAll())
221     for (WItem it : pos.getItems(ItemIndex::FOR_SALE))
222       if (it == item) {
223         PItem ret = pos.removeItem(it);
224         ret->setShopkeeper(nullptr);
225         return ret;
226       }
227   FATAL << "Couldn't find item";
228   return nullptr;
229 }
230 
getTriggers(WConstCollective against) const231 vector<TriggerInfo> Collective::getTriggers(WConstCollective against) const {
232   return control->getTriggers(against);
233 }
234 
getLeader() const235 WConstCreature Collective::getLeader() const {
236   return leader;
237 }
238 
getLeader()239 WCreature Collective::getLeader() {
240   return leader;
241 }
242 
hasLeader() const243 bool Collective::hasLeader() const {
244   return leader && !leader->isDead();
245 }
246 
getLevel() const247 WLevel Collective::getLevel() const {
248   return level;
249 }
250 
getGame() const251 WGame Collective::getGame() const {
252   return level->getModel()->getGame();
253 }
254 
getControl() const255 WCollectiveControl Collective::getControl() const {
256   return control.get();
257 }
258 
getTribeId() const259 TribeId Collective::getTribeId() const {
260   return *tribe;
261 }
262 
getTribe() const263 Tribe* Collective::getTribe() const {
264   return getGame()->getTribe(*tribe);
265 }
266 
getModel() const267 WModel Collective::getModel() const {
268   return getLevel()->getModel();
269 }
270 
getCreatures() const271 const vector<WCreature>& Collective::getCreatures() const {
272   return creatures;
273 }
274 
setMinionTask(WConstCreature c,MinionTask task)275 void Collective::setMinionTask(WConstCreature c, MinionTask task) {
276   if (auto duration = MinionTasks::getDuration(c, task))
277     currentTasks.set(c, {task, c->getLocalTime() + *duration});
278   else
279     currentTasks.set(c, {task, none});
280 }
281 
getMinionTask(WConstCreature c) const282 optional<MinionTask> Collective::getMinionTask(WConstCreature c) const {
283   if (auto current = currentTasks.getMaybe(c))
284     return current->task;
285   else
286     return none;
287 }
288 
isTaskGood(WConstCreature c,MinionTask task,bool ignoreTaskLock) const289 bool Collective::isTaskGood(WConstCreature c, MinionTask task, bool ignoreTaskLock) const {
290   if (!c->getAttributes().getMinionTasks().isAvailable(this, c, task, ignoreTaskLock))
291     return false;
292   switch (task) {
293     case MinionTask::BE_WHIPPED:
294       return c->getMorale() < 0.95;
295     case MinionTask::CROPS:
296     case MinionTask::EXPLORE:
297       return getGame()->getSunlightInfo().getState() == SunlightState::DAY;
298     case MinionTask::SLEEP:
299       if (!config->hasVillainSleepingTask())
300         return true;
301       FALLTHROUGH;
302     case MinionTask::EXPLORE_NOCTURNAL:
303       return getGame()->getSunlightInfo().getState() == SunlightState::NIGHT;
304     default: return true;
305   }
306 }
307 
setRandomTask(WConstCreature c)308 void Collective::setRandomTask(WConstCreature c) {
309   vector<MinionTask> goodTasks;
310   for (MinionTask t : ENUM_ALL(MinionTask))
311     if (isTaskGood(c, t) && c->getAttributes().getMinionTasks().canChooseRandomly(c, t))
312       goodTasks.push_back(t);
313   if (!goodTasks.empty())
314     setMinionTask(c, Random.choose(goodTasks));
315 }
316 
isMinionTaskPossible(WCreature c,MinionTask task)317 bool Collective::isMinionTaskPossible(WCreature c, MinionTask task) {
318   return isTaskGood(c, task, true) && (MinionTasks::generate(this, c, task) || MinionTasks::getExisting(this, c, task));
319 }
320 
getStandardTask(WCreature c)321 WTask Collective::getStandardTask(WCreature c) {
322   auto current = currentTasks.getMaybe(c);
323   if (!current || (current->finishTime && *current->finishTime < c->getLocalTime()) || !isTaskGood(c, current->task)) {
324     currentTasks.erase(c);
325     setRandomTask(c);
326   }
327   if (auto current = currentTasks.getMaybe(c)) {
328     MinionTask task = current->task;
329     auto& info = config->getTaskInfo(task);
330     if (!current->finishTime) // see comment in header
331       currentTasks.getOrFail(c).finishTime = -1000;
332     if (info.warning && !territory->isEmpty())
333       warnings->setWarning(*info.warning, false);
334     if (PTask ret = MinionTasks::generate(this, c, task))
335       if (ret->getMove(c))
336         return taskMap->addTaskFor(std::move(ret), c);
337     if (WTask ret = MinionTasks::getExisting(this, c, task))
338       if (ret->getMove(c)) {
339         taskMap->takeTask(c, ret);
340         return ret;
341       }
342     if (info.warning && !territory->isEmpty())
343       warnings->setWarning(*info.warning, true);
344     currentTasks.erase(c);
345   }
346   return nullptr;
347 }
348 
isConquered() const349 bool Collective::isConquered() const {
350   return getCreatures(MinionTrait::FIGHTER).empty() && !hasLeader();
351 }
352 
getConsumptionTargets(WCreature consumer) const353 vector<WCreature> Collective::getConsumptionTargets(WCreature consumer) const {
354   vector<WCreature> ret;
355   for (WCreature c : getCreatures(MinionTrait::FIGHTER))
356     if (consumer->canConsume(c) && c != getLeader())
357       ret.push_back(c);
358   return ret;
359 }
360 
orderConsumption(WCreature consumer,WCreature who)361 void Collective::orderConsumption(WCreature consumer, WCreature who) {
362   CHECK(getConsumptionTargets(consumer).contains(who));
363   setTask(consumer, Task::consume(this, who));
364 }
365 
getEquipmentTask(WCreature c)366 PTask Collective::getEquipmentTask(WCreature c) {
367   if (!usesEquipment(c))
368     return nullptr;
369   if (!hasTrait(c, MinionTrait::NO_AUTO_EQUIPMENT) && Random.roll(40))
370     minionEquipment->autoAssign(c, getAllItems(ItemIndex::MINION_EQUIPMENT, false));
371   vector<PTask> tasks;
372   for (WItem it : c->getEquipment().getItems())
373     if (!c->getEquipment().isEquipped(it) && c->getEquipment().canEquip(it))
374       tasks.push_back(Task::equipItem(it));
375   for (Position v : zones->getPositions(ZoneId::STORAGE_EQUIPMENT)) {
376     vector<WItem> allItems = v.getItems(ItemIndex::MINION_EQUIPMENT).filter(
377         [this, c] (WConstItem it) { return minionEquipment->isOwner(it, c);});
378     vector<WItem> consumables;
379     for (auto item : allItems)
380       if (item->canEquip())
381         tasks.push_back(Task::pickAndEquipItem(this, v, item));
382       else
383         consumables.push_back(item);
384     if (!consumables.empty())
385       tasks.push_back(Task::pickItem(this, v, consumables));
386   }
387   if (!tasks.empty())
388     return Task::chain(std::move(tasks));
389   return nullptr;
390 }
391 
392 const static EnumSet<MinionTask> healingTasks {MinionTask::SLEEP, MinionTask::GRAVE, MinionTask::LAIR};
393 
considerHealingTask(WCreature c)394 void Collective::considerHealingTask(WCreature c) {
395   if (c->getBody().canHeal() && !c->isAffected(LastingEffect::POISON))
396     for (MinionTask t : healingTasks) {
397       auto currentTask = getMinionTask(c);
398       if (c->getAttributes().getMinionTasks().isAvailable(this, c, t) &&
399           (!currentTask || !healingTasks.contains(*currentTask))) {
400         cancelTask(c);
401         setMinionTask(c, t);
402         return;
403       }
404     }
405 }
406 
clearLeader()407 void Collective::clearLeader() {
408   leader = nullptr;
409 }
410 
setTask(WCreature c,PTask task)411 void Collective::setTask(WCreature c, PTask task) {
412   returnResource(taskMap->freeFromTask(c));
413   taskMap->addTaskFor(std::move(task), c);
414 }
415 
hasTask(WConstCreature c) const416 bool Collective::hasTask(WConstCreature c) const {
417   return taskMap->hasTask(c);
418 }
419 
cancelTask(WConstCreature c)420 void Collective::cancelTask(WConstCreature c) {
421   if (WTask task = taskMap->getTask(c))
422     taskMap->removeTask(task);
423 }
424 
getFirstGoodMove()425 static MoveInfo getFirstGoodMove() {
426   return NoMove;
427 }
428 
429 template <typename MoveFun1, typename... MoveFuns>
getFirstGoodMove(MoveFun1 && f1,MoveFuns &&...funs)430 MoveInfo getFirstGoodMove(MoveFun1&& f1, MoveFuns&&... funs) {
431   if (auto move = std::forward<MoveFun1>(f1)())
432     return move;
433   else
434     return getFirstGoodMove(std::forward<MoveFuns>(funs)...);
435 }
436 
getMove(WCreature c)437 MoveInfo Collective::getMove(WCreature c) {
438   CHECK(control);
439   CHECK(creatures.contains(c));
440   CHECK(!c->isDead());
441 
442   auto waitIfNoMove = [&] (MoveInfo move) -> MoveInfo {
443     if (!move)
444       return c->wait();
445     else
446       return move;
447   };
448 
449   auto priorityTask = [&] {
450     if (WTask task = taskMap->getTask(c))
451       if (taskMap->isPriorityTask(task))
452         return waitIfNoMove(task->getMove(c));
453     return NoMove;
454   };
455 
456   auto followTeamLeader = [&] () -> MoveInfo {
457     for (auto team : teams->getContaining(c))
458       if (teams->isActive(team)) {
459         WConstCreature leader = teams->getLeader(team);
460         if (c != leader) {
461           if (leader->getPosition().dist8(c->getPosition()) > 1)
462             return c->moveTowards(leader->getPosition());
463           else
464             return c->wait();
465         } else
466           if (c == leader && !teams->isPersistent(team))
467             return c->wait();
468       }
469     return NoMove;
470   };
471 
472   auto dropLoot = [&] () -> MoveInfo {
473     if (config->getFetchItems() && territory->contains(c->getPosition())) {
474       vector<WItem> items = c->getEquipment().getItems([this, c](WConstItem item) {
475           return !isItemMarked(item) && !minionEquipment->isOwner(item, c); });
476       if (!items.empty())
477         return c->drop(items);
478     }
479     return NoMove;
480   };
481 
482   auto goToAlarm = [&] () -> MoveInfo {
483     if (hasTrait(c, MinionTrait::FIGHTER) && alarmInfo && alarmInfo->finishTime > getGlobalTime())
484       if (auto action = c->moveTowards(alarmInfo->position))
485         return {1.0, action};
486     return NoMove;
487   };
488 
489   auto normalTask = [&] () -> MoveInfo {
490     if (WTask task = taskMap->getTask(c))
491       return waitIfNoMove(task->getMove(c));
492     return NoMove;
493   };
494 
495   auto newEquipmentTask = [&] {
496     if (PTask t = getEquipmentTask(c))
497       if (auto move = t->getMove(c)) {
498         taskMap->addTaskFor(std::move(t), c);
499         return move;
500       }
501     return NoMove;
502   };
503 
504   auto newStandardTask = [&] {
505     if (WTask t = getStandardTask(c))
506       return waitIfNoMove(t->getMove(c));
507     return NoMove;
508   };
509 
510   auto followLeader = [&] () -> MoveInfo {
511     if (config->getFollowLeaderIfNoTerritory() && hasLeader() && territory->isEmpty()) {
512       Position leaderPos = getLeader()->getPosition();
513       if (leaderPos.dist8(c->getPosition()) < 3)
514         return NoMove;
515       if (auto action = c->moveTowards(leaderPos))
516         return {1.0, action};
517     }
518     return NoMove;
519   };
520 
521   auto returnToBase = [&] () -> MoveInfo {
522     if (!hasTrait(c, MinionTrait::NO_RETURNING) && !territory->isEmpty() &&
523         !territory->contains(c->getPosition()) && teams->getActive(c).empty()) {
524       if (c->getPosition().getModel() == getModel())
525         return c->moveTowards(Random.choose(territory->getAll()));
526       else
527         if (PTask t = Task::transferTo(getModel()))
528           return taskMap->addTaskFor(std::move(t), c)->getMove(c);
529     }
530     return NoMove;
531   };
532 
533   considerHealingTask(c);
534   return getFirstGoodMove(
535       priorityTask,
536       followTeamLeader,
537       dropLoot,
538       goToAlarm,
539       normalTask,
540       newEquipmentTask,
541       newStandardTask,
542       followLeader,
543       returnToBase
544   );
545 }
546 
setControl(PCollectiveControl c)547 void Collective::setControl(PCollectiveControl c) {
548   control = std::move(c);
549 }
550 
getEnemyPositions() const551 vector<Position> Collective::getEnemyPositions() const {
552   vector<Position> enemyPos;
553   for (Position pos : territory->getExtended(10))
554     if (WConstCreature c = pos.getCreature())
555       if (getTribe()->isEnemy(c))
556         enemyPos.push_back(pos);
557   return enemyPos;
558 }
559 
addNewCreatureMessage(const vector<WCreature> & immigrants)560 void Collective::addNewCreatureMessage(const vector<WCreature>& immigrants) {
561   if (immigrants.size() == 1)
562     control->addMessage(PlayerMessage(immigrants[0]->getName().a() + " joins your forces.")
563         .setCreature(immigrants[0]->getUniqueId()));
564   else {
565     control->addMessage(PlayerMessage("A " + immigrants[0]->getName().multiple(immigrants.size()) +
566           " joins your forces.").setCreature(immigrants[0]->getUniqueId()));
567   }
568 }
569 
decayMorale()570 void Collective::decayMorale() {
571   for (WCreature c : getCreatures(MinionTrait::FIGHTER))
572     c->addMorale(-c->getMorale() * 0.0008);
573 }
574 
update(bool currentlyActive)575 void Collective::update(bool currentlyActive) {
576   control->update(currentlyActive);
577   if (config->hasImmigrantion(currentlyActive) && hasLeader())
578     immigration->update();
579 }
580 
tick()581 void Collective::tick() {
582   dangerLevelCache = none;
583   control->tick();
584   zones->tick();
585   decayMorale();
586   if (config->getWarnings() && Random.roll(5))
587     warnings->considerWarnings(this);
588   if (config->getEnemyPositions() && Random.roll(5)) {
589     vector<Position> enemyPos = getEnemyPositions();
590     if (!enemyPos.empty())
591       delayDangerousTasks(enemyPos, getLocalTime() + 20);
592     else {
593       alarmInfo.reset();
594       control->onNoEnemies();
595     }
596     bool allSurrender = true;
597     vector<WCreature> surrenderingVec;
598     for (Position v : enemyPos)
599       if (!surrendering.contains(NOTNULL(v.getCreature()))) {
600         allSurrender = false;
601         break;
602       } else
603         surrenderingVec.push_back(v.getCreature());
604     if (allSurrender) {
605       for (WCreature c : surrenderingVec) {
606         if (!c->isDead() && territory->contains(c->getPosition())) {
607           Position pos = c->getPosition();
608           PCreature prisoner = CreatureFactory::fromId(CreatureId::PRISONER, getTribeId(),
609               MonsterAIFactory::collective(this));
610           if (pos.canEnterEmpty(prisoner.get())) {
611             pos.globalMessage(c->getName().the() + " surrenders.");
612             control->addMessage(PlayerMessage(c->getName().a() + " surrenders.").setPosition(c->getPosition()));
613             c->dieNoReason(Creature::DropType::ONLY_INVENTORY);
614             addCreature(std::move(prisoner), pos, {MinionTrait::PRISONER, MinionTrait::NO_LIMIT});
615           }
616         }
617         surrendering.erase(c);
618       }
619     }
620   }
621   if (config->getConstructions())
622     updateConstructions();
623   if (config->getFetchItems() && Random.roll(5))
624     for (const ItemFetchInfo& elem : CollectiveConfig::getFetchInfo()) {
625       for (Position pos : territory->getAll())
626         fetchItems(pos, elem);
627       for (Position pos : zones->getPositions(ZoneId::FETCH_ITEMS))
628         fetchItems(pos, elem);
629       for (Position pos : zones->getPositions(ZoneId::PERMANENT_FETCH_ITEMS))
630         fetchItems(pos, elem);
631     }
632   if (config->getManageEquipment() && Random.roll(40)) {
633     minionEquipment->updateOwners(getCreatures());
634     minionEquipment->updateItems(getAllItems(ItemIndex::MINION_EQUIPMENT, true));
635   }
636   workshops->scheduleItems(this);
637 }
638 
getCreatures(MinionTrait trait) const639 const vector<WCreature>& Collective::getCreatures(MinionTrait trait) const {
640   return byTrait[trait];
641 }
642 
getCreatures(SpawnType type) const643 const vector<WCreature>& Collective::getCreatures(SpawnType type) const {
644   return bySpawnType[type];
645 }
646 
hasTrait(WConstCreature c,MinionTrait t) const647 bool Collective::hasTrait(WConstCreature c, MinionTrait t) const {
648   return byTrait[t].contains(c);
649 }
650 
hasAnyTrait(WConstCreature c,EnumSet<MinionTrait> traits) const651 bool Collective::hasAnyTrait(WConstCreature c, EnumSet<MinionTrait> traits) const {
652   for (MinionTrait t : traits)
653     if (hasTrait(c, t))
654       return true;
655   return false;
656 }
657 
setTrait(WCreature c,MinionTrait t)658 void Collective::setTrait(WCreature c, MinionTrait t) {
659   if (!hasTrait(c, t))
660     byTrait[t].push_back(c);
661 }
662 
removeTrait(WCreature c,MinionTrait t)663 void Collective::removeTrait(WCreature c, MinionTrait t) {
664   byTrait[t].removeElementMaybe(c);
665 }
666 
getCreaturesAnyOf(EnumSet<MinionTrait> trait) const667 vector<WCreature> Collective::getCreaturesAnyOf(EnumSet<MinionTrait> trait) const {
668   EntitySet<Creature> added;
669   vector<WCreature> ret;
670   for (MinionTrait t : trait)
671     for (WCreature c : byTrait[t])
672       if (!added.contains(c)) {
673         ret.push_back(c);
674         added.insert(c);
675       }
676   return ret;
677 }
678 
getKillManaScore(WConstCreature victim) const679 double Collective::getKillManaScore(WConstCreature victim) const {
680   return 0;
681 /*  int ret = victim->getDifficultyPoints() / 3;
682   if (victim->isAffected(LastingEffect::SLEEP))
683     ret *= 2;
684   return ret;*/
685 }
686 
addMoraleForKill(WConstCreature killer,WConstCreature victim)687 void Collective::addMoraleForKill(WConstCreature killer, WConstCreature victim) {
688   for (WCreature c : getCreatures(MinionTrait::FIGHTER))
689     c->addMorale(c == killer ? 0.25 : 0.015);
690 }
691 
decreaseMoraleForKill(WConstCreature killer,WConstCreature victim)692 void Collective::decreaseMoraleForKill(WConstCreature killer, WConstCreature victim) {
693   for (WCreature c : getCreatures(MinionTrait::FIGHTER))
694     c->addMorale(victim == getLeader() ? -2 : -0.015);
695 }
696 
decreaseMoraleForBanishing(WConstCreature)697 void Collective::decreaseMoraleForBanishing(WConstCreature) {
698   for (WCreature c : getCreatures(MinionTrait::FIGHTER))
699     c->addMorale(-0.05);
700 }
701 
onKillCancelled(WCreature c)702 void Collective::onKillCancelled(WCreature c) {
703 }
704 
onEvent(const GameEvent & event)705 void Collective::onEvent(const GameEvent& event) {
706   using namespace EventInfo;
707   event.visit(
708       [&](const Alarm& info) {
709         static const int alarmTime = 100;
710         if (getTerritory().contains(info.pos)) {
711           control->addMessage(PlayerMessage("An alarm goes off.", MessagePriority::HIGH).setPosition(info.pos));
712           alarmInfo = AlarmInfo {getGlobalTime() + alarmTime, info.pos };
713           for (WCreature c : byTrait[MinionTrait::FIGHTER])
714             if (c->isAffected(LastingEffect::SLEEP))
715               c->removeEffect(LastingEffect::SLEEP);
716         }
717       },
718       [&](const CreatureKilled& info) {
719         if (creatures.contains(info.victim))
720           onMinionKilled(info.victim, info.attacker);
721         if (creatures.contains(info.attacker))
722           onKilledSomeone(info.attacker, info.victim);
723       },
724       [&](const CreatureTortured& info) {
725         if (creatures.contains(info.torturer))
726           returnResource({ResourceId::MANA, 1});
727       },
728       [&](const CreatureSurrendered& info) {
729         if (getCreatures().contains(info.attacker) && !getCreatures().contains(info.victim) &&
730             info.victim->getBody().isHumanoid())
731           surrendering.insert(info.victim);
732       },
733       [&](const TrapTriggered& info) {
734         if (auto& trap = constructions->getTrap(info.pos)) {
735           trap->reset();
736           if (trap->getType() == TrapType::SURPRISE)
737             handleSurprise(info.pos);
738         }
739       },
740       [&](const TrapDisarmed& info) {
741         if (auto& trap = constructions->getTrap(info.pos)) {
742           control->addMessage(PlayerMessage(info.creature->getName().a() +
743               " disarms a " + getTrapName(trap->getType()) + " trap.",
744               MessagePriority::HIGH).setPosition(info.pos));
745           trap->reset();
746         }
747       },
748       [&](const FurnitureDestroyed& info) {
749         constructions->onFurnitureDestroyed(info.position, info.layer);
750         tileEfficiency->update(info.position);
751       },
752       [&](const ConqueredEnemy& info) {
753         auto col = info.collective;
754         if (col->isDiscoverable())
755           if (auto enemyId = col->getEnemyId()) {
756             if (auto& name = col->getName())
757               control->addMessage(PlayerMessage("The tribe of " + name->full + " is destroyed.",
758                   MessagePriority::CRITICAL));
759             else
760               control->addMessage(PlayerMessage("An unnamed tribe is destroyed.", MessagePriority::CRITICAL));
761             if (!conqueredVillains.count(*enemyId)) {
762               auto mana = config->getManaForConquering(col->getVillainType());
763               addMana(mana);
764               control->addMessage(PlayerMessage("You feel a surge of power (+" + toString(mana) + " mana)",
765                   MessagePriority::CRITICAL));
766               conqueredVillains.insert(*enemyId);
767             } else
768               control->addMessage(PlayerMessage("Note: mana is only rewarded once per each kind of enemy.",
769                   MessagePriority::CRITICAL));
770           }
771       },
772       [&](const auto&) {}
773   );
774 }
775 
onPositionDiscovered(Position pos)776 void Collective::onPositionDiscovered(Position pos) {
777   control->onPositionDiscovered(pos);
778 }
779 
onMinionKilled(WCreature victim,WCreature killer)780 void Collective::onMinionKilled(WCreature victim, WCreature killer) {
781   control->onMemberKilled(victim, killer);
782   if (hasTrait(victim, MinionTrait::PRISONER) && killer && getCreatures().contains(killer))
783     returnResource({ResourceId::PRISONER_HEAD, 1});
784   if (!hasTrait(victim, MinionTrait::FARM_ANIMAL)) {
785     decreaseMoraleForKill(killer, victim);
786     if (killer)
787       control->addMessage(PlayerMessage(victim->getName().a() + " is killed by " + killer->getName().a(),
788             MessagePriority::HIGH).setPosition(victim->getPosition()));
789     else
790       control->addMessage(PlayerMessage(victim->getName().a() + " is killed.", MessagePriority::HIGH)
791           .setPosition(victim->getPosition()));
792   }
793   bool fighterKilled = hasTrait(victim, MinionTrait::FIGHTER) || victim == getLeader();
794   removeCreature(victim);
795   if (isConquered() && fighterKilled)
796     getGame()->addEvent(EventInfo::ConqueredEnemy{this});
797 }
798 
onKilledSomeone(WCreature killer,WCreature victim)799 void Collective::onKilledSomeone(WCreature killer, WCreature victim) {
800   if (victim->getTribe() != getTribe()) {
801     addMana(getKillManaScore(victim));
802     addMoraleForKill(killer, victim);
803     kills.insert(victim);
804     int difficulty = victim->getDifficultyPoints();
805     CHECK(difficulty >=0 && difficulty < 100000) << difficulty << " " << victim->getName().bare();
806     points += difficulty;
807     control->addMessage(PlayerMessage(victim->getName().a() + " is killed by " + killer->getName().a())
808         .setPosition(victim->getPosition()));
809   }
810 }
811 
getEfficiency(WConstCreature c) const812 double Collective::getEfficiency(WConstCreature c) const {
813   return pow(2.0, c->getMorale());
814 }
815 
getTerritory() const816 const Territory& Collective::getTerritory() const {
817   return *territory;
818 }
819 
getTerritory()820 Territory& Collective::getTerritory() {
821   return *territory;
822 }
823 
canClaimSquare(Position pos) const824 bool Collective::canClaimSquare(Position pos) const {
825   return getKnownTiles().isKnown(pos) &&
826       pos.isCovered() &&
827       pos.canEnter({MovementTrait::WALK}) &&
828       !pos.isWall();
829 }
830 
claimSquare(Position pos)831 void Collective::claimSquare(Position pos) {
832   //CHECK(canClaimSquare(pos));
833   territory->insert(pos);
834   for (auto furniture : pos.modFurniture())
835     if (!furniture->isWall()) {
836       if (!constructions->containsFurniture(pos, furniture->getLayer()))
837         constructions->addFurniture(pos, ConstructionMap::FurnitureInfo::getBuilt(furniture->getType()));
838       furniture->setTribe(getTribeId());
839     }
840   control->onClaimedSquare(pos);
841 }
842 
getKnownTiles() const843 const KnownTiles& Collective::getKnownTiles() const {
844   return *knownTiles;
845 }
846 
getTileEfficiency() const847 const TileEfficiency& Collective::getTileEfficiency() const {
848   return *tileEfficiency;
849 }
850 
getLocalTime() const851 double Collective::getLocalTime() const {
852   return getModel()->getLocalTime();
853 }
854 
getGlobalTime() const855 double Collective::getGlobalTime() const {
856   return getGame()->getGlobalTime();
857 }
858 
numResource(ResourceId id) const859 int Collective::numResource(ResourceId id) const {
860   int ret = credit[id];
861   if (auto itemIndex = config->getResourceInfo(id).itemIndex)
862     if (auto storageType = config->getResourceInfo(id).storageDestination)
863       for (Position pos : storageType(this))
864         ret += pos.getItems(*itemIndex).size();
865   return ret;
866 }
867 
numResourcePlusDebt(ResourceId id) const868 int Collective::numResourcePlusDebt(ResourceId id) const {
869   return numResource(id) - getDebt(id);
870 }
871 
getDebt(ResourceId id) const872 int Collective::getDebt(ResourceId id) const {
873   int ret = constructions->getDebt(id);
874   for (auto& elem : taskMap->getCompletionCosts())
875     if (elem.second.id == id && !taskMap->getTask(elem.first)->isDone())
876       ret -= elem.second.value;
877   ret += workshops->getDebt(id);
878   return ret;
879 }
880 
hasResource(const CostInfo & cost) const881 bool Collective::hasResource(const CostInfo& cost) const {
882   return numResource(cost.id) >= cost.value;
883 }
884 
takeResource(const CostInfo & cost)885 void Collective::takeResource(const CostInfo& cost) {
886   int num = cost.value;
887   if (num == 0)
888     return;
889   CHECK(num > 0);
890   if (credit[cost.id]) {
891     if (credit[cost.id] >= num) {
892       credit[cost.id] -= num;
893       return;
894     } else {
895       num -= credit[cost.id];
896       credit[cost.id] = 0;
897     }
898   }
899   if (auto itemIndex = config->getResourceInfo(cost.id).itemIndex)
900     if (auto storageType = config->getResourceInfo(cost.id).storageDestination)
901       for (Position pos : storageType(this)) {
902         vector<WItem> goldHere = pos.getItems(*itemIndex);
903         for (WItem it : goldHere) {
904           pos.removeItem(it);
905           if (--num == 0)
906             return;
907         }
908       }
909   FATAL << "Not enough " << config->getResourceInfo(cost.id).name << " missing " << num << " of " << cost.value;
910 }
911 
returnResource(const CostInfo & amount)912 void Collective::returnResource(const CostInfo& amount) {
913   if (amount.value == 0)
914     return;
915   CHECK(amount.value > 0);
916   if (auto storageType = config->getResourceInfo(amount.id).storageDestination) {
917     const set<Position>& destination = storageType(this);
918     if (!destination.empty()) {
919       Random.choose(destination).dropItems(config->getResourceInfo(amount.id).itemId.get(amount.value));
920       return;
921     }
922   }
923   credit[amount.id] += amount.value;
924 }
925 
getTrapItems(TrapType type,const vector<Position> & squares) const926 vector<pair<WItem, Position>> Collective::getTrapItems(TrapType type, const vector<Position>& squares) const {
927   vector<pair<WItem, Position>> ret;
928   for (Position pos : squares)
929     for (auto it : pos.getItems(ItemIndex::TRAP))
930       if (it->getTrapType() == type && !isItemMarked(it))
931         ret.emplace_back(it, pos);
932   return ret;
933 }
934 
usesEquipment(WConstCreature c) const935 bool Collective::usesEquipment(WConstCreature c) const {
936   return config->getManageEquipment()
937     && c->getBody().isHumanoid() && !hasTrait(c, MinionTrait::NO_EQUIPMENT)
938     && !hasTrait(c, MinionTrait::PRISONER);
939 }
940 
getAllItems(bool includeMinions) const941 vector<WItem> Collective::getAllItems(bool includeMinions) const {
942   vector<WItem> allItems;
943   for (Position v : territory->getAll())
944     append(allItems, v.getItems());
945   if (includeMinions)
946     for (WCreature c : getCreatures())
947       append(allItems, c->getEquipment().getItems());
948   return allItems;
949 }
950 
getAllItems(ItemPredicate predicate,bool includeMinions) const951 vector<WItem> Collective::getAllItems(ItemPredicate predicate, bool includeMinions) const {
952   vector<WItem> allItems;
953   for (Position v : territory->getAll())
954     append(allItems, v.getItems(predicate));
955   if (includeMinions)
956     for (WCreature c : getCreatures())
957       append(allItems, c->getEquipment().getItems(predicate));
958   return allItems;
959 }
960 
getAllItems(ItemIndex index,bool includeMinions) const961 vector<WItem> Collective::getAllItems(ItemIndex index, bool includeMinions) const {
962   vector<WItem> allItems;
963   for (Position v : territory->getAll())
964     append(allItems, v.getItems(index));
965   if (includeMinions)
966     for (WCreature c : getCreatures())
967       append(allItems, c->getEquipment().getItems(index));
968   return allItems;
969 }
970 
canPillage() const971 bool Collective::canPillage() const {
972   for (auto pos : territory->getAll())
973     if (!pos.getItems().empty())
974       return true;
975   return false;
976 }
977 
getNumItems(ItemIndex index,bool includeMinions) const978 int Collective::getNumItems(ItemIndex index, bool includeMinions) const {
979   int ret = 0;
980   for (Position v : territory->getAll())
981     ret += v.getItems(index).size();
982   if (includeMinions)
983     for (WCreature c : getCreatures())
984       ret += c->getEquipment().getItems(index).size();
985   return ret;
986 }
987 
getStorageFor(WConstItem item) const988 optional<set<Position>> Collective::getStorageFor(WConstItem item) const {
989   for (auto& info : config->getFetchInfo())
990     if (getIndexPredicate(info.index)(item))
991       return info.destinationFun(this);
992   return none;
993 }
994 
addKnownVillain(WConstCollective col)995 void Collective::addKnownVillain(WConstCollective col) {
996   knownVillains.insert(col);
997 }
998 
isKnownVillain(WConstCollective col) const999 bool Collective::isKnownVillain(WConstCollective col) const {
1000   return (getModel() != col->getModel() && col->getVillainType() != VillainType::NONE) || knownVillains.contains(col);
1001 }
1002 
addKnownVillainLocation(WConstCollective col)1003 void Collective::addKnownVillainLocation(WConstCollective col) {
1004   knownVillainLocations.insert(col);
1005 }
1006 
isKnownVillainLocation(WConstCollective col) const1007 bool Collective::isKnownVillainLocation(WConstCollective col) const {
1008   return knownVillainLocations.contains(col);
1009 }
1010 
isItemMarked(WConstItem it) const1011 bool Collective::isItemMarked(WConstItem it) const {
1012   return !!markedItems.getOrElse(it, nullptr);
1013 }
1014 
markItem(WConstItem it,WConstTask task)1015 void Collective::markItem(WConstItem it, WConstTask task) {
1016   markedItems.set(it, task);
1017 }
1018 
removeTrap(Position pos)1019 void Collective::removeTrap(Position pos) {
1020   constructions->removeTrap(pos);
1021 }
1022 
canAddFurniture(Position position,FurnitureType type) const1023 bool Collective::canAddFurniture(Position position, FurnitureType type) const {
1024   return knownTiles->isKnown(position)
1025       && (territory->contains(position) ||
1026           canClaimSquare(position) ||
1027           CollectiveConfig::canBuildOutsideTerritory(type))
1028       && !getConstructions().getTrap(position)
1029       && !getConstructions().containsFurniture(position, Furniture::getLayer(type))
1030       && position.canConstruct(type);
1031 }
1032 
removeFurniture(Position pos,FurnitureLayer layer)1033 void Collective::removeFurniture(Position pos, FurnitureLayer layer) {
1034   auto f = constructions->getFurniture(pos, layer);
1035   if (f->hasTask())
1036     returnResource(taskMap->removeTask(f->getTask()));
1037   constructions->removeFurniture(pos, layer);
1038 }
1039 
destroySquare(Position pos,FurnitureLayer layer)1040 void Collective::destroySquare(Position pos, FurnitureLayer layer) {
1041   if (constructions->containsFurniture(pos, layer)) {
1042     if (auto furniture = pos.modFurniture(layer))
1043       if (furniture->getTribe() == getTribeId()) {
1044         furniture->destroy(pos, DestroyAction::Type::BASH);
1045         tileEfficiency->update(pos);
1046       }
1047     removeFurniture(pos, layer);
1048   }
1049   if (layer != FurnitureLayer::FLOOR) {
1050     zones->eraseZones(pos);
1051     if (constructions->getTrap(pos))
1052       removeTrap(pos);
1053   }
1054 }
1055 
addFurniture(Position pos,FurnitureType type,const CostInfo & cost,bool noCredit)1056 void Collective::addFurniture(Position pos, FurnitureType type, const CostInfo& cost, bool noCredit) {
1057   /*if (type == FurnitureType::MOUNTAIN && (pos.isChokePoint({MovementTrait::WALK}) ||
1058         constructions->getTotalCount(type) - constructions->getBuiltCount(type) > 0))
1059     return;*/
1060   if (!noCredit || hasResource(cost)) {
1061     constructions->addFurniture(pos, ConstructionMap::FurnitureInfo(type, cost));
1062     updateConstructions();
1063     if (canClaimSquare(pos))
1064       claimSquare(pos);
1065   }
1066 }
1067 
getConstructions() const1068 const ConstructionMap& Collective::getConstructions() const {
1069   return *constructions;
1070 }
1071 
cancelMarkedTask(Position pos)1072 void Collective::cancelMarkedTask(Position pos) {
1073   taskMap->removeTask(taskMap->getMarked(pos));
1074 }
1075 
isMarked(Position pos) const1076 bool Collective::isMarked(Position pos) const {
1077   return !!taskMap->getMarked(pos);
1078 }
1079 
getMarkHighlight(Position pos) const1080 HighlightType Collective::getMarkHighlight(Position pos) const {
1081   return taskMap->getHighlightType(pos);
1082 }
1083 
setPriorityTasks(Position pos)1084 void Collective::setPriorityTasks(Position pos) {
1085   taskMap->setPriorityTasks(pos);
1086 }
1087 
hasPriorityTasks(Position pos) const1088 bool Collective::hasPriorityTasks(Position pos) const {
1089   return taskMap->hasPriorityTasks(pos);
1090 }
1091 
getHighlight(const DestroyAction & action)1092 static HighlightType getHighlight(const DestroyAction& action) {
1093   switch (action.getType()) {
1094     case DestroyAction::Type::CUT:
1095       return HighlightType::CUT_TREE;
1096     default:
1097       return HighlightType::DIG;
1098   }
1099 }
1100 
orderDestruction(Position pos,const DestroyAction & action)1101 void Collective::orderDestruction(Position pos, const DestroyAction& action) {
1102   auto f = NOTNULL(pos.getFurniture(FurnitureLayer::MIDDLE));
1103   CHECK(f->canDestroy(action));
1104   taskMap->markSquare(pos, getHighlight(action), Task::destruction(this, pos, f, action));
1105 }
1106 
addTrap(Position pos,TrapType type)1107 void Collective::addTrap(Position pos, TrapType type) {
1108   constructions->addTrap(pos, ConstructionMap::TrapInfo(type));
1109   updateConstructions();
1110 }
1111 
onAppliedItem(Position pos,WItem item)1112 void Collective::onAppliedItem(Position pos, WItem item) {
1113   CHECK(item->getTrapType());
1114   if (auto& trap = constructions->getTrap(pos))
1115     trap->setArmed();
1116 }
1117 
onAppliedItemCancel(Position pos)1118 void Collective::onAppliedItemCancel(Position pos) {
1119   if (auto& trap = constructions->getTrap(pos))
1120     trap->reset();
1121 }
1122 
isConstructionReachable(Position pos)1123 bool Collective::isConstructionReachable(Position pos) {
1124   for (Position v : pos.neighbors8())
1125     if (knownTiles->isKnown(v))
1126       return true;
1127   return false;
1128 }
1129 
onConstructed(Position pos,FurnitureType type)1130 void Collective::onConstructed(Position pos, FurnitureType type) {
1131   tileEfficiency->update(pos);
1132   if (pos.getFurniture(type)->isWall()) {
1133     constructions->removeFurniture(pos, Furniture::getLayer(type));
1134     if (territory->contains(pos))
1135       territory->remove(pos);
1136     return;
1137   }
1138   constructions->onConstructed(pos, type);
1139   control->onConstructed(pos, type);
1140   if (WTask task = taskMap->getMarked(pos))
1141     taskMap->removeTask(task);
1142 }
1143 
onDestructed(Position pos,FurnitureType type,const DestroyAction & action)1144 void Collective::onDestructed(Position pos, FurnitureType type, const DestroyAction& action) {
1145   tileEfficiency->update(pos);
1146   switch (action.getType()) {
1147     case DestroyAction::Type::CUT:
1148       zones->setZone(pos, ZoneId::FETCH_ITEMS);
1149       break;
1150     case DestroyAction::Type::DIG:
1151       territory->insert(pos);
1152       break;
1153     default:
1154       break;
1155   }
1156   control->onDestructed(pos, type, action);
1157 }
1158 
handleTrapPlacementAndProduction()1159 void Collective::handleTrapPlacementAndProduction() {
1160   EnumMap<TrapType, vector<pair<WItem, Position>>> trapItems(
1161       [this] (TrapType type) { return getTrapItems(type, territory->getAll());});
1162   EnumMap<TrapType, int> missingTraps;
1163   for (auto trapPos : constructions->getAllTraps()) {
1164     auto& trap = *constructions->getTrap(trapPos);
1165     if (!trap.isArmed() && !trap.isMarked() && !isDelayed(trapPos)) {
1166       vector<pair<WItem, Position>>& items = trapItems[trap.getType()];
1167       if (!items.empty()) {
1168         Position pos = items.back().second;
1169         auto task = taskMap->addTask(Task::applyItem(this, pos, items.back().first, trapPos), pos);
1170         markItem(items.back().first, task);
1171         items.pop_back();
1172         trap.setMarked();
1173       } else
1174         ++missingTraps[trap.getType()];
1175     }
1176   }
1177   for (TrapType type : ENUM_ALL(TrapType))
1178     scheduleAutoProduction([type](WConstItem it) { return it->getTrapType() == type;}, missingTraps[type]);
1179 }
1180 
scheduleAutoProduction(function<bool (WConstItem)> itemPredicate,int count)1181 void Collective::scheduleAutoProduction(function<bool(WConstItem)> itemPredicate, int count) {
1182   if (count > 0)
1183     for (auto workshopType : ENUM_ALL(WorkshopType))
1184       for (auto& item : workshops->get(workshopType).getQueued())
1185         if (itemPredicate(item.type.get().get()))
1186           count -= item.number * item.batchSize;
1187   if (count > 0)
1188     for (auto workshopType : ENUM_ALL(WorkshopType)) {
1189       //Don't use alchemy to get resources automatically as it is expensive
1190       if (workshopType == WorkshopType::LABORATORY)
1191         continue;
1192       auto& options = workshops->get(workshopType).getOptions();
1193       for (int index : All(options))
1194         if (itemPredicate(options[index].type.get().get())) {
1195           workshops->get(workshopType).queue(index, (count + options[index].batchSize - 1) / options[index].batchSize);
1196           return;
1197         }
1198     }
1199 }
1200 
updateResourceProduction()1201 void Collective::updateResourceProduction() {
1202   for (ResourceId resourceId : ENUM_ALL(ResourceId))
1203     if (auto index = config->getResourceInfo(resourceId).itemIndex) {
1204       int needed = getDebt(resourceId) - getNumItems(*index);
1205       if (needed > 0)
1206         scheduleAutoProduction([resourceId] (WConstItem it) { return it->getResourceId() == resourceId; }, needed);
1207   }
1208 }
1209 
updateConstructions()1210 void Collective::updateConstructions() {
1211   handleTrapPlacementAndProduction();
1212   for (auto& pos : constructions->getAllFurniture()) {
1213     auto& construction = *constructions->getFurniture(pos.first, pos.second);
1214     if (!isDelayed(pos.first) &&
1215         !construction.hasTask() &&
1216         !construction.isBuilt() &&
1217         hasResource(construction.getCost())) {
1218       constructions->setTask(pos.first, pos.second,
1219           taskMap->addTaskCost(Task::construction(this, pos.first, construction.getFurnitureType()), pos.first,
1220           construction.getCost())->getUniqueId());
1221       takeResource(construction.getCost());
1222     }
1223   }
1224 }
1225 
delayDangerousTasks(const vector<Position> & enemyPos1,double delayTime)1226 void Collective::delayDangerousTasks(const vector<Position>& enemyPos1, double delayTime) {
1227   vector<Vec2> enemyPos = enemyPos1
1228       .filter([=] (const Position& p) { return p.isSameLevel(level); })
1229       .transform([] (const Position& p) { return p.getCoord();});
1230   int infinity = 1000000;
1231   int radius = 10;
1232   Table<int> dist(Rectangle::boundingBox(enemyPos)
1233       .minusMargin(-radius)
1234       .intersection(getLevel()->getBounds()), infinity);
1235   queue<Vec2> q;
1236   for (Vec2 v : enemyPos) {
1237     dist[v] = 0;
1238     q.push(v);
1239   }
1240   while (!q.empty()) {
1241     Vec2 pos = q.front();
1242     q.pop();
1243     delayedPos[Position(pos, level)] = delayTime;
1244     if (dist[pos] >= radius)
1245       continue;
1246     for (Vec2 v : pos.neighbors8())
1247       if (v.inRectangle(dist.getBounds()) && dist[v] == infinity && territory->contains(Position(v, level))) {
1248         dist[v] = dist[pos] + 1;
1249         q.push(v);
1250       }
1251   }
1252 }
1253 
isDelayed(Position pos)1254 bool Collective::isDelayed(Position pos) {
1255   return delayedPos.count(pos) && delayedPos.at(pos) > getLocalTime();
1256 }
1257 
fetchItems(Position pos,const ItemFetchInfo & elem)1258 void Collective::fetchItems(Position pos, const ItemFetchInfo& elem) {
1259   if (isDelayed(pos) || !pos.canEnterEmpty(MovementTrait::WALK) || elem.destinationFun(this).count(pos))
1260     return;
1261   vector<WItem> equipment = pos.getItems(elem.index).filter(
1262       [this, &elem] (WConstItem item) { return elem.predicate(this, item); });
1263   if (!equipment.empty()) {
1264     const set<Position>& destination = elem.destinationFun(this);
1265     if (!destination.empty()) {
1266       warnings->setWarning(elem.warning, false);
1267       if (elem.oneAtATime)
1268         equipment = {equipment[0]};
1269       auto task = taskMap->addTask(Task::bringItem(this, pos, equipment, destination), pos);
1270       for (WItem it : equipment)
1271         markItem(it, task);
1272     } else
1273       warnings->setWarning(elem.warning, true);
1274   }
1275 }
1276 
handleSurprise(Position pos)1277 void Collective::handleSurprise(Position pos) {
1278   Vec2 rad(8, 8);
1279   WCreature c = pos.getCreature();
1280   for (Position v : Random.permutation(pos.getRectangle(Rectangle(-rad, rad + Vec2(1, 1)))))
1281     if (WCreature other = v.getCreature())
1282       if (hasTrait(other, MinionTrait::FIGHTER) && other->getPosition().dist8(pos) > 1) {
1283         for (Position dest : pos.neighbors8(Random))
1284           if (other->getPosition().canMoveCreature(dest)) {
1285             other->getPosition().moveCreature(dest);
1286             break;
1287           }
1288       }
1289   pos.globalMessage("Surprise!");
1290 }
1291 
retire()1292 void Collective::retire() {
1293   knownTiles->limitToModel(getModel());
1294   knownVillainLocations.clear();
1295   knownVillains.clear();
1296 }
1297 
getWarnings()1298 CollectiveWarnings& Collective::getWarnings() {
1299   return *warnings;
1300 }
1301 
getConfig() const1302 const CollectiveConfig& Collective::getConfig() const {
1303   return *config;
1304 }
1305 
addKnownTile(Position pos)1306 bool Collective::addKnownTile(Position pos) {
1307   if (!knownTiles->isKnown(pos)) {
1308     pos.setNeedsRenderUpdate(true);
1309     knownTiles->addTile(pos);
1310     if (pos.getLevel() == level)
1311       if (WTask task = taskMap->getMarked(pos))
1312         if (task->isBogus())
1313           taskMap->removeTask(task);
1314     return true;
1315   } else
1316     return false;
1317 }
1318 
addMana(double value)1319 void Collective::addMana(double value) {
1320   if ((manaRemainder += value) >= 1) {
1321     returnResource({ResourceId::MANA, int(manaRemainder)});
1322     manaRemainder -= int(manaRemainder);
1323   }
1324 }
1325 
addProducesMessage(WConstCreature c,const vector<PItem> & items)1326 void Collective::addProducesMessage(WConstCreature c, const vector<PItem>& items) {
1327   if (items.size() > 1)
1328     control->addMessage(c->getName().a() + " produces " + toString(items.size())
1329         + " " + items[0]->getName(true));
1330   else
1331     control->addMessage(c->getName().a() + " produces " + items[0]->getAName());
1332 }
1333 
onAppliedSquare(WCreature c,Position pos)1334 void Collective::onAppliedSquare(WCreature c, Position pos) {
1335   if (auto furniture = pos.getFurniture(FurnitureLayer::MIDDLE)) {
1336     // Furniture have variable usage time, so just multiply by it to be independent of changes.
1337     double efficiency = tileEfficiency->getEfficiency(pos) * furniture->getUsageTime() * getEfficiency(c);
1338     switch (furniture->getType()) {
1339       case FurnitureType::THRONE:
1340         if (config->getRegenerateMana())
1341           addMana(0.2 * efficiency);
1342         break;
1343       case FurnitureType::WHIPPING_POST:
1344         taskMap->addTask(Task::whipping(pos, c), pos);
1345         break;
1346       case FurnitureType::GALLOWS:
1347         taskMap->addTask(Task::kill(this, c), pos);
1348         break;
1349       case FurnitureType::TORTURE_TABLE:
1350         taskMap->addTask(Task::torture(this, c), pos);
1351         break;
1352       default:
1353         break;
1354     }
1355     if (auto usage = furniture->getUsageType()) {
1356       auto increaseLevel = [&] (ExperienceType exp) {
1357         double increase = 0.005 * efficiency;
1358         if (auto maxLevel = config->getTrainingMaxLevel(exp, furniture->getType()))
1359           increase = min(increase, *maxLevel - c->getAttributes().getExpLevel(exp));
1360         if (increase > 0)
1361           c->increaseExpLevel(exp, increase);
1362       };
1363       switch (*usage) {
1364         case FurnitureUsageType::TRAIN:
1365           increaseLevel(ExperienceType::MELEE);
1366           break;
1367         case FurnitureUsageType::STUDY:
1368           increaseLevel(ExperienceType::SPELL);
1369           if (config->getRegenerateMana())
1370             addMana(0.1 * efficiency);
1371           break;
1372         case FurnitureUsageType::ARCHERY_RANGE:
1373           increaseLevel(ExperienceType::ARCHERY);
1374           break;
1375         default:
1376           break;
1377       }
1378     }
1379     if (auto workshopType = config->getWorkshopType(furniture->getType())) {
1380       auto& info = config->getWorkshopInfo(*workshopType);
1381       vector<PItem> items =
1382           workshops->get(*workshopType).addWork(efficiency * c->getAttributes().getSkills().getValue(info.skill));
1383       if (!items.empty()) {
1384         if (items[0]->getClass() == ItemClass::WEAPON)
1385           getGame()->getStatistics().add(StatId::WEAPON_PRODUCED);
1386         if (items[0]->getClass() == ItemClass::ARMOR)
1387           getGame()->getStatistics().add(StatId::ARMOR_PRODUCED);
1388         if (items[0]->getClass() == ItemClass::POTION)
1389           getGame()->getStatistics().add(StatId::POTION_PRODUCED);
1390         addProducesMessage(c, items);
1391         c->getPosition().dropItems(std::move(items));
1392       }
1393     }
1394   }
1395 }
1396 
getMissingTrainingFurniture(WConstCreature c,ExperienceType expType) const1397 optional<FurnitureType> Collective::getMissingTrainingFurniture(WConstCreature c, ExperienceType expType) const {
1398   if (c->getAttributes().isTrainingMaxedOut(expType))
1399     return none;
1400   optional<FurnitureType> requiredDummy;
1401   for (auto dummyType : CollectiveConfig::getTrainingFurniture(expType)) {
1402     bool canTrain = *config->getTrainingMaxLevel(expType, dummyType) >
1403         c->getAttributes().getExpLevel(expType);
1404     bool hasDummy = getConstructions().getBuiltCount(dummyType) > 0;
1405     if (canTrain && hasDummy)
1406       return none;
1407     if (!requiredDummy && canTrain && !hasDummy)
1408       requiredDummy = dummyType;
1409   }
1410   return requiredDummy;
1411 }
1412 
getDangerLevel() const1413 double Collective::getDangerLevel() const {
1414   if (!dangerLevelCache) {
1415     double ret = 0;
1416     for (WConstCreature c : getCreatures(MinionTrait::FIGHTER))
1417       ret += c->getDifficultyPoints();
1418     ret += constructions->getBuiltCount(FurnitureType::IMPALED_HEAD) * 150;
1419     dangerLevelCache = ret;
1420   }
1421   return *dangerLevelCache;
1422 }
1423 
hasTech(TechId id) const1424 bool Collective::hasTech(TechId id) const {
1425   return technologies.contains(id);
1426 }
1427 
acquireTech(Technology * tech)1428 void Collective::acquireTech(Technology* tech) {
1429   technologies.push_back(tech->getId());
1430   Technology::onAcquired(tech->getId(), this);
1431 }
1432 
getTechnologies() const1433 vector<Technology*> Collective::getTechnologies() const {
1434   return technologies.transform([] (const TechId t) { return Technology::get(t); });
1435 }
1436 
getKills() const1437 const EntitySet<Creature>& Collective::getKills() const {
1438   return kills;
1439 }
1440 
getPoints() const1441 int Collective::getPoints() const {
1442   return points;
1443 }
1444 
onRansomPaid()1445 void Collective::onRansomPaid() {
1446   control->onRansomPaid();
1447 }
1448 
onExternalEnemyKilled(const std::string & name)1449 void Collective::onExternalEnemyKilled(const std::string& name) {
1450   control->addMessage(PlayerMessage("You resisted the attack of " + name + ".",
1451       MessagePriority::CRITICAL));
1452   int mana = 100;
1453   addMana(mana);
1454   control->addMessage(PlayerMessage("You feel a surge of power (+" + toString(mana) + " mana)",
1455       MessagePriority::CRITICAL));
1456 }
1457 
onCopulated(WCreature who,WCreature with)1458 void Collective::onCopulated(WCreature who, WCreature with) {
1459   if (with->getName().bare() == "vampire")
1460     control->addMessage(who->getName().a() + " makes love to " + with->getName().a()
1461         + " with a monkey on " + who->getAttributes().getGender().his() + " knee");
1462   else
1463     control->addMessage(who->getName().a() + " makes love to " + with->getName().a());
1464   if (getCreatures().contains(with))
1465     with->addMorale(1);
1466   if (!who->isAffected(LastingEffect::PREGNANT) && Random.roll(2)) {
1467     who->addEffect(LastingEffect::PREGNANT, getConfig().getImmigrantTimeout());
1468     control->addMessage(who->getName().a() + " becomes pregnant.");
1469   }
1470 }
1471 
getMinionEquipment()1472 MinionEquipment& Collective::getMinionEquipment() {
1473   return *minionEquipment;
1474 }
1475 
getMinionEquipment() const1476 const MinionEquipment& Collective::getMinionEquipment() const {
1477   return *minionEquipment;
1478 }
1479 
getWorkshops()1480 Workshops& Collective::getWorkshops() {
1481   return *workshops;
1482 }
1483 
getWorkshops() const1484 const Workshops& Collective::getWorkshops() const {
1485   return *workshops;
1486 }
1487 
getImmigration()1488 Immigration& Collective::getImmigration() {
1489   return *immigration;
1490 }
1491 
getImmigration() const1492 const Immigration& Collective::getImmigration() const {
1493   return *immigration;
1494 }
1495 
addAttack(const CollectiveAttack & attack)1496 void Collective::addAttack(const CollectiveAttack& attack) {
1497   control->addAttack(attack);
1498 }
1499 
getTeams()1500 CollectiveTeams& Collective::getTeams() {
1501   return *teams;
1502 }
1503 
getTeams() const1504 const CollectiveTeams& Collective::getTeams() const {
1505   return *teams;
1506 }
1507 
freeTeamMembers(TeamId id)1508 void Collective::freeTeamMembers(TeamId id) {
1509   for (WCreature c : teams->getMembers(id)) {
1510     if (c->isAffected(LastingEffect::SLEEP))
1511       c->removeEffect(LastingEffect::SLEEP);
1512   }
1513 }
1514 
getZones()1515 Zones& Collective::getZones() {
1516   return *zones;
1517 }
1518 
getZones() const1519 const Zones& Collective::getZones() const {
1520   return *zones;
1521 }
1522 
getTaskMap() const1523 const TaskMap& Collective::getTaskMap() const {
1524   return *taskMap;
1525 }
1526 
getTaskMap()1527 TaskMap& Collective::getTaskMap() {
1528   return *taskMap;
1529 }
1530 
getPopulationSize() const1531 int Collective::getPopulationSize() const {
1532   return getCreatures().size() - getCreatures(MinionTrait::NO_LIMIT).size();
1533 }
1534 
getMaxPopulation() const1535 int Collective::getMaxPopulation() const {
1536   int ret = config->getMaxPopulation();
1537   for (auto& elem : config->getPopulationIncreases()) {
1538     int sz = getConstructions().getBuiltPositions(elem.type).size();
1539     ret += min<int>(elem.maxIncrease, elem.increasePerSquare * sz);
1540   }
1541   return ret;
1542 }
1543 
1544 REGISTER_TYPE(Collective)
1545 REGISTER_TYPE(ListenerTemplate<Collective>)
1546