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