From 6c08abddeff99d60027a2f45eb445810e1234176 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Mon, 1 Aug 2016 16:11:22 +0200 Subject: [PATCH 001/148] Remove array serialization function --- serialization.h | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/serialization.h b/serialization.h index 2cb723353..ef58d0f3e 100644 --- a/serialization.h +++ b/serialization.h @@ -319,21 +319,6 @@ inline void serialize(Archive & ar, std::vector & t, unsigned int boost::serialization::split_free(ar, t, file_version); } -#ifdef CLANG // clang doesn't see the serialization of std::array in boost, for some reason -#ifndef OSX -template -void serialize(Archive& ar, std::array& a, const unsigned int) -{ - ar & boost::serialization::make_nvp( - "elems", - *static_cast(static_cast(a.data())) - ); - -} - -#endif -#endif - #ifdef DEBUG_STL // stl debug dummies From 3536e1a881070bc108cc034c7c5433320ff47be4 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Mon, 1 Aug 2016 16:13:28 +0200 Subject: [PATCH 002/148] Stop giving mana for kills; Give mana for destroying lesser and main villains --- collective.cpp | 21 +++++++++++++++++++-- player_control.cpp | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/collective.cpp b/collective.cpp index 75dd6f9bd..37cf18fe5 100644 --- a/collective.cpp +++ b/collective.cpp @@ -37,6 +37,7 @@ #include "collective_name.h" #include "creature_attributes.h" #include "event_proxy.h" +#include "villain_type.h" struct Collective::ItemFetchInfo { ItemIndex index; @@ -1108,10 +1109,11 @@ vector Collective::getCreatures(EnumSet with, EnumSetgetDifficultyPoints() / 3; + return 0; +/* int ret = victim->getDifficultyPoints() / 3; if (victim->isAffected(LastingEffect::SLEEP)) ret *= 2; - return ret; + return ret;*/ } void Collective::addMoraleForKill(const Creature* killer, const Creature* victim) { @@ -1132,6 +1134,15 @@ void Collective::decreaseMoraleForBanishing(const Creature*) { void Collective::onKillCancelled(Creature* c) { } +static int getManaForConquering(VillainType type) { + switch (type) { + case VillainType::MAIN: return 400; + case VillainType::LESSER: return 200; + default: return 0; + } +} + + void Collective::onEvent(const GameEvent& event) { switch (event.getId()) { case EventId::ALARM: { @@ -1209,6 +1220,12 @@ void Collective::onEvent(const GameEvent& event) { minionEquipment->own(event.get().creature, getOnlyElement(event.get().items)); break; + case EventId::CONQUERED_ENEMY: { + Collective* col = event.get(); + if (col->getVillainType()) + addMana(getManaForConquering(*col->getVillainType())); + } + break; default: break; } diff --git a/player_control.cpp b/player_control.cpp index c7dbe2b30..d70bd537d 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -276,7 +276,7 @@ static vector getHints() { "Research geology to uncover ores in the mountain.", "Morale affects minion productivity and chances of fleeing from battle.", // "You can turn these hints off in the settings (F2).", - "Killing a leader greatly lowers the morale of his tribe and stops immigration.", +// "Killing a leader greatly lowers the morale of his tribe and stops immigration.", "Your minions' morale is boosted when they are commanded by the Keeper.", }; } From faceb4f7ea06b0db326881cdf94ee22e86e58150 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Mon, 1 Aug 2016 17:52:50 +0200 Subject: [PATCH 003/148] Only display team in control mode tab when it's larger than 1 --- gui_builder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui_builder.cpp b/gui_builder.cpp index 16d880802..de3e96b65 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -812,7 +812,7 @@ PGuiElem GuiBuilder::drawPlayerInventory(PlayerInfo& info) { for (auto& elem : drawEffectsList(info)) list.addElem(std::move(elem)); list.addSpace(); - if (!info.team.empty()) { + if (info.team.size() > 1) { const int numPerLine = 6; vector widths { 60 }; vector currentLine = makeVec( From b5c42fe8d540fa1134155b87675ed5291acecb37 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Tue, 2 Aug 2016 12:40:09 +0200 Subject: [PATCH 004/148] Remove unused and costly call to getDangerLevel() --- village_control.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/village_control.cpp b/village_control.cpp index b32903456..3f56c4df3 100644 --- a/village_control.cpp +++ b/village_control.cpp @@ -177,8 +177,6 @@ void VillageControl::update(bool currentlyActive) { considerWelcomeMessage(); considerCancellingAttack(); checkEntries(); - if (Collective* enemy = getEnemyCollective()) - maxEnemyPower = max(maxEnemyPower, enemy->getDangerLevel()); vector allMembers = getCollective()->getCreatures(); for (auto team : getCollective()->getTeams().getAll()) { for (const Creature* c : getCollective()->getTeams().getMembers(team)) From 74406771e45ac2cd4dedb47c0567ae44e7b8d440 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Tue, 2 Aug 2016 13:39:21 +0200 Subject: [PATCH 005/148] Use EnumMap in equipment --- equipment.cpp | 12 ++++-------- equipment.h | 8 ++++---- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/equipment.cpp b/equipment.cpp index 96cd6b001..cd0444f99 100644 --- a/equipment.cpp +++ b/equipment.cpp @@ -37,17 +37,14 @@ SERIALIZABLE(Equipment); SERIALIZATION_CONSTRUCTOR_IMPL(Equipment); vector Equipment::getItem(EquipmentSlot slot) const { - if (items.count(slot) > 0) - return items.at(slot); - else - return {}; + return items[slot]; } bool Equipment::isEquiped(const Item* item) const { if (!item->canEquip()) return false; EquipmentSlot slot = item->getEquipmentSlot(); - return items.count(slot) && contains(items.at(slot), item); + return contains(items[slot], item); } int Equipment::getMaxItems(EquipmentSlot slot) const { @@ -61,7 +58,7 @@ bool Equipment::canEquip(const Item* item) const { if (!item->canEquip() || isEquiped(item)) return false; EquipmentSlot slot = item->getEquipmentSlot(); - return !items.count(slot) || items.at(slot).size() < getMaxItems(slot); + return items[slot].size() < getMaxItems(slot); } void Equipment::equip(Item* item, EquipmentSlot slot) { @@ -71,8 +68,7 @@ void Equipment::equip(Item* item, EquipmentSlot slot) { void Equipment::unequip(const Item* item) { EquipmentSlot slot = item->getEquipmentSlot(); - CHECK(items.count(slot)); - removeElement(items.at(slot), item); + removeElement(items[slot], item); } PItem Equipment::removeItem(Item* item) { diff --git a/equipment.h b/equipment.h index a38984b06..c1f0563cb 100644 --- a/equipment.h +++ b/equipment.h @@ -19,7 +19,7 @@ #include "inventory.h" #include "enums.h" -enum class EquipmentSlot { +RICH_ENUM(EquipmentSlot, WEAPON, RANGED_WEAPON, HELMET, @@ -27,8 +27,8 @@ enum class EquipmentSlot { BODY_ARMOR, BOOTS, AMULET, - RINGS, -}; + RINGS +); class Equipment : public Inventory { public: @@ -47,7 +47,7 @@ class Equipment : public Inventory { static map slotTitles; private: - map> SERIAL(items); + EnumMap> SERIAL(items); }; #endif From 643ce355abacf0931ad4987e9b9b02d5c05f63d8 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Tue, 2 Aug 2016 14:48:02 +0200 Subject: [PATCH 006/148] Maintain a vector of equipped items in Equipment to speed up queries --- collective.cpp | 2 +- creature.cpp | 14 ++++++-------- effect.cpp | 6 +----- equipment.cpp | 24 ++++++++++++++---------- equipment.h | 4 +++- item_factory.cpp | 4 ++-- monster_ai.cpp | 2 +- player.cpp | 4 ++-- player_control.cpp | 2 +- 9 files changed, 31 insertions(+), 31 deletions(-) diff --git a/collective.cpp b/collective.cpp index 37cf18fe5..9c2b8c52d 100644 --- a/collective.cpp +++ b/collective.cpp @@ -530,7 +530,7 @@ PTask Collective::getEquipmentTask(Creature* c) { autoEquipment(c, Random.roll(10)); vector tasks; for (Item* it : c->getEquipment().getItems()) - if (!c->getEquipment().isEquiped(it) && c->getEquipment().canEquip(it)) + if (!c->getEquipment().isEquipped(it) && c->getEquipment().canEquip(it)) tasks.push_back(Task::equipItem(it)); for (Position v : getAllSquares(config->getEquipmentStorage())) { vector it = filter(v.getItems(ItemIndex::MINION_EQUIPMENT), diff --git a/creature.cpp b/creature.cpp index ffc3ceccb..05362774c 100644 --- a/creature.cpp +++ b/creature.cpp @@ -572,15 +572,15 @@ CreatureAction Creature::equip(Item* item) const { } CreatureAction Creature::unequip(Item* item) const { - if (!equipment->isEquiped(item)) - return CreatureAction("This item is not equiped."); + if (!equipment->isEquipped(item)) + return CreatureAction("This item is not equipped."); if (!getBody().isHumanoid()) return CreatureAction("You can't remove this item!"); if (getBody().numGood(BodyPart::ARM) == 0) return CreatureAction("You have no healthy arms!"); return CreatureAction(this, [=](Creature* self) { Debug() << getName().the() << " unequip"; - CHECK(equipment->isEquiped(item)) << "Item not equiped."; + CHECK(equipment->isEquipped(item)) << "Item not equipped."; EquipmentSlot slot = item->getEquipmentSlot(); self->equipment->unequip(item); playerMessage("You " + string(slot == EquipmentSlot::WEAPON ? " sheathe " : " remove ") + @@ -723,9 +723,8 @@ int simulAttackPen(int attackers) { int Creature::getAttr(AttrType type) const { int def = getBody().modifyAttr(type, attributes->getRawAttr(type)); - for (Item* item : equipment->getItems()) - if (equipment->isEquiped(item)) - def += CHECK_RANGE(item->getAttr(type), -10000000, 10000000, getName().bare()); + for (Item* item : equipment->getAllEquipped()) + def += CHECK_RANGE(item->getAttr(type), -10000000, 10000000, getName().bare()); switch (type) { case AttrType::DEXTERITY: case AttrType::STRENGTH: @@ -751,8 +750,7 @@ int Creature::accuracyBonus() const { int Creature::getModifier(ModifierType type) const { int def = 0; - for (Item* item : equipment->getItems()) - if (equipment->isEquiped(item)) + for (Item* item : equipment->getAllEquipped()) def += CHECK_RANGE(item->getModifier(type), -10000000, 10000000, getName().bare()); for (SkillId skill : ENUM_ALL(SkillId)) def += CHECK_RANGE(Skill::get(skill)->getModifier(this, type), -10000000, 10000000, getName().bare()); diff --git a/effect.cpp b/effect.cpp index eab021cfd..7023b3469 100644 --- a/effect.cpp +++ b/effect.cpp @@ -192,11 +192,7 @@ static void enhanceWeapon(Creature* c, int mod = 1, const string msg = "is impro } static void destroyEquipment(Creature* c) { - vector equiped; - for (Item* item : c->getEquipment().getItems()) - if (c->getEquipment().isEquiped(item)) - equiped.push_back(item); - Item* dest = Random.choose(equiped); + Item* dest = Random.choose(c->getEquipment().getAllEquipped()); c->you(MsgType::YOUR, dest->getName() + " crumbles to dust."); c->steal({dest}); return; diff --git a/equipment.cpp b/equipment.cpp index cd0444f99..6e963d335 100644 --- a/equipment.cpp +++ b/equipment.cpp @@ -30,7 +30,8 @@ map Equipment::slotTitles = { template void Equipment::serialize(Archive& ar, const unsigned int version) { - ar & SUBCLASS(Inventory) & SVAR(items); + ar & SUBCLASS(Inventory); + serializeAll(ar, items, equipped); } SERIALIZABLE(Equipment); @@ -40,11 +41,12 @@ vector Equipment::getItem(EquipmentSlot slot) const { return items[slot]; } -bool Equipment::isEquiped(const Item* item) const { - if (!item->canEquip()) - return false; - EquipmentSlot slot = item->getEquipmentSlot(); - return contains(items[slot], item); +const vector& Equipment::getAllEquipped() const { + return equipped; +} + +bool Equipment::isEquipped(const Item* item) const { + return item->canEquip() && contains(items[item->getEquipmentSlot()], item); } int Equipment::getMaxItems(EquipmentSlot slot) const { @@ -55,7 +57,7 @@ int Equipment::getMaxItems(EquipmentSlot slot) const { } bool Equipment::canEquip(const Item* item) const { - if (!item->canEquip() || isEquiped(item)) + if (!item->canEquip() || isEquipped(item)) return false; EquipmentSlot slot = item->getEquipmentSlot(); return items[slot].size() < getMaxItems(slot); @@ -63,16 +65,17 @@ bool Equipment::canEquip(const Item* item) const { void Equipment::equip(Item* item, EquipmentSlot slot) { items[slot].push_back(item); + equipped.push_back(item); CHECK(hasItem(item)); } void Equipment::unequip(const Item* item) { - EquipmentSlot slot = item->getEquipmentSlot(); - removeElement(items[slot], item); + removeElement(items[item->getEquipmentSlot()], item); + removeElement(equipped, item); } PItem Equipment::removeItem(Item* item) { - if (isEquiped(item)) + if (isEquipped(item)) unequip(item); return Inventory::removeItem(item); } @@ -86,6 +89,7 @@ vector Equipment::removeItems(const vector& items) { vector Equipment::removeAllItems() { items.clear(); + equipped.clear(); return Inventory::removeAllItems(); } diff --git a/equipment.h b/equipment.h index c1f0563cb..e1a831333 100644 --- a/equipment.h +++ b/equipment.h @@ -33,12 +33,13 @@ RICH_ENUM(EquipmentSlot, class Equipment : public Inventory { public: vector getItem(EquipmentSlot slot) const; - bool isEquiped(const Item*) const; + bool isEquipped(const Item*) const; bool canEquip(const Item*) const; void equip(Item*, EquipmentSlot); void unequip(const Item*); PItem removeItem(Item*); int getMaxItems(EquipmentSlot) const; + const vector& getAllEquipped() const; vector removeItems(const vector&); vector removeAllItems(); @@ -48,6 +49,7 @@ class Equipment : public Inventory { private: EnumMap> SERIAL(items); + vector SERIAL(equipped); }; #endif diff --git a/item_factory.cpp b/item_factory.cpp index 55d99b14e..1b2104cf1 100644 --- a/item_factory.cpp +++ b/item_factory.cpp @@ -80,7 +80,7 @@ class AmuletOfWarning : public Item { virtual void specialTick(Position position) override { Creature* owner = position.getCreature(); - if (owner && owner->getEquipment().isEquiped(this)) { + if (owner && owner->getEquipment().isEquipped(this)) { bool isDanger = false; bool isBigDanger = false; for (Position v : position.getRectangle(Rectangle(-radius, -radius, radius + 1, radius + 1))) { @@ -123,7 +123,7 @@ class AmuletOfHealing : public Item { virtual void specialTick(Position position) override { Creature* owner = position.getCreature(); - if (owner && owner->getEquipment().isEquiped(this)) + if (owner && owner->getEquipment().isEquipped(this)) owner->heal(1.0 / 20); } diff --git a/monster_ai.cpp b/monster_ai.cpp index 390a8e8e1..784c07475 100644 --- a/monster_ai.cpp +++ b/monster_ai.cpp @@ -447,7 +447,7 @@ class Fighter : public Behaviour { Item* best = nullptr; int damage = 0; auto items = creature->getEquipment().getItems([this](Item* item) { - return !creature->getEquipment().isEquiped(item);}); + return !creature->getEquipment().isEquipped(item);}); for (Item* item : items) if (getThrowValue(item) > damage) { damage = getThrowValue(item); diff --git a/player.cpp b/player.cpp index ff73853ea..cc7e112aa 100644 --- a/player.cpp +++ b/player.cpp @@ -133,7 +133,7 @@ void Player::onBump(Creature*) { } string Player::getInventoryItemName(const Item* item, bool plural) const { - if (getCreature()->getEquipment().isEquiped(item)) + if (getCreature()->getEquipment().isEquipped(item)) return item->getNameAndModifiers(plural, getCreature()) + " " + getSlotSuffix(item->getEquipmentSlot()); else @@ -949,7 +949,7 @@ ItemInfo Player::getItemInfo(const vector& stack) const { c.viewId = stack[0]->getViewObject().id(); c.ids = transform2::Id>(stack, [](const Item* it) { return it->getUniqueId();}); c.actions = getItemActions(stack); - c.equiped = getCreature()->getEquipment().isEquiped(stack[0]); ); + c.equiped = getCreature()->getEquipment().isEquipped(stack[0]); ); } vector Player::getItemInfos(const vector& items) const { diff --git a/player_control.cpp b/player_control.cpp index d70bd537d..c8219c3c0 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -607,7 +607,7 @@ void PlayerControl::fillEquipment(Creature* creature, PlayerInfo& info) const { append(slotIndex, vector(items.size(), slot)); for (Item* item : items) { removeElement(ownedItems, item); - bool equiped = creature->getEquipment().isEquiped(item); + bool equiped = creature->getEquipment().isEquipped(item); bool locked = getCollective()->getMinionEquipment().isLocked(creature, item->getUniqueId()); info.inventory.push_back(getItemInfo({item}, equiped, !equiped, locked, ItemInfo::EQUIPMENT)); info.inventory.back().actions.push_back(locked ? ItemAction::UNLOCK : ItemAction::LOCK); From 27fa18dd534dc49294ee43523e9bd38afc4dfa0f Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Fri, 5 Aug 2016 09:15:58 +0200 Subject: [PATCH 007/148] Add UI for workshop queues --- Makefile | 2 + collective.cpp | 12 +++- collective.h | 5 ++ collective_config.cpp | 86 +++++++++++++++++++++++--- collective_config.h | 3 + enums.h | 1 + game_info.h | 23 +++++-- gui_builder.cpp | 137 +++++++++++++++++++++++++++++++++++------- gui_builder.h | 7 ++- gui_elem.cpp | 5 +- item_action.h | 15 ++++- item_type.h | 7 +-- player_control.cpp | 104 ++++++++++++++++++++++++++------ player_control.h | 10 ++- user_input.h | 15 ++++- window_view.cpp | 2 +- 16 files changed, 367 insertions(+), 67 deletions(-) diff --git a/Makefile b/Makefile index a7ff8c216..19f2b9407 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,8 @@ BOOST_LIBS = -lboost_serialization -lboost_program_options -lboost_filesystem -l OPENGL_LIBS = -lGL endif +LDFLAGS += -L/usr/local/lib + SRCS = $(shell ls -t *.cpp) LIBS = -L/usr/lib/x86_64-linux-gnu $(OPENGL_LIBS) -lSDL2 -lopenal -lvorbis -lvorbisfile -lSDL2_image $(BOOST_LIBS) -lz -lpthread -lcurl ${LDFLAGS} diff --git a/collective.cpp b/collective.cpp index 9c2b8c52d..093e7dd43 100644 --- a/collective.cpp +++ b/collective.cpp @@ -56,7 +56,7 @@ void Collective::serialize(Archive& ar, const unsigned int version) { serializeAll(ar, surrendering, delayedPos, knownTiles, technologies, numFreeTech, kills, points, currentTasks); serializeAll(ar, credit, level, minionPayment, pregnancies, nextPayoutTime, minionAttraction, teams, name); serializeAll(ar, config, warnings, banished, squaresInUse, equipmentUpdates); - serializeAll(ar, villainType, eventProxy); + serializeAll(ar, villainType, eventProxy, workshops); } SERIALIZABLE(Collective); @@ -93,7 +93,7 @@ const vector& Collective::getFetchInfo() const { Collective::Collective(Level* l, const CollectiveConfig& cfg, TribeId t, EnumMap _credit, const CollectiveName& n) : eventProxy(this, l->getModel()), credit(_credit), control(CollectiveControl::idle(this)), - tribe(t), level(NOTNULL(l)), nextPayoutTime(-1), name(n), config(cfg) { + tribe(t), level(NOTNULL(l)), nextPayoutTime(-1), name(n), config(cfg), workshops(config->getWorkshops()) { } const CollectiveName& Collective::getName() const { @@ -2092,6 +2092,14 @@ const MinionEquipment& Collective::getMinionEquipment() const { return *minionEquipment; } +Workshops& Collective::getWorkshops() { + return *workshops; +} + +const Workshops& Collective::getWorkshops() const { + return *workshops; +} + int Collective::getSalary(const Creature* c) const { if (auto payment = minionPayment.getMaybe(c)) return payment->salary; diff --git a/collective.h b/collective.h index e6fe86309..cde39dfcc 100644 --- a/collective.h +++ b/collective.h @@ -48,6 +48,7 @@ class Territory; class CollectiveName; template class EventProxy; +class Workshops; class Collective : public TaskCallback { public: @@ -185,6 +186,9 @@ class Collective : public TaskCallback { MinionEquipment& getMinionEquipment(); const MinionEquipment& getMinionEquipment() const; + Workshops& getWorkshops(); + const Workshops& getWorkshops() const; + struct MinionPaymentInfo { int SERIAL(salary); double SERIAL(workAmount); @@ -358,6 +362,7 @@ class Collective : public TaskCallback { EntitySet SERIAL(banished); EntitySet SERIAL(equipmentUpdates); optional SERIAL(villainType); + HeapAllocated SERIAL(workshops); }; #endif diff --git a/collective_config.cpp b/collective_config.cpp index cfce5e397..401ffa922 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -9,6 +9,9 @@ #include "item_type.h" #include "resource_id.h" #include "inventory.h" +#include "workshops.h" +#include "lasting_effect.h" +#include "item.h" AttractionInfo::AttractionInfo(MinionAttraction a, double cl, double min, bool mand) : attraction(a), amountClaimed(cl), minAmount(min), mandatory(mand) {} @@ -286,14 +289,15 @@ const vector& CollectiveConfig::getResourceStorage() { const map& CollectiveConfig::getResourceInfo() { static map ret = { - {CollectiveResourceId::MANA, { {}, none, ItemId::GOLD_PIECE, "mana"}}, - {CollectiveResourceId::PRISONER_HEAD, { {}, none, ItemId::GOLD_PIECE, "", true}}, - {CollectiveResourceId::GOLD, {{SquareId::TREASURE_CHEST}, ItemIndex::GOLD, ItemId::GOLD_PIECE,"gold",}}, - {CollectiveResourceId::WOOD, { resourceStorage, ItemIndex::WOOD, ItemId::WOOD_PLANK, "wood"}}, - {CollectiveResourceId::IRON, { resourceStorage, ItemIndex::IRON, ItemId::IRON_ORE, "iron"}}, - {CollectiveResourceId::STONE, { resourceStorage, ItemIndex::STONE, ItemId::ROCK, "granite"}}, - {CollectiveResourceId::CORPSE, { {SquareId::CEMETERY}, ItemIndex::REVIVABLE_CORPSE, ItemId::GOLD_PIECE, - "corpses", true}} + {CollectiveResourceId::MANA, { {}, none, ItemId::GOLD_PIECE, "mana", ViewId::MANA}}, + {CollectiveResourceId::PRISONER_HEAD, { {}, none, ItemId::GOLD_PIECE, "", ViewId::IMPALED_HEAD, true}}, + {CollectiveResourceId::GOLD, + {{SquareId::TREASURE_CHEST}, ItemIndex::GOLD, ItemId::GOLD_PIECE, "gold", ViewId::GOLD}}, + {CollectiveResourceId::WOOD, { resourceStorage, ItemIndex::WOOD, ItemId::WOOD_PLANK, "wood", ViewId::WOOD_PLANK}}, + {CollectiveResourceId::IRON, { resourceStorage, ItemIndex::IRON, ItemId::IRON_ORE, "iron", ViewId::IRON_ROCK}}, + {CollectiveResourceId::STONE, { resourceStorage, ItemIndex::STONE, ItemId::ROCK, "granite", ViewId::ROCK}}, + {CollectiveResourceId::CORPSE, + { {SquareId::CEMETERY}, ItemIndex::REVIVABLE_CORPSE, ItemId::GOLD_PIECE, "corpses", ViewId::BODY_PART, true}} }; return ret; } @@ -338,3 +342,69 @@ map CollectiveConfig::getTaskInfo() const { return ret; }; +Workshops CollectiveConfig::getWorkshops() const { + return Workshops({ + {WorkshopType::WORKSHOP, { + Workshops::Item::fromType(ItemId::FIRST_AID_KIT, {CollectiveResourceId::WOOD, 20}), + Workshops::Item::fromType(ItemId::LEATHER_ARMOR, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType(ItemId::LEATHER_HELM, {CollectiveResourceId::WOOD, 30}), + Workshops::Item::fromType(ItemId::LEATHER_BOOTS, {CollectiveResourceId::WOOD, 50}), + Workshops::Item::fromType(ItemId::LEATHER_GLOVES, {CollectiveResourceId::WOOD, 10}), + Workshops::Item::fromType(ItemId::CLUB, {CollectiveResourceId::WOOD, 50}), + Workshops::Item::fromType(ItemId::HEAVY_CLUB, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType(ItemId::BOW, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 2}, 20), + Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType({ItemId::TRAP_ITEM, + TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType({ItemId::TRAP_ITEM, + TrapInfo({TrapType::ALARM, EffectId::ALARM})}, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::WEB, + EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType({ItemId::TRAP_ITEM, + TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::TERROR, + EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, {CollectiveResourceId::WOOD, 100}), + }}, + {WorkshopType::FORGE, { + Workshops::Item::fromType(ItemId::SWORD, {CollectiveResourceId::IRON, 100}), + Workshops::Item::fromType(ItemId::SPECIAL_SWORD, {CollectiveResourceId::IRON, 1000}), + Workshops::Item::fromType(ItemId::CHAIN_ARMOR, {CollectiveResourceId::IRON, 200}), + Workshops::Item::fromType(ItemId::IRON_HELM, {CollectiveResourceId::IRON, 80}), + Workshops::Item::fromType(ItemId::IRON_BOOTS, {CollectiveResourceId::IRON, 120}), + Workshops::Item::fromType(ItemId::WAR_HAMMER, {CollectiveResourceId::IRON, 190}), + Workshops::Item::fromType(ItemId::BATTLE_AXE, {CollectiveResourceId::IRON, 250}), + Workshops::Item::fromType(ItemId::SPECIAL_WAR_HAMMER, {CollectiveResourceId::IRON, 1900}), + Workshops::Item::fromType(ItemId::SPECIAL_BATTLE_AXE, {CollectiveResourceId::IRON, 2000}), + }}, + {WorkshopType::LABORATORY, { + Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLOWED}}, + {CollectiveResourceId::MANA, 10}), + Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLEEP}}, + {CollectiveResourceId::MANA, 10}), + Workshops::Item::fromType({ItemId::POTION, EffectId::HEAL}, {CollectiveResourceId::MANA, 30}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::POISON_RESISTANT}}, {CollectiveResourceId::MANA, 30}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::POISON}}, {CollectiveResourceId::MANA, 30}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::SPEED}}, {CollectiveResourceId::MANA, 30}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::BLIND}}, {CollectiveResourceId::MANA, 50}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::FLYING}}, {CollectiveResourceId::MANA, 80}), + Workshops::Item::fromType({ItemId::POTION, + EffectType{EffectId::LASTING, LastingEffect::INVISIBLE}}, {CollectiveResourceId::MANA, 200}), + }}, + {WorkshopType::JEWELER, { + Workshops::Item::fromType({ItemId::RING, LastingEffect::POISON_RESISTANT}, + {CollectiveResourceId::GOLD, 100}), + Workshops::Item::fromType({ItemId::RING, LastingEffect::FIRE_RESISTANT}, + {CollectiveResourceId::GOLD, 150}), + Workshops::Item::fromType(ItemId::WARNING_AMULET, {CollectiveResourceId::GOLD, 150}), + Workshops::Item::fromType(ItemId::DEFENSE_AMULET, {CollectiveResourceId::GOLD, 200}), + Workshops::Item::fromType(ItemId::HEALING_AMULET, {CollectiveResourceId::GOLD, 300}), + }}, + }); +} + diff --git a/collective_config.h b/collective_config.h index 466368de7..0a961680f 100644 --- a/collective_config.h +++ b/collective_config.h @@ -19,6 +19,7 @@ #include "square_type.h" #include "util.h" #include "minion_task.h" +#include "workshops.h" enum class ItemClass; @@ -107,6 +108,7 @@ struct ResourceInfo { optional itemIndex; ItemId itemId; string name; + ViewId viewId; bool dontDisplay; }; @@ -154,6 +156,7 @@ class CollectiveConfig { const vector& getPopulationIncreases() const; const optional& getGuardianInfo() const; vector getBirthSpawns() const; + Workshops getWorkshops() const; bool activeImmigrantion(const Game*) const; const EnumMap& getDormInfo() const; diff --git a/enums.h b/enums.h index ef770d4a2..56f267476 100644 --- a/enums.h +++ b/enums.h @@ -90,4 +90,5 @@ enum class CollectiveWarning; enum class SoundId; enum class MessagePriority; +enum class WorkshopType; #endif diff --git a/game_info.h b/game_info.h index 7f10b1eb5..a435d99b7 100644 --- a/game_info.h +++ b/game_info.h @@ -152,13 +152,27 @@ class CollectiveInfo { }; vector HASH(minionGroups); vector HASH(enemyGroups); - struct ChosenInfo { + struct ChosenCreatureInfo { UniqueEntity::Id HASH(chosenId); vector HASH(creatures); optional HASH(teamId); HASH_ALL(chosenId, creatures, teamId); }; - optional HASH(chosen); + optional HASH(chosenCreature); + struct WorkshopButton { + string HASH(name); + ViewId HASH(viewId); + bool HASH(active); + HASH_ALL(name, viewId, active); + }; + vector HASH(workshopButtons); + struct ChosenWorkshopInfo { + vector HASH(options); + vector HASH(queued); + int HASH(index); + HASH_ALL(index, options, queued); + }; + optional HASH(chosenWorkshop); struct Resource { ViewId HASH(viewId); int HASH(count); @@ -183,7 +197,8 @@ class CollectiveInfo { ViewId HASH(viewId); string HASH(name); char HASH(hotkey); - HASH_ALL(viewId, name, hotkey); + bool HASH(active); + HASH_ALL(viewId, name, hotkey, active); }; vector HASH(techButtons); @@ -203,7 +218,7 @@ class CollectiveInfo { }; optional HASH(ransom); - HASH_ALL(warning, buildings, minionButtons, libraryButtons, minionCount, minionLimit, monsterHeader, minions, minionGroups, enemyGroups, chosen, numResource, teams, nextPayout, payoutTimeRemaining, techButtons, taskMap, ransom); + HASH_ALL(warning, buildings, minionButtons, libraryButtons, minionCount, minionLimit, monsterHeader, minions, minionGroups, enemyGroups, chosenCreature, numResource, teams, nextPayout, payoutTimeRemaining, techButtons, taskMap, ransom, chosenWorkshop, workshopButtons); }; class VillageInfo { diff --git a/gui_builder.cpp b/gui_builder.cpp index de3e96b65..eb42c8350 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -60,9 +60,7 @@ void GuiBuilder::setCollectiveTab(CollectiveTab t) { if (collectiveTab != t) { collectiveTab = t; clearActiveButton(); - if (t != CollectiveTab::MINIONS) { - closeOverlayWindows(); - } + closeOverlayWindows(); } } @@ -73,6 +71,7 @@ CollectiveTab GuiBuilder::getCollectiveTab() const { void GuiBuilder::closeOverlayWindows() { // send a random id which wont be found callbacks.input({UserInputId::CREATURE_BUTTON, UniqueEntity::Id()}); + callbacks.input({UserInputId::WORKSHOP, -1}); } optional GuiBuilder::getActiveButton(CollectiveTab tab) const { @@ -138,9 +137,8 @@ PGuiElem GuiBuilder::getButtonLine(CollectiveInfo::Button button, int num, Colle line.buildHorizontalList()); } -vector GuiBuilder::drawButtons(vector buttons, CollectiveTab tab) { - vector elems; - vector invisible; +GuiFactory::ListBuilder GuiBuilder::drawButtons(vector buttons, CollectiveTab tab) { + auto elems = gui.getListBuilder(legendLineHeight); string lastGroup; for (int i : All(buttons)) { if (!buttons[i].groupName.empty() && buttons[i].groupName != lastGroup) { @@ -161,41 +159,60 @@ vector GuiBuilder::drawButtons(vector buttons, line.push_back(gui.stack( gui.uiHighlightConditional([=] { return activeGroup == lastGroup;}), gui.label(lastGroup, colors[ColorId::WHITE], hotkey))); - elems.push_back(gui.stack( + elems.addElem(gui.stack( gui.buttonChar(buttonFun, buttons[i].hotkeyOpensGroup ? buttons[i].hotkey : 0), gui.horizontalList(std::move(line), 35))); } if (buttons[i].groupName.empty()) - elems.push_back(getButtonLine(buttons[i], i, tab)); + elems.addElem(getButtonLine(buttons[i], i, tab)); } - elems.push_back(gui.invisible(gui.stack(std::move(invisible)))); return elems; } PGuiElem GuiBuilder::drawBuildings(CollectiveInfo& info) { int newHash = combineHash(info.buildings); if (newHash != buildingsHash) { - buildingsCache = gui.scrollable(gui.verticalList(drawButtons(info.buildings, CollectiveTab::BUILDINGS), - legendLineHeight), &buildingsScroll, &scrollbarsHeld); + buildingsCache = gui.scrollable(drawButtons(info.buildings, CollectiveTab::BUILDINGS).buildVerticalList(), + &buildingsScroll, &scrollbarsHeld); buildingsHash = newHash; } return gui.external(buildingsCache.get()); } PGuiElem GuiBuilder::drawTechnology(CollectiveInfo& info) { - int hash = combineHash(info.techButtons, info.libraryButtons); + int hash = combineHash(info.techButtons, info.workshopButtons, info.libraryButtons); if (hash != technologyHash) { technologyHash = hash; - vector lines = drawButtons(info.libraryButtons, CollectiveTab::TECHNOLOGY); + auto lines = drawButtons(info.libraryButtons, CollectiveTab::TECHNOLOGY); + lines.addSpace(legendLineHeight / 2); for (int i : All(info.techButtons)) { vector line; line.push_back(gui.viewObject(info.techButtons[i].viewId)); line.push_back(gui.label(info.techButtons[i].name, colors[ColorId::WHITE], info.techButtons[i].hotkey)); - lines.push_back(gui.stack(gui.buttonChar( - getButtonCallback(UserInput(UserInputId::TECHNOLOGY, i)), info.techButtons[i].hotkey), + lines.addElem(gui.stack( + gui.buttonChar([this, i] { + closeOverlayWindows(); + getButtonCallback(UserInput(UserInputId::TECHNOLOGY, i))(); + }, info.techButtons[i].hotkey), gui.horizontalList(std::move(line), 35))); } - technologyCache = gui.verticalList(std::move(lines), legendLineHeight); + lines.addSpace(legendLineHeight / 2); + for (int i : All(info.workshopButtons)) { + auto line = gui.getListBuilder(35); + line.addElem(gui.viewObject(info.workshopButtons[i].viewId)); + line.addElem(gui.label(info.workshopButtons[i].name, colors[ColorId::WHITE])); + PGuiElem elem = line.buildHorizontalList(); + if (info.workshopButtons[i].active) + elem = gui.stack( + gui.uiHighlight(colors[ColorId::GREEN]), + std::move(elem)); + lines.addElem(gui.stack( + gui.button([this, i] { + workshopsScroll2 = workshopsScroll = 0; + getButtonCallback(UserInput(UserInputId::WORKSHOP, i))(); }), + std::move(elem))); + } + technologyCache = lines.buildVerticalList(); } return gui.external(technologyCache.get()); } @@ -651,6 +668,8 @@ static string getActionText(ItemAction a) { case ItemAction::REPLACE: return "replace"; case ItemAction::LOCK: return "lock"; case ItemAction::UNLOCK: return "unlock"; + case ItemAction::REMOVE: return "remove"; + case ItemAction::CHANGE_NUMBER: return "change number"; } } @@ -1061,28 +1080,101 @@ void GuiBuilder::drawRansomOverlay(vector& ret, const CollectiveInf OverlayInfo::TOP_RIGHT}); } +void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& info) { + int margin = 20; + int rightElemMargin = 10; + Vec2 size(860, 600); + if (info.chosenWorkshop) { + auto& options = info.chosenWorkshop->options; + auto& queued = info.chosenWorkshop->queued; + auto lines = gui.getListBuilder(legendLineHeight); + lines.addElem(gui.label("Available:", colors[ColorId::YELLOW])); + for (int i : All(options)) { + auto& elem = options[i]; + auto line = gui.getListBuilder(); + line.addElem(gui.viewObject(elem.viewId), 35); + line.addElem(gui.label(elem.name), 10); + line.addBackElem(gui.label(toString(elem.number) + "x"), 35); + line.addBackElem(gui.alignment(GuiFactory::Alignment::RIGHT, drawCost(*elem.price)), 80); + lines.addElem(gui.rightMargin(rightElemMargin, gui.stack( + gui.uiHighlightMouseOver(colors[ColorId::GREEN]), + gui.button(getButtonCallback({UserInputId::WORKSHOP_ADD, i})), + line.buildHorizontalList()))); + } + auto lines2 = gui.getListBuilder(legendLineHeight); + lines2.addElem(gui.label("In queue:", colors[ColorId::YELLOW])); + for (int i : All(queued)) { + auto& elem = queued[i]; + auto line = gui.getListBuilder(); + line.addElemAuto(gui.stack( + gui.uiHighlightMouseOver(colors[ColorId::GREEN]), + gui.buttonRect([=] (Rectangle bounds) { + auto lines = gui.getListBuilder(legendLineHeight); + bool exit = false; + optional ret; + for (auto action : elem.actions) { + function buttonFun = [] {}; + if (!elem.unavailable) + buttonFun = [&exit, &ret, action] { + ret = action; + exit = true; + }; + lines.addElem(gui.stack( + gui.button(buttonFun), + gui.uiHighlightMouseOver(colors[ColorId::GREEN]), + gui.label(getActionText(action)))); + } + drawMiniMenu(std::move(lines), exit, bounds.bottomLeft(), 200); + if (ret) + callbacks.input({UserInputId::WORKSHOP_ITEM_ACTION, + WorkshopQueuedActionInfo{i, *ret}}); + }), + gui.getListBuilder() + .addElem(gui.viewObject(elem.viewId), 35) + .addElemAuto(gui.label(elem.name)).buildHorizontalList())); + line.addBackElem(gui.stack( + gui.uiHighlightMouseOver(colors[ColorId::GREEN]), + gui.button(getButtonCallback({UserInputId::WORKSHOP_ITEM_ACTION, + WorkshopQueuedActionInfo{i, ItemAction::CHANGE_NUMBER}})), + gui.label(toString(elem.number) + "x")), 35); + line.addBackElem(gui.alignment(GuiFactory::Alignment::RIGHT, drawCost(*elem.price)), 80); + lines2.addElem(gui.rightMargin(rightElemMargin, line.buildHorizontalList())); + } + size.y = min(600, max(lines.getSize(), lines2.getSize()) + 2 * margin); + ret.push_back({gui.miniWindow(gui.stack( + gui.keyHandler(getButtonCallback({UserInputId::WORKSHOP, info.chosenWorkshop->index}), + {gui.getKey(SDL::SDLK_ESCAPE)}, true), + gui.getListBuilder(430) + .addElem(gui.margins(gui.scrollable(lines.buildVerticalList(), &workshopsScroll, &scrollbarsHeld), + margin)) + .addElem(gui.margins(gui.scrollable(lines2.buildVerticalList(), &workshopsScroll2, &scrollbarsHeld), + margin)).buildHorizontalList())), + size, OverlayInfo::MINIONS}); + } +} + void GuiBuilder::drawMinionsOverlay(vector& ret, CollectiveInfo& info) { int margin = 20; Vec2 size(600, 600); int minionListWidth = 220; size.x += minionListWidth; - if (info.chosen) { + if (info.chosenCreature) { int newHash = info.getHash(); if (newHash != minionsOverlayHash) { PGuiElem minionPage; - auto& minions = info.chosen->creatures; - auto current = info.chosen->chosenId; + auto& minions = info.chosenCreature->creatures; + auto current = info.chosenCreature->chosenId; for (int i : All(minions)) if (minions[i].creatureId == current) minionPage = gui.margins(drawMinionPage(minions[i]), 10, 15, 10, 10); if (!minionPage) return; PGuiElem menu; - PGuiElem leftSide = drawMinionButtons(minions, current, info.chosen->teamId); - if (info.chosen->teamId) { + PGuiElem leftSide = drawMinionButtons(minions, current, info.chosenCreature->teamId); + if (info.chosenCreature->teamId) { auto list = gui.getListBuilder(legendLineHeight); list.addElem(gui.stack( - gui.button(getButtonCallback({UserInputId::CANCEL_TEAM, *info.chosen->teamId})), + gui.button(getButtonCallback({UserInputId::CANCEL_TEAM, *info.chosenCreature->teamId})), gui.labelHighlight("[Disband team]", colors[ColorId::LIGHT_BLUE]))); list.addElem(gui.label("Control a chosen minion to", Renderer::smallTextSize, colors[ColorId::LIGHT_GRAY]), Renderer::smallTextSize + 2); @@ -1151,6 +1243,7 @@ void GuiBuilder::drawBandOverlay(vector& ret, CollectiveInfo& info) if (info.ransom) drawRansomOverlay(ret, *info.ransom); drawMinionsOverlay(ret, info); + drawWorkshopsOverlay(ret, info); drawTasksOverlay(ret, info); drawBuildingsOverlay(ret, info); } diff --git a/gui_builder.h b/gui_builder.h index ed50f7972..32623e7cf 100644 --- a/gui_builder.h +++ b/gui_builder.h @@ -190,6 +190,8 @@ class GuiBuilder { double lyingItemsScroll = 0; double villagesScroll = 0; double tasksScroll = 0; + double workshopsScroll = 0; + double workshopsScroll2 = 0; double minionPageScroll = 0; int itemIndex = -1; int numSeenVillains = -1; @@ -218,11 +220,14 @@ class GuiBuilder { Clock clock; } fpsCounter, upsCounter; - vector drawButtons(vector buttons, CollectiveTab); + GuiFactory::ListBuilder drawButtons(vector buttons, CollectiveTab); PGuiElem getButtonLine(CollectiveInfo::Button, int num, CollectiveTab); void drawMinionsOverlay(vector&, CollectiveInfo&); PGuiElem minionsOverlayCache; int minionsOverlayHash = 0; + void drawWorkshopsOverlay(vector&, CollectiveInfo&); + PGuiElem workshopsOverlayCache; + int workshopsOverlayHash = 0; void drawTasksOverlay(vector&, CollectiveInfo&); void drawRansomOverlay(vector& ret, const CollectiveInfo::Ransom&); void drawBuildingsOverlay(vector&, CollectiveInfo&); diff --git a/gui_elem.cpp b/gui_elem.cpp index 6b874ac54..eb8dcc180 100644 --- a/gui_elem.cpp +++ b/gui_elem.cpp @@ -890,7 +890,10 @@ class AlignmentGui : public GuiLayout { case GuiFactory::Alignment::TOP_RIGHT: return Rectangle(getBounds().right() - getWidth(), getBounds().top(), getBounds().right(), getBounds().top() + getHeight()); - default: FAIL << "Unhandled"; + case GuiFactory::Alignment::RIGHT: + return Rectangle(getBounds().right() - getWidth(), getBounds().top(), getBounds().right(), + getBounds().bottom()); + default: FAIL << "Unhandled alignment: " << (int)alignment; } return Rectangle(); } diff --git a/item_action.h b/item_action.h index 65f3012c6..40c5ce2ee 100644 --- a/item_action.h +++ b/item_action.h @@ -2,6 +2,19 @@ #define _ITEM_ACTION_H -enum class ItemAction { DROP, DROP_MULTI, APPLY, EQUIP, UNEQUIP, THROW, LOCK, UNLOCK, REPLACE, GIVE }; +enum class ItemAction { + DROP, + DROP_MULTI, + APPLY, + EQUIP, + UNEQUIP, + THROW, + LOCK, + UNLOCK, + REPLACE, + GIVE, + REMOVE, + CHANGE_NUMBER +}; #endif diff --git a/item_type.h b/item_type.h index 8198c8164..5fd3987f2 100644 --- a/item_type.h +++ b/item_type.h @@ -66,10 +66,9 @@ struct TrapInfo { TrapType SERIAL(trapType); EffectType SERIAL(effectType); bool SERIAL(alwaysVisible); - template - void serialize(Archive& ar, const unsigned int version) { - ar & SVAR(trapType) & SVAR(effectType) & SVAR(alwaysVisible); - } + SERIALIZE_ALL(trapType, effectType, alwaysVisible); + bool operator == (const TrapInfo& o) const { return trapType == o.trapType && effectType == o.effectType && + alwaysVisible == o.alwaysVisible; } }; class ItemType : public EnumVariant void PlayerControl::serialize(Archive& ar, const unsigned int version) { @@ -419,18 +420,6 @@ bool PlayerControl::isTurnBased() { return getControlled(); } -ViewId PlayerControl::getResourceViewId(ResourceId id) const { - switch (id) { - case ResourceId::CORPSE: - case ResourceId::PRISONER_HEAD: - case ResourceId::MANA: return ViewId::MANA; - case ResourceId::GOLD: return ViewId::GOLD; - case ResourceId::WOOD: return ViewId::WOOD_PLANK; - case ResourceId::IRON: return ViewId::IRON_ROCK; - case ResourceId::STONE: return ViewId::ROCK; - } -} - static vector marketItems { {ItemId::POTION, EffectId::HEAL}, {ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::SLEEP)}, @@ -731,9 +720,10 @@ void PlayerControl::handleLibrary(View* view) { typedef CollectiveInfo::Button Button; -optional> PlayerControl::getCostObj(CostInfo cost) const { - if (cost.value > 0 && !CollectiveConfig::getResourceInfo().at(cost.id).dontDisplay) - return make_pair(getResourceViewId(cost.id), cost.value); +static optional> getCostObj(CostInfo cost) { + auto& resourceInfo = CollectiveConfig::getResourceInfo().at(cost.id); + if (cost.value > 0 && !resourceInfo.dontDisplay) + return make_pair(resourceInfo.viewId, cost.value); else return none; } @@ -1085,6 +1075,47 @@ void PlayerControl::fillMinions(CollectiveInfo& info) const { info.minionLimit = getCollective()->getMaxPopulation(); } +static ItemInfo getWorkshopItem(const Workshops::Item& option) { + return CONSTRUCT(ItemInfo, + c.name = option.name; + c.viewId = option.viewId; + c.price = getCostObj(option.cost); + c.unavailable = !option.active; + c.actions = LIST(ItemAction::REMOVE, ItemAction::CHANGE_NUMBER); + c.number = option.number; + ); +} + +void PlayerControl::fillWorkshopInfo(CollectiveInfo& info) const { + info.workshopButtons.clear(); + int index = 0; + int i = 0; + for (auto& elem : getWorkshopInfo()) { + info.workshopButtons.push_back(elem.button); + if (chosenWorkshop == elem.workshopType) { + index = i; + info.workshopButtons.back().active = true; + } + ++i; + } + if (chosenWorkshop) { + info.chosenWorkshop = CollectiveInfo::ChosenWorkshopInfo { + transform2(getCollective()->getWorkshops().getOptions(*chosenWorkshop), getWorkshopItem), + transform2(getCollective()->getWorkshops().getQueued(*chosenWorkshop), getWorkshopItem), + index + }; + } +} + +vector PlayerControl::getWorkshopInfo() const { + return { + {{"Workshop", ViewId::WORKSHOP, false}, WorkshopType::WORKSHOP}, + {{"Forge", ViewId::FORGE, false}, WorkshopType::FORGE}, + {{"Laboratory", ViewId::LABORATORY, false}, WorkshopType::LABORATORY}, + {{"Jeweler", ViewId::JEWELER, false}, WorkshopType::JEWELER}, + }; +} + void PlayerControl::refreshGameInfo(GameInfo& gameInfo) const { gameInfo.singleModel = getGame()->isSingleModel(); gameInfo.villageInfo.villages.clear(); @@ -1109,21 +1140,23 @@ void PlayerControl::refreshGameInfo(GameInfo& gameInfo) const { info.buildings = fillButtons(getBuildInfo()); info.libraryButtons = fillButtons(libraryInfo); fillMinions(info); - info.chosen.reset(); + info.chosenCreature.reset(); if (chosenCreature) if (Creature* c = getCreature(*chosenCreature)) { if (!getChosenTeam()) - info.chosen = {*chosenCreature, getPlayerInfos(getMinionsLike(c))}; + info.chosenCreature = {*chosenCreature, getPlayerInfos(getMinionsLike(c))}; else - info.chosen = {*chosenCreature, getPlayerInfos(getTeams().getMembers(*getChosenTeam())), *getChosenTeam()}; + info.chosenCreature = {*chosenCreature, getPlayerInfos(getTeams().getMembers(*getChosenTeam())), + *getChosenTeam()}; } + fillWorkshopInfo(info); info.monsterHeader = "Minions: " + toString(info.minionCount) + " / " + toString(info.minionLimit); info.enemyGroups = getEnemyGroups(); info.numResource.clear(); for (auto elem : CollectiveConfig::getResourceInfo()) if (!elem.second.dontDisplay) info.numResource.push_back( - {getResourceViewId(elem.first), getCollective()->numResourcePlusDebt(elem.first), elem.second.name}); + {elem.second.viewId, getCollective()->numResourcePlusDebt(elem.first), elem.second.name}); info.warning = ""; gameInfo.time = getCollective()->getGame()->getGlobalTime(); gameInfo.modifiedSquares = gameInfo.totalSquares = 0; @@ -1577,6 +1610,39 @@ void PlayerControl::processInput(View* view, UserInput input) { case UserInputId::DRAW_LEVEL_MAP: view->drawLevelMap(this); break; case UserInputId::DRAW_WORLD_MAP: getGame()->presentWorldmap(); break; case UserInputId::TECHNOLOGY: getTechInfo()[input.get()].butFun(this, view); break; + case UserInputId::WORKSHOP: { + int index = input.get(); + auto info = getWorkshopInfo(); + if (index < 0 || index >= info.size()) + chosenWorkshop = none; + else { + WorkshopType type = info[index].workshopType; + if (chosenWorkshop == type) + chosenWorkshop = none; + else + chosenWorkshop = type; + } + } + break; + case UserInputId::WORKSHOP_ADD: + if (chosenWorkshop) + getCollective()->getWorkshops().queue(*chosenWorkshop, input.get()); + break; + case UserInputId::WORKSHOP_ITEM_ACTION: { + auto& info = input.get(); + if (chosenWorkshop) + switch (info.action) { + case ItemAction::REMOVE: + getCollective()->getWorkshops().unqueue(*chosenWorkshop, info.itemIndex); + break; + case ItemAction::CHANGE_NUMBER: + if (auto number = getView()->getNumber("Choose number of items:", 0, 300, 1)) + getCollective()->getWorkshops().changeNumber(*chosenWorkshop, info.itemIndex, *number); + default: + break; + } + } + break; case UserInputId::CREATURE_GROUP_BUTTON: if (Creature* c = getCreature(input.get())) if (!chosenCreature || getChosenTeam() || !getCreature(*chosenCreature) || diff --git a/player_control.h b/player_control.h index a8980cacc..18982313b 100644 --- a/player_control.h +++ b/player_control.h @@ -77,7 +77,6 @@ class PlayerControl : public CreatureView, public CollectiveControl { vector requirements; }; static vector getRoomInfo(); - static vector getWorkshopInfo(); SERIALIZATION_DECL(PlayerControl); @@ -141,14 +140,13 @@ class PlayerControl : public CreatureView, public CollectiveControl { void handleSelection(Vec2 pos, const BuildInfo&, bool rectangle, bool deselectOnly = false); vector fillButtons(const vector& buildInfo) const; VillageInfo::Village getVillageInfo(const Collective* enemy) const; + void fillWorkshopInfo(CollectiveInfo&) const; vector getBuildInfo() const; static vector getBuildInfo(TribeId); static vector workshopInfo; static vector libraryInfo; static vector minionsInfo; - ViewId getResourceViewId(CollectiveResourceId id) const; - optional> getCostObj(CostInfo) const; CostInfo getRoomCost(SquareType, CostInfo baseCost, double exponent) const; typedef CollectiveInfo::TechButton TechButton; @@ -160,6 +158,11 @@ class PlayerControl : public CreatureView, public CollectiveControl { function butFun; }; vector getTechInfo() const; + struct WorkshopInfo { + CollectiveInfo::WorkshopButton button; + WorkshopType workshopType; + }; + vector getWorkshopInfo() const; int getImpCost() const; bool canBuildDoor(Position) const; @@ -213,6 +216,7 @@ class PlayerControl : public CreatureView, public CollectiveControl { int SERIAL(startImpNum) = -1; bool SERIAL(payoutWarning) = false; optional::Id> chosenCreature; + optional chosenWorkshop; optional getChosenTeam() const; void setChosenTeam(optional); optional chosenTeam; diff --git a/user_input.h b/user_input.h index bae6f3cfd..e20c8dccc 100644 --- a/user_input.h +++ b/user_input.h @@ -62,6 +62,9 @@ enum class UserInputId { SELECT_TEAM, ACTIVATE_TEAM, TECHNOLOGY, + WORKSHOP, + WORKSHOP_ADD, + WORKSHOP_ITEM_ACTION, VILLAGE_ACTION, GO_TO_VILLAGE, PAY_RANSOM, @@ -126,6 +129,12 @@ struct EquipmentActionInfo { SERIALIZE_ALL(creature, ids, slot, action); }; +struct WorkshopQueuedActionInfo { + int SERIAL(itemIndex); + ItemAction SERIAL(action); + SERIALIZE_ALL(itemIndex, action); +}; + struct RenameActionInfo { UniqueEntity::Id SERIAL(creature); string SERIAL(name); @@ -136,7 +145,7 @@ enum class SpellId; class UserInput : public EnumVariant::Id, UniqueEntity::Id, InventoryItemInfo, Vec2, TeamCreatureInfo, SpellId, VillageActionInfo, - TaskActionInfo, EquipmentActionInfo, RenameActionInfo), + TaskActionInfo, EquipmentActionInfo, RenameActionInfo, WorkshopQueuedActionInfo), ASSIGN(BuildingInfo, UserInputId::BUILD, UserInputId::LIBRARY, @@ -159,6 +168,8 @@ class UserInput : public EnumVariant { diff --git a/window_view.cpp b/window_view.cpp index 96090dcd1..e6145897f 100644 --- a/window_view.cpp +++ b/window_view.cpp @@ -472,7 +472,7 @@ vector WindowView::getAllGuiElems() { return concat({mapGui}, extractRefs(tempGuiElems)); vector ret = concat(extractRefs(tempGuiElems), extractRefs(blockingElems)); if (gameReady) - ret = concat(concat({mapGui}, ret), {minimapDecoration.get()}); + ret = concat({mapGui, minimapDecoration.get()}, ret); return ret; } From 5e1f3c14cccb023140189081253d4bb751be9e11 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Fri, 5 Aug 2016 14:35:28 +0200 Subject: [PATCH 008/148] Refactor list UI --- gui_builder.cpp | 190 ++++++++++++++++++++++-------------------------- gui_elem.cpp | 180 +++++++++++++++++++++++++-------------------- gui_elem.h | 10 ++- util.cpp | 4 + util.h | 1 + window_view.cpp | 4 +- 6 files changed, 203 insertions(+), 186 deletions(-) diff --git a/gui_builder.cpp b/gui_builder.cpp index eb42c8350..dd4d7dfa7 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -153,15 +153,15 @@ GuiFactory::ListBuilder GuiBuilder::drawButtons(vector b activeGroup = none; } }; - vector line; - line.push_back(gui.viewObject(buttons[i].viewId)); + auto line = gui.getListBuilder(); + line.addElem(gui.viewObject(buttons[i].viewId), 35); char hotkey = buttons[i].hotkeyOpensGroup ? buttons[i].hotkey : 0; - line.push_back(gui.stack( + line.addElemAuto(gui.stack( gui.uiHighlightConditional([=] { return activeGroup == lastGroup;}), gui.label(lastGroup, colors[ColorId::WHITE], hotkey))); elems.addElem(gui.stack( gui.buttonChar(buttonFun, buttons[i].hotkeyOpensGroup ? buttons[i].hotkey : 0), - gui.horizontalList(std::move(line), 35))); + line.buildHorizontalList())); } if (buttons[i].groupName.empty()) elems.addElem(getButtonLine(buttons[i], i, tab)); @@ -186,21 +186,21 @@ PGuiElem GuiBuilder::drawTechnology(CollectiveInfo& info) { auto lines = drawButtons(info.libraryButtons, CollectiveTab::TECHNOLOGY); lines.addSpace(legendLineHeight / 2); for (int i : All(info.techButtons)) { - vector line; - line.push_back(gui.viewObject(info.techButtons[i].viewId)); - line.push_back(gui.label(info.techButtons[i].name, colors[ColorId::WHITE], info.techButtons[i].hotkey)); + auto line = gui.getListBuilder(); + line.addElem(gui.viewObject(info.techButtons[i].viewId), 35); + line.addElemAuto(gui.label(info.techButtons[i].name, colors[ColorId::WHITE], info.techButtons[i].hotkey)); lines.addElem(gui.stack( gui.buttonChar([this, i] { closeOverlayWindows(); getButtonCallback(UserInput(UserInputId::TECHNOLOGY, i))(); }, info.techButtons[i].hotkey), - gui.horizontalList(std::move(line), 35))); + line.buildHorizontalList())); } lines.addSpace(legendLineHeight / 2); for (int i : All(info.workshopButtons)) { - auto line = gui.getListBuilder(35); - line.addElem(gui.viewObject(info.workshopButtons[i].viewId)); - line.addElem(gui.label(info.workshopButtons[i].name, colors[ColorId::WHITE])); + auto line = gui.getListBuilder(); + line.addElem(gui.viewObject(info.workshopButtons[i].viewId), 35); + line.addElemAuto(gui.label(info.workshopButtons[i].name, colors[ColorId::WHITE])); PGuiElem elem = line.buildHorizontalList(); if (info.workshopButtons[i].active) elem = gui.stack( @@ -236,10 +236,10 @@ PGuiElem GuiBuilder::drawKeeperHelp() { "press mouse wheel: level map", "", "follow the orange hints :-)"}; - vector lines; + auto lines = gui.getListBuilder(legendLineHeight); for (string line : helpText) - lines.push_back(gui.label(line, colors[ColorId::LIGHT_BLUE])); - keeperHelp = gui.verticalList(std::move(lines), legendLineHeight); + lines.addElem(gui.label(line, colors[ColorId::LIGHT_BLUE])); + keeperHelp = lines.buildVerticalList(); } return gui.external(keeperHelp.get()); } @@ -276,26 +276,24 @@ PGuiElem GuiBuilder::drawBottomBandInfo(GameInfo& gameInfo) { CollectiveInfo& info = gameInfo.collectiveInfo; GameSunlightInfo& sunlightInfo = gameInfo.sunlightInfo; if (!bottomBandCache) { - vector topLine; + auto topLine = gui.getListBuilder(resourceSpace); for (int i : All(info.numResource)) { - vector res; - res.push_back(gui.viewObject(info.numResource[i].viewId)); - res.push_back(gui.labelFun([&info, i] { return toString(info.numResource[i].count); }, + auto res = gui.getListBuilder(); + res.addElem(gui.viewObject(info.numResource[i].viewId), 30); + res.addElemAuto(gui.labelFun([&info, i] { return toString(info.numResource[i].count); }, [&info, i] { return info.numResource[i].count >= 0 ? colors[ColorId::WHITE] : colors[ColorId::RED]; })); - topLine.push_back(gui.stack(getHintCallback({info.numResource[i].name}), - gui.horizontalList(std::move(res), 30))); + topLine.addElem(gui.stack(getHintCallback({info.numResource[i].name}), res.buildHorizontalList())); } - vector bottomLine; - bottomLine.push_back(getTurnInfoGui(gameInfo.time)); - bottomLine.push_back(getSunlightInfoGui(sunlightInfo)); - bottomLine.push_back(gui.labelFun([&gameInfo] { + auto bottomLine = gui.getListBuilder(140); + bottomLine.addElem(getTurnInfoGui(gameInfo.time)); + bottomLine.addElem(getSunlightInfoGui(sunlightInfo)); + bottomLine.addElem(gui.labelFun([&gameInfo] { return "population: " + toString(gameInfo.collectiveInfo.minionCount) + " / " + toString(gameInfo.collectiveInfo.minionLimit); })); - int numTop = topLine.size(); - int numBottom = bottomLine.size(); - bottomBandCache = gui.verticalList(makeVec( - gui.centerHoriz(gui.horizontalList(std::move(topLine), resourceSpace), numTop * resourceSpace), - gui.centerHoriz(gui.horizontalList(std::move(bottomLine), 140, 3), numBottom * 140)), 28); + bottomBandCache = gui.getListBuilder(28) + .addElem(gui.centerHoriz(topLine.buildHorizontalList())) + .addElem(gui.centerHoriz(bottomLine.buildHorizontalList())) + .buildVerticalList(); } return gui.external(bottomBandCache.get()); } @@ -358,22 +356,23 @@ PGuiElem GuiBuilder::drawRightBandInfo(GameInfo& info) { PGuiElem main = gui.stack(std::move(tabs)); main = gui.margins(std::move(main), 15, 15, 15, 5); int numButtons = buttons.size(); + auto buttonList = gui.getListBuilder(50); + for (auto& elem : buttons) + buttonList.addElem(std::move(elem)); PGuiElem butGui = gui.margins( - gui.centerHoriz(gui.horizontalList(std::move(buttons), 50), numButtons * 50), 0, 5, 9, 5); - vector bottomLine; - bottomLine.push_back(gui.stack( - gui.horizontalList(makeVec( - gui.label("speed:"), - gui.labelFun([this] { return getCurrentGameSpeedName();}, - [this] { return colors[clock->isPaused() ? ColorId::RED : ColorId::WHITE]; })), 60), - gui.button([&] { gameSpeedDialogOpen = !gameSpeedDialogOpen; }))); + gui.centerHoriz(buttonList.buildHorizontalList()), 0, 5, 9, 5); + auto bottomLine = gui.getListBuilder(); + bottomLine.addElem(gui.stack( + gui.getListBuilder() + .addElemAuto(gui.labelFun([this] { return getCurrentGameSpeedName();}, + [this] { return colors[clock->isPaused() ? ColorId::RED : ColorId::WHITE]; })).buildHorizontalList(), + gui.button([&] { gameSpeedDialogOpen = !gameSpeedDialogOpen; })), 160); int modifiedSquares = info.modifiedSquares; int totalSquares = info.totalSquares; - bottomLine.push_back( + bottomLine.addElemAuto( gui.labelFun([=]()->string { return "FPS " + toString(fpsCounter.getFps()) + " / " + toString(upsCounter.getFps())/* + " SMOD " + toString(modifiedSquares) + "/" + toString(totalSquares)*/; }, colors[ColorId::WHITE])); - PGuiElem bottomElem = gui.horizontalList(std::move(bottomLine), 160); - main = gui.margin(gui.margins(std::move(bottomElem), 25, 0, 0, 0), + main = gui.margin(gui.leftMargin(25, bottomLine.buildHorizontalList()), std::move(main), 18, gui.BOTTOM); rightBandInfoCache = gui.margin(std::move(butGui), std::move(main), 55, gui.TOP); } @@ -406,9 +405,9 @@ void GuiBuilder::drawGameSpeedDialog(vector& overlays) { vector lines; int keyMargin = 95; lines.push_back(gui.stack( - gui.horizontalList(makeVec( - gui.label("pause"), - gui.label("[space]")), keyMargin), + gui.getListBuilder(keyMargin) + .addElem(gui.label("pause")) + .addElem(gui.label("[space]")).buildHorizontalList(), gui.button([=] { if (clock->isPaused()) clock->cont(); @@ -418,9 +417,9 @@ void GuiBuilder::drawGameSpeedDialog(vector& overlays) { }, gui.getKey(SDL::SDLK_SPACE)))); for (GameSpeed speed : ENUM_ALL(GameSpeed)) { Color color = colors[speed == gameSpeed ? ColorId::GREEN : ColorId::WHITE]; - lines.push_back(gui.stack(gui.horizontalList(makeVec( - gui.label(getGameSpeedName(speed), color), - gui.label("'" + string(1, getHotkeyChar(speed)) + "' ", color)), keyMargin), + lines.push_back(gui.stack(gui.getListBuilder(keyMargin) + .addElem(gui.label(getGameSpeedName(speed), color)) + .addElem(gui.label("'" + string(1, getHotkeyChar(speed)) + "' ", color)).buildHorizontalList(), gui.button([=] { gameSpeed = speed; gameSpeedDialogOpen = false; clock->cont();}, gui.getKey(getHotkey(speed))))); } @@ -482,16 +481,14 @@ vector GuiBuilder::drawPlayerAttributes(const vector topLine = drawPlayerAttributes(info.attributes); - vector bottomLine; - bottomLine.push_back(getTurnInfoGui(gameInfo.time)); - bottomLine.push_back(getSunlightInfoGui(gameInfo.sunlightInfo)); - int numTop = topLine.size(); - int numBottom = bottomLine.size(); - return gui.verticalList(makeVec( - gui.centerHoriz(gui.horizontalList(std::move(topLine), resourceSpace), numTop * resourceSpace), - gui.centerHoriz(gui.horizontalList(std::move(bottomLine), 140, 3), numBottom * 140)), 28); + return gui.getListBuilder(28) + .addElem(gui.centerHoriz(gui.horizontalList( + drawPlayerAttributes(gameInfo.playerInfo.attributes), resourceSpace))) + .addElem(gui.centerHoriz(gui.getListBuilder(140) + .addElem(getTurnInfoGui(gameInfo.time)) + .addElem(getSunlightInfoGui(gameInfo.sunlightInfo)) + .buildHorizontalList())) + .buildVerticalList(); } static int viewObjectWidth = 27; @@ -833,21 +830,19 @@ PGuiElem GuiBuilder::drawPlayerInventory(PlayerInfo& info) { list.addSpace(); if (info.team.size() > 1) { const int numPerLine = 6; - vector widths { 60 }; - vector currentLine = makeVec( - gui.label("Team: ", colors[ColorId::WHITE])); + auto currentLine = gui.getListBuilder(); + currentLine.addElem(gui.label("Team: ", colors[ColorId::WHITE]), 60); for (auto& elem : info.team) { - currentLine.push_back(gui.stack( + currentLine.addElem(gui.stack( gui.viewObject(elem.viewId), - gui.label(toString(elem.expLevel), 12))); - widths.push_back(30); - if (currentLine.size() >= numPerLine) { - list.addElem(gui.horizontalList(std::move(currentLine), widths)); - widths.clear(); + gui.label(toString(elem.expLevel), 12)), 30); + if (currentLine.getLength() >= numPerLine) { + list.addElem(currentLine.buildHorizontalList()); + currentLine.clear(); } } - if (!currentLine.empty()) - list.addElem(gui.horizontalList(std::move(currentLine), widths)); + if (!currentLine.isEmpty()) + list.addElem(currentLine.buildHorizontalList()); list.addSpace(); } for (auto& elem : drawSkillsList(info)) @@ -976,21 +971,20 @@ PGuiElem GuiBuilder::drawMinions(CollectiveInfo& info) { list.addElem(gui.label(info.monsterHeader, colors[ColorId::WHITE])); for (int i : All(info.minionGroups)) { auto& elem = info.minionGroups[i]; - vector line; + auto line = gui.getListBuilder(); vector widths; - line.push_back(gui.viewObject(elem.viewId)); - widths.push_back(40); - line.push_back(gui.label(toString(elem.count) + " " + elem.name, colors[ColorId::WHITE])); + line.addElem(gui.viewObject(elem.viewId), 40); + PGuiElem tmp = gui.label(toString(elem.count) + " " + elem.name, colors[ColorId::WHITE]); if (elem.highlight) - line.back() = gui.stack(gui.uiHighlight(), std::move(line.back())); - widths.push_back(200); + tmp = gui.stack(gui.uiHighlight(), std::move(tmp)); + line.addElem(std::move(tmp), 200); list.addElem(gui.leftMargin(20, gui.stack( gui.button(getButtonCallback({UserInputId::CREATURE_GROUP_BUTTON, elem.creatureId})), gui.dragSource({DragContentId::CREATURE_GROUP, elem.creatureId}, [=]{ return gui.getListBuilder(10) .addElemAuto(gui.label(toString(elem.count) + " ")) .addElem(gui.viewObject(elem.viewId)).buildHorizontalList();}), - gui.horizontalList(std::move(line), widths)))); + line.buildHorizontalList()))); } list.addElem(gui.label("Teams: ", colors[ColorId::WHITE])); list.addElemAuto(drawTeams(info)); @@ -1106,7 +1100,7 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& for (int i : All(queued)) { auto& elem = queued[i]; auto line = gui.getListBuilder(); - line.addElemAuto(gui.stack( + line.addMiddleElem(gui.stack( gui.uiHighlightMouseOver(colors[ColorId::GREEN]), gui.buttonRect([=] (Rectangle bounds) { auto lines = gui.getListBuilder(legendLineHeight); @@ -1543,18 +1537,13 @@ PGuiElem GuiBuilder::getHighlight(MenuType type, const string& label, int height PGuiElem GuiBuilder::drawListGui(const string& title, const vector& options, MenuType menuType, int* height, int* highlight, int* choice) { - vector lines; - vector heights; + auto lines = gui.getListBuilder(listLineHeight); int leftMargin = 30; if (!title.empty()) { - lines.push_back(gui.leftMargin(leftMargin, gui.label(capitalFirst(title), colors[ColorId::WHITE]))); - heights.push_back(listLineHeight); - lines.push_back(gui.empty()); - heights.push_back(listLineHeight); - } else { - lines.push_back(gui.empty()); - heights.push_back(listLineHeight / 2); - } + lines.addElem(gui.leftMargin(leftMargin, gui.label(capitalFirst(title), colors[ColorId::WHITE]))); + lines.addSpace(); + } else + lines.addSpace(listLineHeight / 2); int numActive = 0; int secColumnWidth = 0; int columnWidth = 300; @@ -1578,13 +1567,10 @@ PGuiElem GuiBuilder::drawListGui(const string& title, const vector& op color = getMessageColor(*p); vector label1 = getMultiLine(options[i].getText(), color, menuType, columnWidth); if (options.size() == 1 && label1.size() > 1) { // hacky way of checking that we display a wall of text - append(heights, vector(label1.size(), listLineHeight)); for (auto& line : label1) - line = gui.margins(std::move(line), leftMargin, 0, 0, 0); - append(lines, std::move(label1)); + lines.addElem(gui.leftMargin(leftMargin, std::move(line))); break; } - heights.push_back((label1.size() - 1) * listBrokenLineHeight + listLineHeight); PGuiElem line; if (menuType != MenuType::MAIN) line = gui.verticalList(std::move(label1), listBrokenLineHeight); @@ -1596,21 +1582,21 @@ PGuiElem GuiBuilder::drawListGui(const string& title, const vector& op if (!options[i].getSecondColumn().empty()) line = gui.horizontalList(makeVec(std::move(line), gui.label(options[i].getSecondColumn())), columnWidth + 80); - lines.push_back(menuElemMargins(std::move(line))); + line = menuElemMargins(std::move(line)); if (highlight && options[i].getMod() == ListElem::NORMAL) { - lines.back() = gui.stack(makeVec( + line = gui.stack(makeVec( gui.button([=]() { *choice = numActive; }), - std::move(lines.back()), + std::move(line), gui.mouseHighlight(getHighlight(menuType, options[i].getText(), listLineHeight), numActive, highlight))); ++numActive; } - lines.back() = gui.margins(std::move(lines.back()), leftMargin, 0, 0, 0); + line = gui.margins(std::move(line), leftMargin, 0, 0, 0); + lines.addElemAuto(std::move(line)); } - *height = accumulate(heights.begin(), heights.end(), 0); if (menuType != MenuType::MAIN) - return gui.verticalList(std::move(lines), heights); + return lines.buildVerticalList(); else - return gui.verticalListFit(std::move(lines), 0.0); + return lines.buildVerticalListFit(); } static optional getMoraleIcon(double morale) { @@ -1736,7 +1722,7 @@ PGuiElem GuiBuilder::drawActivityButton(const PlayerInfo& minion) { exit = true; }; tasks.addElem(GuiFactory::ListBuilder(gui) - .addElemAuto(gui.stack( + .addMiddleElem(gui.stack( gui.button(buttonFun), gui.uiHighlightConditional([=]{ return task.current; }), gui.label(getTaskText(task.task), colors[getTaskColor(task)]))) @@ -2263,11 +2249,11 @@ vector GuiBuilder::drawRecruitList(const vector& creatur bool canAfford = elem.any.cost->second <= budget; ColorId color = canAfford ? ColorId::WHITE : ColorId::GRAY; lines.push_back(gui.stack( - gui.keyHandler([callback] { callback(none); }, {gui.getKey(SDL::SDLK_ESCAPE), gui.getKey(SDL::SDLK_RETURN)}), - canAfford ? gui.button([callback, elem] { callback(elem.any.uniqueId); }) : gui.empty(), - gui.leftMargin(25, gui.stack( - canAfford ? gui.mouseHighlight2(gui.highlight(listLineHeight)) : gui.empty(), - GuiFactory::ListBuilder(gui) + gui.keyHandler([callback] { callback(none); }, {gui.getKey(SDL::SDLK_ESCAPE), gui.getKey(SDL::SDLK_RETURN)}), + canAfford ? gui.button([callback, elem] { callback(elem.any.uniqueId); }) : gui.empty(), + gui.leftMargin(25, gui.stack( + canAfford ? gui.mouseHighlight2(gui.highlight(listLineHeight)) : gui.empty(), + GuiFactory::ListBuilder(gui) .addElemAuto(gui.rightMargin(10, gui.label(toString(elem.count), colors[color]))) .addElem(gui.viewObject(elem.viewId), 50) .addElem(gui.label("level " + toString(elem.any.expLevel), colors[color]), 50) diff --git a/gui_elem.cpp b/gui_elem.cpp index eb8dcc180..335aceafc 100644 --- a/gui_elem.cpp +++ b/gui_elem.cpp @@ -934,36 +934,39 @@ PGuiElem GuiFactory::keyHandler(function fun, vector key, bo return PGuiElem(new KeyHandler2(fun, key, capture)); } -class VerticalList : public GuiLayout { +class ElemList : public GuiLayout { public: - VerticalList(vector e, vector h, int alignBack = 0) - : GuiLayout(std::move(e)), heights(h), numAlignBack(alignBack) { + ElemList(vector e, vector h, int alignBack, bool middleEl) + : GuiLayout(std::move(e)), heights(h), numAlignBack(alignBack), middleElem(middleEl) { //CHECK(heights.size() > 0); CHECK(heights.size() == elems.size()); + int sum = 0; + for (int h : heights) { + accuHeights.push_back(sum); + sum += h; + } + accuHeights.push_back(sum); } - Rectangle getBackPosition(int num) { - int height = std::accumulate(heights.begin() + num + 1, heights.end(), 0); - return Rectangle(getBounds().bottomLeft() - Vec2(0, height + heights[num]), getBounds().bottomRight() - - Vec2(0, height)); + Range getBackPosition(int num, Range bounds) { + int height = accuHeights[heights.size()] - accuHeights[num + 1]; + return Range(bounds.getEnd() - height - heights[num], bounds.getEnd() - height); } - virtual Rectangle getElemBounds(int num) override { - if (num >= heights.size() - numAlignBack) - return getBackPosition(num); - int height = std::accumulate(heights.begin(), heights.begin() + num, 0); - return Rectangle(getBounds().topLeft() + Vec2(0, height), getBounds().topRight() - + Vec2(0, height + heights[num])); + Range getMiddlePosition(Range bounds) { + CHECK(heights.size() > numAlignBack); + int height1 = accuHeights[heights.size() - numAlignBack - 1]; + int height2 = accuHeights[heights.size()] - accuHeights[heights.size() - numAlignBack]; + return Range(bounds.getStart() + height1, bounds.getEnd() - height2); } - void renderPart(Renderer& r, int scrollPos) { - int totHeight = 0; - for (int i : Range(scrollPos, heights.size())) { - if (totHeight + elems[i]->getBounds().height() > getBounds().height()) - break; - elems[i]->render(r); - totHeight += elems[i]->getBounds().height(); - } + Range getElemPosition(int num, Range bounds) { + if (middleElem && num == heights.size() - numAlignBack - 1) + return getMiddlePosition(bounds); + if (num >= heights.size() - numAlignBack) + return getBackPosition(num, bounds); + int height = accuHeights[num]; + return Range(bounds.getStart() + height, bounds.getStart() + height + heights[num]); } int getLastTopElem(int myHeight) { @@ -986,6 +989,40 @@ class VerticalList : public GuiLayout { return ret; } + int getSize() { + return heights.size(); + } + + int getTotalHeight() { + return getElemsHeight(getSize()); + } + + + protected: + vector heights; + vector accuHeights; + int numAlignBack; + bool middleElem; +}; + +class VerticalList : public ElemList { + public: + using ElemList::ElemList; + + void renderPart(Renderer& r, int scrollPos) { + int totHeight = 0; + for (int i : Range(scrollPos, heights.size())) { + if (totHeight + elems[i]->getBounds().height() > getBounds().height()) + break; + elems[i]->render(r); + totHeight += elems[i]->getBounds().height(); + } + } + + virtual Rectangle getElemBounds(int num) override { + return Rectangle(getBounds().getXRange(), getElemPosition(num, getBounds().getYRange())); + } + optional getPreferredWidth() override { for (auto& elem : elems) if (auto width = elem->getPreferredWidth()) @@ -996,20 +1033,38 @@ class VerticalList : public GuiLayout { optional getPreferredHeight() override { return getTotalHeight(); } +}; - int getSize() { - return heights.size(); +PGuiElem GuiFactory::verticalList(vector e, int height) { + vector heights(e.size(), height); + return PGuiElem(new VerticalList(std::move(e), heights, 0, false)); +} + +class HorizontalList : public ElemList { + public: + using ElemList::ElemList; + + virtual Rectangle getElemBounds(int num) override { + return Rectangle(getElemPosition(num, getBounds().getXRange()), getBounds().getYRange()); } - int getTotalHeight() { - return getElemsHeight(getSize()); + optional getPreferredHeight() override { + for (auto& elem : elems) + if (auto height = elem->getPreferredHeight()) + return height; + return none; } - protected: - vector heights; - int numAlignBack; + optional getPreferredWidth() override { + return getTotalHeight(); + } }; +PGuiElem GuiFactory::horizontalList(vector e, int height) { + vector heights(e.size(), height); + return PGuiElem(new HorizontalList(std::move(e), heights, 0, false)); +} + GuiFactory::ListBuilder GuiFactory::getListBuilder(int defaultSize) { return ListBuilder(*this, defaultSize); } @@ -1018,6 +1073,7 @@ GuiFactory::ListBuilder::ListBuilder(GuiFactory& g, int defSz) : gui(g), default GuiFactory::ListBuilder& GuiFactory::ListBuilder::addElem(PGuiElem elem, int size) { CHECK(!backElems); + CHECK(!middleElem); if (size == 0) { CHECK(defaultSize > 0); size = defaultSize; @@ -1028,6 +1084,8 @@ GuiFactory::ListBuilder& GuiFactory::ListBuilder::addElem(PGuiElem elem, int siz } GuiFactory::ListBuilder& GuiFactory::ListBuilder::addSpace(int size) { + CHECK(!backElems); + CHECK(!middleElem); if (size == 0) { CHECK(defaultSize > 0); size = defaultSize; @@ -1039,12 +1097,19 @@ GuiFactory::ListBuilder& GuiFactory::ListBuilder::addSpace(int size) { GuiFactory::ListBuilder& GuiFactory::ListBuilder::addElemAuto(PGuiElem elem) { CHECK(!backElems); + CHECK(!middleElem); int size = -1; elems.push_back(std::move(elem)); sizes.push_back(size); return *this; } +GuiFactory::ListBuilder& GuiFactory::ListBuilder::addMiddleElem(PGuiElem elem) { + addElem(std::move(elem), 1234); + middleElem = true; + return *this; +} + GuiFactory::ListBuilder& GuiFactory::ListBuilder::addBackElemAuto(PGuiElem elem) { ++backElems; int size = -1; @@ -1068,6 +1133,10 @@ int GuiFactory::ListBuilder::getSize() const { return std::accumulate(sizes.begin(), sizes.end(), 0); } +int GuiFactory::ListBuilder::getLength() const { + return sizes.size(); +} + bool GuiFactory::ListBuilder::isEmpty() const { return sizes.empty(); } @@ -1086,16 +1155,14 @@ PGuiElem GuiFactory::ListBuilder::buildVerticalList() { for (int i : All(sizes)) if (sizes[i] == -1) sizes[i] = *elems[i]->getPreferredHeight(); - PGuiElem ret = gui.verticalList(std::move(elems), sizes, backElems); - return ret; + return PGuiElem(new VerticalList(std::move(elems), sizes, backElems, middleElem)); } PGuiElem GuiFactory::ListBuilder::buildHorizontalList() { for (int i : All(sizes)) if (sizes[i] == -1) sizes[i] = *elems[i]->getPreferredWidth(); - PGuiElem ret = gui.horizontalList(std::move(elems), sizes, backElems); - return ret; + return PGuiElem(new HorizontalList(std::move(elems), sizes, backElems, middleElem)); } PGuiElem GuiFactory::ListBuilder::buildHorizontalListFit() { @@ -1103,13 +1170,9 @@ PGuiElem GuiFactory::ListBuilder::buildHorizontalListFit() { return ret; } -PGuiElem GuiFactory::verticalList(vector e, vector heights, int numAlignBottom) { - return PGuiElem(new VerticalList(std::move(e), heights, numAlignBottom)); -} - -PGuiElem GuiFactory::verticalList(vector e, int height, int numAlignBottom) { - vector heights(e.size(), height); - return PGuiElem(new VerticalList(std::move(e), heights, numAlignBottom)); +PGuiElem GuiFactory::ListBuilder::buildVerticalListFit() { + PGuiElem ret = gui.verticalListFit(std::move(elems), 0); + return ret; } class VerticalListFit : public GuiLayout { @@ -1154,45 +1217,6 @@ PGuiElem GuiFactory::horizontalListFit(vector e, double spacing) { return PGuiElem(new HorizontalListFit(std::move(e), spacing)); } -class HorizontalList : public VerticalList { - public: - using VerticalList::VerticalList; - - Rectangle getBackPosition(int num) { - int height = std::accumulate(heights.begin() + num + 1, heights.end(), 0); - return Rectangle(getBounds().topRight() - Vec2(height + heights[num], 0), getBounds().bottomRight() - - Vec2(height, 0)); - } - - virtual Rectangle getElemBounds(int num) override { - if (num >= heights.size() - numAlignBack) - return getBackPosition(num); - int height = std::accumulate(heights.begin(), heights.begin() + num, 0); - return Rectangle(getBounds().topLeft() + Vec2(height, 0), getBounds().bottomLeft() - + Vec2(height + heights[num], 0)); - } - - optional getPreferredHeight() override { - for (auto& elem : elems) - if (auto height = elem->getPreferredHeight()) - return height; - return none; - } - - optional getPreferredWidth() override { - return getTotalHeight(); - } -}; - -PGuiElem GuiFactory::horizontalList(vector e, vector widths, int numAlignRight) { - return PGuiElem(new HorizontalList(std::move(e), widths, numAlignRight)); -} - -PGuiElem GuiFactory::horizontalList(vector e, int width, int numAlignRight) { - vector widths(e.size(), width); - return horizontalList(std::move(e), widths, numAlignRight); -} - class VerticalAspect : public GuiLayout { public: VerticalAspect(PGuiElem e, double r) : GuiLayout(makeVec(std::move(e))), ratio(r) {} diff --git a/gui_elem.h b/gui_elem.h index 34ac17b51..66f2b5651 100644 --- a/gui_elem.h +++ b/gui_elem.h @@ -92,10 +92,13 @@ class GuiFactory { ListBuilder& addElemAuto(PGuiElem); ListBuilder& addBackElemAuto(PGuiElem); ListBuilder& addBackElem(PGuiElem, int size = 0); + ListBuilder& addMiddleElem(PGuiElem); PGuiElem buildVerticalList(); + PGuiElem buildVerticalListFit(); PGuiElem buildHorizontalList(); PGuiElem buildHorizontalListFit(); int getSize() const; + int getLength() const; bool isEmpty() const; vector& getAllElems(); void clear(); @@ -106,13 +109,12 @@ class GuiFactory { vector sizes; int defaultSize = 0; int backElems = 0; + bool middleElem = false; }; ListBuilder getListBuilder(int defaultSize = 0); - PGuiElem verticalList(vector, int elemHeight, int numAlignBottom = 0); - PGuiElem verticalList(vector, vector elemHeight, int numAlignBottom = 0); + PGuiElem verticalList(vector, int elemHeight); PGuiElem verticalListFit(vector, double spacing); - PGuiElem horizontalList(vector, int elemWidth, int numAlignRight = 0); - PGuiElem horizontalList(vector, vector elemWidth, int numAlignRight = 0); + PGuiElem horizontalList(vector, int elemHeight); PGuiElem horizontalListFit(vector, double spacing = 0); PGuiElem verticalAspect(PGuiElem, double ratio); PGuiElem empty(); diff --git a/util.cpp b/util.cpp index 22de83450..094112a5e 100644 --- a/util.cpp +++ b/util.cpp @@ -490,6 +490,10 @@ Rectangle::Rectangle(Vec2 p, Vec2 k) : px(p.x), py(p.y), kx(k.x), ky(k.y), w(k.x CHECK(k.y > p.y) << p << " " << k; } +Rectangle::Rectangle(Range xRange, Range yRange) + : Rectangle(xRange.getStart(), yRange.getStart(), xRange.getEnd(), yRange.getEnd()) { +} + Rectangle::Iter::Iter(int x1, int y1, int px1, int py1, int kx1, int ky1) : pos(x1, y1), px(px1), py(py1), kx(kx1), ky(ky1) {} Vec2 Rectangle::randomVec2() const { diff --git a/util.h b/util.h index 7b73ef624..24e9b02f6 100644 --- a/util.h +++ b/util.h @@ -234,6 +234,7 @@ class Rectangle { Rectangle(Vec2 dim); Rectangle(int px, int py, int kx, int ky); Rectangle(Vec2 p, Vec2 k); + Rectangle(Range xRange, Range yRange); static Rectangle boundingBox(const vector& v); static Rectangle centered(Vec2 center, int radius); diff --git a/window_view.cpp b/window_view.cpp index e6145897f..19af65f33 100644 --- a/window_view.cpp +++ b/window_view.cpp @@ -143,8 +143,8 @@ void WindowView::mapCreatureClickFun(UniqueEntity::Id id) { if (isKeyPressed(SDL::SDL_SCANCODE_LCTRL) || isKeyPressed(SDL::SDL_SCANCODE_RCTRL)) { inputQueue.push(UserInput(UserInputId::CREATURE_BUTTON2, id)); } else { - inputQueue.push(UserInput(UserInputId::CREATURE_BUTTON, id)); guiBuilder.setCollectiveTab(CollectiveTab::MINIONS); + inputQueue.push(UserInput(UserInputId::CREATURE_BUTTON, id)); } } @@ -783,7 +783,7 @@ optional WindowView::chooseItem(const vector& items, double* scro int menuHeight = lines.size() * guiBuilder.getStandardLineHeight() + 30; PGuiElem menu = gui.miniWindow(gui.margins( gui.scrollable(gui.verticalList(std::move(lines), guiBuilder.getStandardLineHeight()), scrollPos), - 15, 15, 15, 15), [&retVal] { retVal = optional(none); }); + 15), [&retVal] { retVal = optional(none); }); PGuiElem bg2 = gui.darken(); bg2->setBounds(renderer.getSize()); while (1) { From 71b538bb8272e4ddc6d6c10653d376bc58b9a2de Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Fri, 5 Aug 2016 21:13:20 +0200 Subject: [PATCH 009/148] Control workshops through workshop UI --- attack_trigger.h | 6 ++- collective.cpp | 100 +++++++++++------------------------ collective.h | 13 ++--- collective_builder.cpp | 8 +-- collective_builder.h | 9 ++-- collective_config.cpp | 91 ++++++++++++++++++-------------- collective_config.h | 10 +++- collective_control.cpp | 5 ++ collective_control.h | 2 +- creature.h | 1 + enemy_factory.cpp | 3 +- enemy_factory.h | 1 + game.cpp | 4 +- game_info.h | 4 +- gui_builder.cpp | 7 ++- gui_elem.cpp | 9 ++++ gui_elem.h | 1 + item_factory.cpp | 71 ------------------------- item_factory.h | 4 -- level.cpp | 49 ++++++++--------- level.h | 4 +- monster_ai.cpp | 1 + player_control.cpp | 14 ++--- player_control.h | 2 +- square_factory.cpp | 1 + square_factory.h | 3 +- task.cpp | 1 + technology.cpp | 1 + util.h | 80 +++++++++++++++------------- village_behaviour.cpp | 11 ++-- village_behaviour.h | 12 +++-- village_control.cpp | 1 + village_control.h | 2 +- village_control_serial.cpp | 1 + workshops.cpp | 105 +++++++++++++++++++++++++++++++++++++ workshops.h | 55 +++++++++++++++++++ 36 files changed, 405 insertions(+), 287 deletions(-) create mode 100644 workshops.cpp create mode 100644 workshops.h diff --git a/attack_trigger.h b/attack_trigger.h index cc98c5861..08d2dfa67 100644 --- a/attack_trigger.h +++ b/attack_trigger.h @@ -17,9 +17,11 @@ RICH_ENUM(AttackTriggerId, PROXIMITY ); -typedef EnumVariant AttackTrigger; + ASSIGN(SquareType, AttackTriggerId::ROOM_BUILT)> { + using EnumVariant::EnumVariant; +}; struct TriggerInfo { AttackTrigger trigger; diff --git a/collective.cpp b/collective.cpp index 093e7dd43..4d22b1e0a 100644 --- a/collective.cpp +++ b/collective.cpp @@ -38,6 +38,8 @@ #include "creature_attributes.h" #include "event_proxy.h" #include "villain_type.h" +#include "workshops.h" +#include "attack_trigger.h" struct Collective::ItemFetchInfo { ItemIndex index; @@ -177,7 +179,7 @@ void Collective::addCreature(Creature* c, EnumSet traits) { c->setController(PController(new Monster(c, MonsterAIFactory::collective(this)))); if (!leader) leader = c; - CHECK(c->getTribeId() == tribe); + CHECK(c->getTribeId() == *tribe); if (Game* game = getGame()) for (Collective* col : getGame()->getCollectives()) if (contains(col->getCreatures(), c)) @@ -300,11 +302,11 @@ CollectiveControl* Collective::getControl() const { } TribeId Collective::getTribeId() const { - return tribe; + return *tribe; } Tribe* Collective::getTribe() const { - return getGame()->getTribe(tribe); + return getGame()->getTribe(*tribe); } const vector& Collective::getCreatures() const { @@ -448,7 +450,7 @@ PTask Collective::generateMinionTask(Creature* c, MinionTask task) { return Task::consume(this, target); break; case MinionTaskInfo::EAT: { - const set& hatchery = getSquares(getHatcheryType(tribe)); + const set& hatchery = getSquares(getHatcheryType(*tribe)); if (!hatchery.empty()) return Task::eat(hatchery); break; @@ -1201,7 +1203,7 @@ void Collective::onEvent(const GameEvent& event) { case EventId::SQUARE_DESTROYED: { Position pos = event.get(); if (territory->contains(pos)) { - for (auto& elem : mySquares) + for (auto& elem : *mySquares) if (elem.second.count(pos)) { elem.second.erase(pos); if (config->getEfficiencySquares().count(elem.first)) @@ -1210,7 +1212,7 @@ void Collective::onEvent(const GameEvent& event) { for (auto& elem : mySquares2) if (elem.second.count(pos)) elem.second.erase(pos); - mySquares[SquareId::FLOOR].insert(pos); + (*mySquares)[SquareId::FLOOR].insert(pos); if (constructions->containsSquare(pos)) constructions->onSquareDestroyed(pos); } @@ -1282,8 +1284,8 @@ vector Collective::getAllSquares(const vector& types, bool const set& Collective::getSquares(SquareType type) const { static set empty; - if (mySquares.count(type)) - return mySquares.at(type); + if (mySquares->count(type)) + return mySquares->at(type); else return empty; } @@ -1297,7 +1299,7 @@ const set& Collective::getSquares(SquareApplyType type) const { } vector Collective::getSquareTypes() const { - return getKeys(mySquares); + return getKeys(*mySquares); } const Territory& Collective::getTerritory() const { @@ -1307,16 +1309,16 @@ const Territory& Collective::getTerritory() const { void Collective::claimSquare(Position pos) { territory->insert(pos); if (pos.getApplyType() == SquareApplyType::SLEEP) - mySquares[SquareId::BED].insert(pos); + (*mySquares)[SquareId::BED].insert(pos); if (pos.getApplyType() == SquareApplyType::CROPS) - mySquares[SquareId::CROPS].insert(pos); + (*mySquares)[SquareId::CROPS].insert(pos); if (auto type = pos.getApplyType()) mySquares2[*type].insert(pos); } void Collective::changeSquareType(Position pos, SquareType from, SquareType to) { - mySquares[from].erase(pos); - mySquares[to].insert(pos); + (*mySquares)[from].erase(pos); + (*mySquares)[to].insert(pos); for (auto& elem : mySquares2) if (elem.second.count(pos)) elem.second.erase(pos); @@ -1721,7 +1723,7 @@ bool Collective::isConstructionReachable(Position pos) { void Collective::onConstructed(Position pos, const SquareType& type) { CHECK(!getSquares(type).count(pos)); - for (auto& elem : mySquares) + for (auto& elem : *mySquares) elem.second.erase(pos); if (type.getId() == SquareId::MOUNTAIN) { destroySquare(pos); @@ -1731,7 +1733,7 @@ void Collective::onConstructed(Position pos, const SquareType& type) { } if (!contains({SquareId::TREE_TRUNK}, type.getId())) territory->insert(pos); - mySquares[type].insert(pos); + (*mySquares)[type].insert(pos); if (config->getEfficiencySquares().count(type)) updateEfficiency(pos, type); if (constructions->containsSquare(pos) && !constructions->getSquare(pos).isBuilt()) @@ -1925,15 +1927,6 @@ void Collective::addMana(double value) { } } -bool Collective::isItemNeeded(const Item* item) const { - if (isWarning(Warning::NO_WEAPONS) && item->getClass() == ItemClass::WEAPON) - return true; - set neededTraps = getNeededTraps(); - if (!neededTraps.empty() && item->getTrapType() && neededTraps.count(*item->getTrapType())) - return true; - return false; -} - void Collective::addProducesMessage(const Creature* c, const vector& items) { if (items.size() > 1) control->addMessage(c->getName().a() + " produces " + toString(items.size()) @@ -1942,36 +1935,6 @@ void Collective::addProducesMessage(const Creature* c, const vector& item control->addMessage(c->getName().a() + " produces " + items[0]->getAName()); } -static vector workshopSquares { - SquareId::WORKSHOP, - SquareId::FORGE, - SquareId::JEWELER, - SquareId::LABORATORY -}; - -struct WorkshopInfo { - ItemFactory factory; - CostInfo itemCost; -}; - -// The production cost is actually not applied ATM. -static WorkshopInfo getWorkshopInfo(Collective* c, Position pos) { - for (auto elem : workshopSquares) - if (c->getSquares(elem).count(pos)) - switch (elem.getId()) { - case SquareId::WORKSHOP: - return { ItemFactory::workshop(c->getTechnologies()), {CollectiveResourceId::GOLD, 0}}; - case SquareId::FORGE: - return { ItemFactory::forge(c->getTechnologies()), {CollectiveResourceId::IRON, 10}}; - case SquareId::LABORATORY: - return { ItemFactory::laboratory(c->getTechnologies()), {CollectiveResourceId::MANA, 10}}; - case SquareId::JEWELER: - return { ItemFactory::jeweler(c->getTechnologies()), {CollectiveResourceId::GOLD, 5}}; - default: FAIL << "Bad workshop position ";// << pos; - } - return { ItemFactory::workshop(c->getTechnologies()),{CollectiveResourceId::GOLD, 5}}; -} - void Collective::onAppliedSquare(Position pos) { Creature* c = NOTNULL(pos.getCreature()); MinionTask currentTask = currentTasks.getOrFail(c).task; @@ -1999,23 +1962,20 @@ void Collective::onAppliedSquare(Position pos) { } if (getSquares(SquareId::TRAINING_ROOM).count(pos)) c->getAttributes().exerciseAttr(Random.choose(), getEfficiency(pos)); - if (contains(getAllSquares(workshopSquares), pos)) - if (Random.rollD(40.0 / getEfficiency(pos))) { - vector items; - for (int i : Range(20)) { - auto workshopInfo = getWorkshopInfo(this, pos); - items = workshopInfo.factory.random(); - if (isItemNeeded(items[0].get())) - break; + for (auto& elem : config->getWorkshopInfo()) + if (getSquares(elem.squareType).count(pos)) { + vector items = + workshops->get(elem.workshopType).addWork(getEfficiency(pos) * (1 + c->getMorale()) / 2); + if (!items.empty()) { + if (items[0]->getClass() == ItemClass::WEAPON) + getGame()->getStatistics().add(StatId::WEAPON_PRODUCED); + if (items[0]->getClass() == ItemClass::ARMOR) + getGame()->getStatistics().add(StatId::ARMOR_PRODUCED); + if (items[0]->getClass() == ItemClass::POTION) + getGame()->getStatistics().add(StatId::POTION_PRODUCED); + addProducesMessage(c, items); + pos.dropItems(std::move(items)); } - if (items[0]->getClass() == ItemClass::WEAPON) - getGame()->getStatistics().add(StatId::WEAPON_PRODUCED); - if (items[0]->getClass() == ItemClass::ARMOR) - getGame()->getStatistics().add(StatId::ARMOR_PRODUCED); - if (items[0]->getClass() == ItemClass::POTION) - getGame()->getStatistics().add(StatId::POTION_PRODUCED); - addProducesMessage(c, items); - pos.dropItems(std::move(items)); } } diff --git a/collective.h b/collective.h index cde39dfcc..9bb193cf2 100644 --- a/collective.h +++ b/collective.h @@ -12,16 +12,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ . */ -#ifndef _COLLECTIVE_H -#define _COLLECTIVE_H +#pragma once #include "move_info.h" #include "task_callback.h" #include "minion_task.h" #include "square_apply_type.h" #include "resource_id.h" -#include "square_type.h" -#include "attack_trigger.h" #include "collective_warning.h" #include "event_listener.h" #include "entity_map.h" @@ -30,6 +27,7 @@ class CollectiveAttack; class Creature; class CollectiveControl; class Tribe; +class TribeId; class Level; class Trigger; struct ImmigrantInfo; @@ -49,6 +47,7 @@ class CollectiveName; template class EventProxy; class Workshops; +class SquareType; class Collective : public TaskCallback { public: @@ -317,9 +316,9 @@ class Collective : public TaskCallback { EnumMap> SERIAL(byTrait); EnumMap> SERIAL(bySpawnType); PCollectiveControl SERIAL(control); - TribeId SERIAL(tribe); + HeapAllocated SERIAL(tribe); Level* SERIAL(level) = nullptr; - unordered_map> SERIAL(mySquares); + HeapAllocated>> SERIAL(mySquares); unordered_map, CustomHash> SERIAL(mySquares2); map SERIAL(squareEfficiency); HeapAllocated SERIAL(territory); @@ -364,5 +363,3 @@ class Collective : public TaskCallback { optional SERIAL(villainType); HeapAllocated SERIAL(workshops); }; - -#endif diff --git a/collective_builder.cpp b/collective_builder.cpp index 55ad175d2..526280fe1 100644 --- a/collective_builder.cpp +++ b/collective_builder.cpp @@ -6,8 +6,10 @@ #include "creature_name.h" #include "collective_name.h" #include "creature_attributes.h" +#include "collective_config.h" +#include "tribe.h" -CollectiveBuilder::CollectiveBuilder(CollectiveConfig cfg, TribeId t) +CollectiveBuilder::CollectiveBuilder(const CollectiveConfig& cfg, TribeId t) : config(cfg), tribe(t) { } @@ -27,7 +29,7 @@ CollectiveBuilder& CollectiveBuilder::setRaceName(const string& n) { } CollectiveBuilder& CollectiveBuilder::addCreature(Creature* c) { - if (!c->getAttributes().isInnocent() && (!creatures.empty() || config.isLeaderFighter())) + if (!c->getAttributes().isInnocent() && (!creatures.empty() || config->isLeaderFighter())) creatures.push_back({c, {MinionTrait::FIGHTER}}); else creatures.push_back({c, {}}); @@ -52,7 +54,7 @@ CollectiveBuilder& CollectiveBuilder::addSquares(const vector& v) { PCollective CollectiveBuilder::build() { CHECK(!creatures.empty()); - Collective* c = new Collective(NOTNULL(level), config, tribe, credit, + Collective* c = new Collective(NOTNULL(level), *config, *tribe, credit, CollectiveName(raceName, locationName, creatures[0].creature)); for (auto& elem : creatures) c->addCreature(elem.creature, elem.traits); diff --git a/collective_builder.h b/collective_builder.h index ece82e118..c43fe6bd5 100644 --- a/collective_builder.h +++ b/collective_builder.h @@ -4,17 +4,18 @@ #include "enums.h" #include "util.h" #include "minion_task.h" -#include "collective_config.h" class Tribe; +class TribeId; class Level; class Creature; struct ImmigrantInfo; class Position; +class CollectiveConfig; class CollectiveBuilder { public: - CollectiveBuilder(CollectiveConfig, TribeId); + CollectiveBuilder(const CollectiveConfig&, TribeId); CollectiveBuilder& setLevel(Level*); CollectiveBuilder& setCredit(map); CollectiveBuilder& addCreature(Creature*); @@ -32,8 +33,8 @@ class CollectiveBuilder { EnumSet traits; }; vector creatures; - CollectiveConfig config; - TribeId tribe; + HeapAllocated config; + HeapAllocated tribe; map credit; vector squares; optional locationName; diff --git a/collective_config.cpp b/collective_config.cpp index 401ffa922..a1b29dae2 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -12,6 +12,7 @@ #include "workshops.h" #include "lasting_effect.h" #include "item.h" +#include "square_type.h" AttractionInfo::AttractionInfo(MinionAttraction a, double cl, double min, bool mand) : attraction(a), amountClaimed(cl), minAmount(min), mandatory(mand) {} @@ -342,68 +343,80 @@ map CollectiveConfig::getTaskInfo() const { return ret; }; -Workshops CollectiveConfig::getWorkshops() const { +static vector workshops { + {SquareId::WORKSHOP, WorkshopType::WORKSHOP}, + {SquareId::FORGE, WorkshopType::FORGE}, + {SquareId::LABORATORY, WorkshopType::LABORATORY}, + {SquareId::JEWELER, WorkshopType::JEWELER}, +}; + +const vector& CollectiveConfig::getWorkshopInfo() const { + return workshops; +} + + +HeapAllocated CollectiveConfig::getWorkshops() const { return Workshops({ {WorkshopType::WORKSHOP, { - Workshops::Item::fromType(ItemId::FIRST_AID_KIT, {CollectiveResourceId::WOOD, 20}), - Workshops::Item::fromType(ItemId::LEATHER_ARMOR, {CollectiveResourceId::WOOD, 100}), - Workshops::Item::fromType(ItemId::LEATHER_HELM, {CollectiveResourceId::WOOD, 30}), - Workshops::Item::fromType(ItemId::LEATHER_BOOTS, {CollectiveResourceId::WOOD, 50}), - Workshops::Item::fromType(ItemId::LEATHER_GLOVES, {CollectiveResourceId::WOOD, 10}), - Workshops::Item::fromType(ItemId::CLUB, {CollectiveResourceId::WOOD, 50}), - Workshops::Item::fromType(ItemId::HEAVY_CLUB, {CollectiveResourceId::WOOD, 100}), - Workshops::Item::fromType(ItemId::BOW, {CollectiveResourceId::WOOD, 100}), - Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 2}, 20), - Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType(ItemId::FIRST_AID_KIT, {CollectiveResourceId::WOOD, 20}, 1), + Workshops::Item::fromType(ItemId::LEATHER_ARMOR, {CollectiveResourceId::WOOD, 100}, 6), + Workshops::Item::fromType(ItemId::LEATHER_HELM, {CollectiveResourceId::WOOD, 30}, 1), + Workshops::Item::fromType(ItemId::LEATHER_BOOTS, {CollectiveResourceId::WOOD, 50}, 2), + Workshops::Item::fromType(ItemId::LEATHER_GLOVES, {CollectiveResourceId::WOOD, 10}, 1), + Workshops::Item::fromType(ItemId::CLUB, {CollectiveResourceId::WOOD, 50}, 3), + Workshops::Item::fromType(ItemId::HEAVY_CLUB, {CollectiveResourceId::WOOD, 100}, 5), + Workshops::Item::fromType(ItemId::BOW, {CollectiveResourceId::WOOD, 100}, 13), + Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 2}, 5, 20), + Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, {CollectiveResourceId::STONE, 250}, 20), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, {CollectiveResourceId::WOOD, 100}), + TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, {CollectiveResourceId::WOOD, 100}, 10), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::ALARM, EffectId::ALARM})}, {CollectiveResourceId::WOOD, 100}), + TrapInfo({TrapType::ALARM, EffectId::ALARM})}, {CollectiveResourceId::WOOD, 100}, 8), Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::WEB, - EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, {CollectiveResourceId::WOOD, 100}), + EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, {CollectiveResourceId::WOOD, 100}, 8), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, {CollectiveResourceId::WOOD, 100}), + TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, {CollectiveResourceId::WOOD, 100}, 8), Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::TERROR, - EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, {CollectiveResourceId::WOOD, 100}), + EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, {CollectiveResourceId::WOOD, 100}, 8), }}, {WorkshopType::FORGE, { - Workshops::Item::fromType(ItemId::SWORD, {CollectiveResourceId::IRON, 100}), - Workshops::Item::fromType(ItemId::SPECIAL_SWORD, {CollectiveResourceId::IRON, 1000}), - Workshops::Item::fromType(ItemId::CHAIN_ARMOR, {CollectiveResourceId::IRON, 200}), - Workshops::Item::fromType(ItemId::IRON_HELM, {CollectiveResourceId::IRON, 80}), - Workshops::Item::fromType(ItemId::IRON_BOOTS, {CollectiveResourceId::IRON, 120}), - Workshops::Item::fromType(ItemId::WAR_HAMMER, {CollectiveResourceId::IRON, 190}), - Workshops::Item::fromType(ItemId::BATTLE_AXE, {CollectiveResourceId::IRON, 250}), - Workshops::Item::fromType(ItemId::SPECIAL_WAR_HAMMER, {CollectiveResourceId::IRON, 1900}), - Workshops::Item::fromType(ItemId::SPECIAL_BATTLE_AXE, {CollectiveResourceId::IRON, 2000}), + Workshops::Item::fromType(ItemId::SWORD, {CollectiveResourceId::IRON, 100}, 10), + Workshops::Item::fromType(ItemId::SPECIAL_SWORD, {CollectiveResourceId::IRON, 1000}, 80), + Workshops::Item::fromType(ItemId::CHAIN_ARMOR, {CollectiveResourceId::IRON, 200}, 30), + Workshops::Item::fromType(ItemId::IRON_HELM, {CollectiveResourceId::IRON, 80}, 8), + Workshops::Item::fromType(ItemId::IRON_BOOTS, {CollectiveResourceId::IRON, 120}, 12), + Workshops::Item::fromType(ItemId::WAR_HAMMER, {CollectiveResourceId::IRON, 190}, 16), + Workshops::Item::fromType(ItemId::BATTLE_AXE, {CollectiveResourceId::IRON, 250}, 22), + Workshops::Item::fromType(ItemId::SPECIAL_WAR_HAMMER, {CollectiveResourceId::IRON, 1900}, 120), + Workshops::Item::fromType(ItemId::SPECIAL_BATTLE_AXE, {CollectiveResourceId::IRON, 2000}, 180), }}, {WorkshopType::LABORATORY, { Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLOWED}}, - {CollectiveResourceId::MANA, 10}), + {CollectiveResourceId::MANA, 10}, 2), Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLEEP}}, - {CollectiveResourceId::MANA, 10}), - Workshops::Item::fromType({ItemId::POTION, EffectId::HEAL}, {CollectiveResourceId::MANA, 30}), + {CollectiveResourceId::MANA, 10}, 2), + Workshops::Item::fromType({ItemId::POTION, EffectId::HEAL}, {CollectiveResourceId::MANA, 30}, 4), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::POISON_RESISTANT}}, {CollectiveResourceId::MANA, 30}), + EffectType{EffectId::LASTING, LastingEffect::POISON_RESISTANT}}, {CollectiveResourceId::MANA, 30}, 3), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::POISON}}, {CollectiveResourceId::MANA, 30}), + EffectType{EffectId::LASTING, LastingEffect::POISON}}, {CollectiveResourceId::MANA, 30}, 2), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::SPEED}}, {CollectiveResourceId::MANA, 30}), + EffectType{EffectId::LASTING, LastingEffect::SPEED}}, {CollectiveResourceId::MANA, 30}, 4), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::BLIND}}, {CollectiveResourceId::MANA, 50}), + EffectType{EffectId::LASTING, LastingEffect::BLIND}}, {CollectiveResourceId::MANA, 50}, 4), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::FLYING}}, {CollectiveResourceId::MANA, 80}), + EffectType{EffectId::LASTING, LastingEffect::FLYING}}, {CollectiveResourceId::MANA, 80}, 6), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::INVISIBLE}}, {CollectiveResourceId::MANA, 200}), + EffectType{EffectId::LASTING, LastingEffect::INVISIBLE}}, {CollectiveResourceId::MANA, 200}, 6), }}, {WorkshopType::JEWELER, { Workshops::Item::fromType({ItemId::RING, LastingEffect::POISON_RESISTANT}, - {CollectiveResourceId::GOLD, 100}), + {CollectiveResourceId::GOLD, 100}, 10), Workshops::Item::fromType({ItemId::RING, LastingEffect::FIRE_RESISTANT}, - {CollectiveResourceId::GOLD, 150}), - Workshops::Item::fromType(ItemId::WARNING_AMULET, {CollectiveResourceId::GOLD, 150}), - Workshops::Item::fromType(ItemId::DEFENSE_AMULET, {CollectiveResourceId::GOLD, 200}), - Workshops::Item::fromType(ItemId::HEALING_AMULET, {CollectiveResourceId::GOLD, 300}), + {CollectiveResourceId::GOLD, 150}, 10), + Workshops::Item::fromType(ItemId::WARNING_AMULET, {CollectiveResourceId::GOLD, 150}, 10), + Workshops::Item::fromType(ItemId::DEFENSE_AMULET, {CollectiveResourceId::GOLD, 200}, 10), + Workshops::Item::fromType(ItemId::HEALING_AMULET, {CollectiveResourceId::GOLD, 300}, 10), }}, }); } diff --git a/collective_config.h b/collective_config.h index 0a961680f..32c340ab6 100644 --- a/collective_config.h +++ b/collective_config.h @@ -19,11 +19,11 @@ #include "square_type.h" #include "util.h" #include "minion_task.h" -#include "workshops.h" enum class ItemClass; class Game; +class Workshops; enum class AttractionId { SQUARE, @@ -124,6 +124,11 @@ struct MinionTaskInfo { bool centerOnly = false; }; +struct WorkshopInfo { + SquareId squareType; + WorkshopType workshopType; +}; + class CollectiveConfig { public: static CollectiveConfig keeper(double immigrantFrequency, int payoutTime, double payoutMultiplier, @@ -156,7 +161,8 @@ class CollectiveConfig { const vector& getPopulationIncreases() const; const optional& getGuardianInfo() const; vector getBirthSpawns() const; - Workshops getWorkshops() const; + HeapAllocated getWorkshops() const; + const vector& getWorkshopInfo() const; bool activeImmigrantion(const Game*) const; const EnumMap& getDormInfo() const; diff --git a/collective_control.cpp b/collective_control.cpp index 777a34d6e..85470019e 100644 --- a/collective_control.cpp +++ b/collective_control.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "collective_control.h" #include "collective.h" +#include "attack_trigger.h" template void CollectiveControl::serialize(Archive& ar, const unsigned int version) { @@ -52,6 +53,10 @@ class IdleControl : public CollectiveControl { } }; +vector CollectiveControl::getTriggers(const Collective* against) const { + return {}; +} + PCollectiveControl CollectiveControl::idle(Collective* col) { return PCollectiveControl(new IdleControl(col)); } diff --git a/collective_control.h b/collective_control.h index 9e6e8726e..c9e4146b8 100644 --- a/collective_control.h +++ b/collective_control.h @@ -23,7 +23,7 @@ class CollectiveControl { virtual void onConstructed(Position, const SquareType&) {} virtual void onNoEnemies() {} virtual void onRansomPaid() {} - virtual vector getTriggers(const Collective* against) const { return {}; } + virtual vector getTriggers(const Collective* against) const; SERIALIZATION_DECL(CollectiveControl); diff --git a/creature.h b/creature.h index 1c332f08e..b67856410 100644 --- a/creature.h +++ b/creature.h @@ -74,6 +74,7 @@ class Creature : public Renderable, public UniqueEntity { bool dodgeAttack(const Attack&); bool takeDamage(const Attack&); void heal(double amount = 1, bool replaceLimbs = false); + /** Morale is in the range [-1:1] **/ double getMorale() const; void addMorale(double); DEF_UNIQUE_PTR(MoraleOverride); diff --git a/enemy_factory.cpp b/enemy_factory.cpp index 19658f303..811c42175 100644 --- a/enemy_factory.cpp +++ b/enemy_factory.cpp @@ -3,6 +3,7 @@ #include "location.h" #include "name_generator.h" #include "technology.h" +#include "attack_trigger.h" EnemyFactory::EnemyFactory(RandomGen& r) : random(r) { } @@ -604,7 +605,7 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { c.minPopulation = 0; c.minTeamSize = 1; c.triggers = LIST(AttackTriggerId::SELF_VICTIMS, AttackTriggerId::STOLEN_ITEMS); - c.attackBehaviour = AttackBehaviourId::KILL_LEADER; + c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER); c.ransom = make_pair(0.5, random.get(200, 400));)); } } diff --git a/enemy_factory.h b/enemy_factory.h index 557585407..02da32067 100644 --- a/enemy_factory.h +++ b/enemy_factory.h @@ -5,6 +5,7 @@ #include "collective_config.h" #include "villain_type.h" #include "village_behaviour.h" +#include "attack_trigger.h" struct EnemyInfo; diff --git a/game.cpp b/game.cpp index 9b37083ac..89b392f28 100644 --- a/game.cpp +++ b/game.cpp @@ -25,6 +25,8 @@ #include "save_file_info.h" #include "file_sharing.h" #include "villain_type.h" +#include "square_type.h" +#include "attack_trigger.h" template void Game::serialize(Archive& ar, const unsigned int version) { @@ -167,7 +169,7 @@ void Game::prepareSiteRetirement() { c.minTeamSize = 5; c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, {AttackTriggerId::SELF_VICTIMS}, AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}); - c.attackBehaviour = AttackBehaviourId::KILL_LEADER; + c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER); c.ransom = make_pair(0.8, Random.get(500, 700));)))); for (Collective* col : models[baseModel]->getCollectives()) for (Creature* c : col->getCreatures()) diff --git a/game_info.h b/game_info.h index a435d99b7..5e3193499 100644 --- a/game_info.h +++ b/game_info.h @@ -7,7 +7,6 @@ #include "item_action.h" #include "village_action.h" #include "cost_info.h" -#include "attack_trigger.h" #include "view_id.h" #include "player_message.h" @@ -44,7 +43,8 @@ struct ItemInfo { optional HASH(owner); enum Type {EQUIPMENT, CONSUMABLE, OTHER} HASH(type); optional> HASH(price); - HASH_ALL(name, fullName, description, number, viewId, ids, actions, equiped, locked, pending, unavailable, slot, owner, type, price); + double HASH(productionState); + HASH_ALL(name, fullName, description, number, viewId, ids, actions, equiped, locked, pending, unavailable, slot, owner, type, price, productionState); }; diff --git a/gui_builder.cpp b/gui_builder.cpp index dd4d7dfa7..849a8ff68 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -1096,7 +1096,7 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& line.buildHorizontalList()))); } auto lines2 = gui.getListBuilder(legendLineHeight); - lines2.addElem(gui.label("In queue:", colors[ColorId::YELLOW])); + lines2.addElem(gui.label("In production:", colors[ColorId::YELLOW])); for (int i : All(queued)) { auto& elem = queued[i]; auto line = gui.getListBuilder(); @@ -1132,7 +1132,10 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& WorkshopQueuedActionInfo{i, ItemAction::CHANGE_NUMBER}})), gui.label(toString(elem.number) + "x")), 35); line.addBackElem(gui.alignment(GuiFactory::Alignment::RIGHT, drawCost(*elem.price)), 80); - lines2.addElem(gui.rightMargin(rightElemMargin, line.buildHorizontalList())); + lines2.addElem(gui.stack( + gui.bottomMargin(5, + gui.progressBar(transparency(colors[ColorId::DARK_GREEN], 128), elem.productionState)), + gui.rightMargin(rightElemMargin, line.buildHorizontalList()))); } size.y = min(600, max(lines.getSize(), lines2.getSize()) + 2 * margin); ret.push_back({gui.miniWindow(gui.stack( diff --git a/gui_elem.cpp b/gui_elem.cpp index 335aceafc..d11c5ad93 100644 --- a/gui_elem.cpp +++ b/gui_elem.cpp @@ -1402,6 +1402,15 @@ PGuiElem GuiFactory::marginFit(PGuiElem top, PGuiElem rest, double width, Margin return PGuiElem(new MarginFit(std::move(top), std::move(rest), width, type)); } +PGuiElem GuiFactory::progressBar(Color c, double state) { + return PGuiElem(new DrawCustom([=] (Renderer& r, Rectangle bounds) { + int width = bounds.width() * state; + if (width > 0) + r.drawFilledRectangle(Rectangle(bounds.topLeft(), + Vec2(bounds.left() + width, bounds.bottom())), c); + })); +} + class Margins : public GuiLayout { public: Margins(PGuiElem content, int l, int t, int r, int b) diff --git a/gui_elem.h b/gui_elem.h index 66f2b5651..6e2b1768e 100644 --- a/gui_elem.h +++ b/gui_elem.h @@ -133,6 +133,7 @@ class GuiFactory { PGuiElem rightMargin(int size, PGuiElem content); PGuiElem topMargin(int size, PGuiElem content); PGuiElem bottomMargin(int size, PGuiElem content); + PGuiElem progressBar(Color, double state); PGuiElem label(const string&, Color = colors[ColorId::WHITE], char hotkey = 0); PGuiElem labelHighlight(const string&, Color = colors[ColorId::WHITE], char hotkey = 0); PGuiElem labelHighlightBlink(const string& s, Color, Color); diff --git a/item_factory.cpp b/item_factory.cpp index 1b2104cf1..be0bee57f 100644 --- a/item_factory.cpp +++ b/item_factory.cpp @@ -493,77 +493,6 @@ ItemFactory ItemFactory::minerals() { {ItemId::ROCK, 1 }}); } -ItemFactory ItemFactory::workshop(const vector& techs) { - ItemFactory factory({ - {ItemId::FIRST_AID_KIT, 4}, - {ItemId::LEATHER_ARMOR, 4 }, - {ItemId::LEATHER_HELM, 2 }, - {ItemId::LEATHER_BOOTS, 2 }, - {ItemId::LEATHER_GLOVES, 2 }, - {ItemId::CLUB, 3 }, - {ItemId::HEAVY_CLUB, 1 }, - }); - if (contains(techs, Technology::get(TechId::TRAPS))) { - factory.addItem({ItemId::BOULDER_TRAP_ITEM, 0.5 }); - factory.addItem({{ItemId::TRAP_ITEM, TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, 0.5 }); - factory.addItem({{ItemId::TRAP_ITEM, TrapInfo({TrapType::ALARM, EffectId::ALARM})}, 1 }); - factory.addItem({{ItemId::TRAP_ITEM, TrapInfo({TrapType::WEB, - EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, 1 }); - factory.addItem({{ItemId::TRAP_ITEM, TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, 1 }); - factory.addItem({{ItemId::TRAP_ITEM, TrapInfo({TrapType::TERROR, - EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, 1 }); - } - if (contains(techs, Technology::get(TechId::ARCHERY))) { - factory.addItem({ItemId::BOW, 2 }); - factory.addItem({ItemId::ARROW, 2, 10, 30 }); - } - return factory; -} - -ItemFactory ItemFactory::forge(const vector& techs) { -// CHECK(contains(techs, Technology::get(TechId::IRON_WORKING))); - ItemFactory factory({ - {ItemId::SWORD, 6 }, - {ItemId::SPECIAL_SWORD, 0.05 }, - {ItemId::CHAIN_ARMOR, 4 }, - {ItemId::IRON_HELM, 2 }, - {ItemId::IRON_BOOTS, 2 }, - }); - if (contains(techs, Technology::get(TechId::TWO_H_WEAP))) { - factory.addItem({ItemId::BATTLE_AXE, 5 }); - factory.addItem({ItemId::WAR_HAMMER, 5 }); - factory.addItem({ItemId::SPECIAL_BATTLE_AXE, 0.05 }); - factory.addItem({ItemId::SPECIAL_WAR_HAMMER, 0.05 }); - } - return factory; -} - -ItemFactory ItemFactory::jeweler(const vector& techs) { - return ItemFactory({ - {ItemId::WARNING_AMULET, 3 }, - {ItemId::HEALING_AMULET, 3 }, - {ItemId::DEFENSE_AMULET, 3 }, - {{ItemId::RING, LastingEffect::POISON_RESISTANT}, 3}, - {{ItemId::RING, LastingEffect::FIRE_RESISTANT}, 3}, - }); -} - -ItemFactory ItemFactory::laboratory(const vector& techs) { - ItemFactory factory({ - {{ItemId::POTION, EffectId::HEAL}, 1 }, - {{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::SLEEP)}, 1 }, - {{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::SLOWED)}, 1 }, - {{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::POISON_RESISTANT)}, 1 }}); - if (contains(techs, Technology::get(TechId::ALCHEMY_ADV))) { - factory.addItem({{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::BLIND)}, 1 }); - factory.addItem({{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::INVISIBLE)}, 1 }); - factory.addItem({{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::FLYING)}, 1 }); - factory.addItem({{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::POISON)}, 1 }); - factory.addItem({{ItemId::POTION, EffectType(EffectId::LASTING, LastingEffect::SPEED)}, 1 }); - } - return factory; -} - ItemFactory ItemFactory::potions() { return ItemFactory({ {{ItemId::POTION, EffectId::HEAL}, 1 }, diff --git a/item_factory.h b/item_factory.h index 4ada9f700..07a6067b6 100644 --- a/item_factory.h +++ b/item_factory.h @@ -44,10 +44,6 @@ class ItemFactory { static ItemFactory orcShop(); static ItemFactory gnomeShop(); static ItemFactory dragonCave(); - static ItemFactory workshop(const vector& techs); - static ItemFactory forge(const vector& techs); - static ItemFactory jeweler(const vector& techs); - static ItemFactory laboratory(const vector& techs); static ItemFactory minerals(); static ItemFactory singleType(ItemType); diff --git a/level.cpp b/level.cpp index 32905f627..3c65f61d4 100644 --- a/level.cpp +++ b/level.cpp @@ -34,6 +34,7 @@ #include "sunlight_info.h" #include "game.h" #include "creature_attributes.h" +#include "square_array.h" template void Level::serialize(Archive& ar, const unsigned int version) { @@ -50,13 +51,13 @@ Level::~Level() {} Level::Level(SquareArray s, Model* m, vector l, const string& n, Table sun, LevelId id) - : squares(std::move(s)), oldSquares(squares.getBounds()), memoryUpdates(squares.getBounds(), true), + : squares(std::move(s)), oldSquares(squares->getBounds()), memoryUpdates(squares->getBounds(), true), locations(l), model(m), - name(n), sunlight(sun), bucketMap(squares.getBounds().width(), squares.getBounds().height(), - FieldOfView::sightRange), lightAmount(squares.getBounds(), 0), lightCapAmount(squares.getBounds(), 1), + name(n), sunlight(sun), bucketMap(squares->getBounds().width(), squares->getBounds().height(), + FieldOfView::sightRange), lightAmount(squares->getBounds(), 0), lightCapAmount(squares->getBounds(), 1), levelId(id) { - for (Vec2 pos : squares.getBounds()) { - const Square* square = squares.getReadonly(pos); + for (Vec2 pos : squares->getBounds()) { + const Square* square = squares->getReadonly(pos); square->onAddedToLevel(Position(pos, this)); if (optional link = square->getLandingLink()) landingSquares[*link].push_back(Position(pos, this)); @@ -64,9 +65,9 @@ Level::Level(SquareArray s, Model* m, vector l, const string& n, for (Location *l : locations) l->setLevel(this); for (VisionId vision : ENUM_ALL(VisionId)) - fieldOfView[vision] = FieldOfView(squares, vision); - for (Vec2 pos : squares.getBounds()) - addLightSource(pos, squares.getReadonly(pos)->getLightEmission(), 1); + fieldOfView[vision] = FieldOfView(*squares, vision); + for (Vec2 pos : squares->getBounds()) + addLightSource(pos, squares->getReadonly(pos)->getLightEmission(), 1); } LevelId Level::getUniqueId() const { @@ -144,7 +145,7 @@ void Level::removeSquare(Position pos, PSquare defaultSquare) { void Level::replaceSquare(Position position, PSquare newSquare, bool storePrevious) { Vec2 pos = position.getCoord(); - Square* oldSquare = squares.getSquare(pos); + Square* oldSquare = squares->getSquare(pos); oldSquare->onConstructNewSquare(position, newSquare.get()); Creature* c = oldSquare->getCreature(); if (c) @@ -160,29 +161,29 @@ void Level::replaceSquare(Position position, PSquare newSquare, bool storePrevio if (auto tribe = oldSquare->getForbiddenTribe()) newSquare->forbidMovementForTribe(position, *tribe); if (storePrevious) - oldSquares[pos] = squares.extractSquare(pos); - squares.putSquare(pos, std::move(newSquare)); - squares.getSquare(pos)->onAddedToLevel(position); + oldSquares[pos] = squares->extractSquare(pos); + squares->putSquare(pos, std::move(newSquare)); + squares->getSquare(pos)->onAddedToLevel(position); if (c) { - squares.getSquare(pos)->setCreature(c); + squares->getSquare(pos)->setCreature(c); } - addLightSource(pos, squares.getSquare(pos)->getLightEmission(), 1); + addLightSource(pos, squares->getSquare(pos)->getLightEmission(), 1); updateVisibility(pos); updateConnectivity(pos); } void Level::updateVisibility(Vec2 changedSquare) { for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL)) { - addLightSource(pos, squares.getReadonly(pos)->getLightEmission(), -1); - if (Creature* c = squares.getReadonly(pos)->getCreature()) + addLightSource(pos, squares->getReadonly(pos)->getLightEmission(), -1); + if (Creature* c = squares->getReadonly(pos)->getCreature()) if (c->isDarknessSource()) addDarknessSource(pos, darknessRadius, -1); } for (VisionId vision : ENUM_ALL(VisionId)) fieldOfView[vision].squareChanged(changedSquare); for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL)) { - addLightSource(pos, squares.getReadonly(pos)->getLightEmission(), 1); - if (Creature* c = squares.getReadonly(pos)->getCreature()) + addLightSource(pos, squares->getReadonly(pos)->getLightEmission(), 1); + if (Creature* c = squares->getReadonly(pos)->getCreature()) if (c->isDarknessSource()) addDarknessSource(pos, darknessRadius, 1); } @@ -537,12 +538,12 @@ void Level::setBackgroundLevel(const Level* l, Vec2 offs) { const Square* Level::getSafeSquare(Vec2 pos) const { CHECK(inBounds(pos)); - return squares.getReadonly(pos); + return squares->getReadonly(pos); } Square* Level::modSafeSquare(Vec2 pos) { CHECK(inBounds(pos)); - return squares.getSquare(pos); + return squares->getSquare(pos); } Position Level::getPosition(Vec2 pos) const { @@ -562,7 +563,7 @@ void Level::addTickingSquare(Vec2 pos) { void Level::tick() { for (Vec2 pos : tickingSquares) - squares.getSquare(pos)->tick(Position(pos, this)); + squares->getSquare(pos)->tick(Position(pos, this)); } bool Level::inBounds(Vec2 pos) const { @@ -574,11 +575,11 @@ Rectangle Level::getBounds() const { } int Level::getWidth() const { - return squares.getBounds().width(); + return squares->getBounds().width(); } int Level::getHeight() const { - return squares.getBounds().height(); + return squares->getBounds().height(); } const string& Level::getName() const { @@ -621,7 +622,7 @@ const optional& Level::getBackgroundObject(Vec2 pos) const { } int Level::getNumModifiedSquares() const { - return squares.getNumModified(); + return squares->getNumModified(); } void Level::setNeedsMemoryUpdate(Vec2 pos, bool s) { diff --git a/level.h b/level.h index 58b62a846..f53ecc2d8 100644 --- a/level.h +++ b/level.h @@ -24,7 +24,6 @@ #include "sectors.h" #include "stair_key.h" #include "entity_set.h" -#include "square_array.h" #include "view_object.h" class Model; @@ -42,6 +41,7 @@ class PlayerMessage; class CreatureBucketMap; class Position; class Game; +class SquareArray; RICH_ENUM(VisionId, ELF, @@ -230,7 +230,7 @@ class Level { const Square* getSafeSquare(Vec2) const; Square* modSafeSquare(Vec2); Vec2 transform(Vec2); - SquareArray SERIAL(squares); + HeapAllocated SERIAL(squares); Table SERIAL(oldSquares); Table> SERIAL(background); Table SERIAL(memoryUpdates); diff --git a/monster_ai.cpp b/monster_ai.cpp index 784c07475..a765a0306 100644 --- a/monster_ai.cpp +++ b/monster_ai.cpp @@ -31,6 +31,7 @@ #include "task.h" #include "game.h" #include "creature_attributes.h" +#include "creature_factory.h" class Behaviour { public: diff --git a/player_control.cpp b/player_control.cpp index 3b87878a7..ee46a97d9 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -62,6 +62,7 @@ #include "villain_type.h" #include "event_proxy.h" #include "workshops.h" +#include "attack_trigger.h" template void PlayerControl::serialize(Archive& ar, const unsigned int version) { @@ -1081,6 +1082,7 @@ static ItemInfo getWorkshopItem(const Workshops::Item& option) { c.viewId = option.viewId; c.price = getCostObj(option.cost); c.unavailable = !option.active; + c.productionState = option.state; c.actions = LIST(ItemAction::REMOVE, ItemAction::CHANGE_NUMBER); c.number = option.number; ); @@ -1100,8 +1102,8 @@ void PlayerControl::fillWorkshopInfo(CollectiveInfo& info) const { } if (chosenWorkshop) { info.chosenWorkshop = CollectiveInfo::ChosenWorkshopInfo { - transform2(getCollective()->getWorkshops().getOptions(*chosenWorkshop), getWorkshopItem), - transform2(getCollective()->getWorkshops().getQueued(*chosenWorkshop), getWorkshopItem), + transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getOptions(), getWorkshopItem), + transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getQueued(), getWorkshopItem), index }; } @@ -1626,18 +1628,18 @@ void PlayerControl::processInput(View* view, UserInput input) { break; case UserInputId::WORKSHOP_ADD: if (chosenWorkshop) - getCollective()->getWorkshops().queue(*chosenWorkshop, input.get()); + getCollective()->getWorkshops().get(*chosenWorkshop).queue(input.get()); break; case UserInputId::WORKSHOP_ITEM_ACTION: { auto& info = input.get(); if (chosenWorkshop) switch (info.action) { case ItemAction::REMOVE: - getCollective()->getWorkshops().unqueue(*chosenWorkshop, info.itemIndex); + getCollective()->getWorkshops().get(*chosenWorkshop).unqueue(info.itemIndex); break; case ItemAction::CHANGE_NUMBER: - if (auto number = getView()->getNumber("Choose number of items:", 0, 300, 1)) - getCollective()->getWorkshops().changeNumber(*chosenWorkshop, info.itemIndex, *number); + if (auto number = getView()->getNumber("Change the number of items:", 0, 300, 1)) + getCollective()->getWorkshops().get(*chosenWorkshop).changeNumber(info.itemIndex, *number); default: break; } diff --git a/player_control.h b/player_control.h index 18982313b..595434e0c 100644 --- a/player_control.h +++ b/player_control.h @@ -21,7 +21,6 @@ #include "collective_control.h" #include "cost_info.h" #include "game_info.h" -#include "square_type.h" #include "map_memory.h" #include "position.h" #include "collective_warning.h" @@ -42,6 +41,7 @@ struct EquipmentActionInfo; struct TeamCreatureInfo; template class EventProxy; +class SquareType; class PlayerControl : public CreatureView, public CollectiveControl { public: diff --git a/square_factory.cpp b/square_factory.cpp index c82340025..ded066d86 100644 --- a/square_factory.cpp +++ b/square_factory.cpp @@ -40,6 +40,7 @@ #include "square_interaction.h" #include "game.h" #include "event_listener.h" +#include "square_type.h" class Magma : public Square { public: diff --git a/square_factory.h b/square_factory.h index 4d3f745ed..235f663d3 100644 --- a/square_factory.h +++ b/square_factory.h @@ -17,10 +17,11 @@ #define _SQUARE_FACTORY #include "util.h" -#include "square_type.h" class Square; +class SquareType; class StairKey; +class TribeId; class SquareFactory { public: diff --git a/task.cpp b/task.cpp index 5a409fd4c..768c27d9c 100644 --- a/task.cpp +++ b/task.cpp @@ -38,6 +38,7 @@ #include "model.h" #include "collective_name.h" #include "creature_attributes.h" +#include "square_type.h" template void Task::serialize(Archive& ar, const unsigned int version) { diff --git a/technology.cpp b/technology.cpp index 06013e561..52bece9e2 100644 --- a/technology.cpp +++ b/technology.cpp @@ -25,6 +25,7 @@ #include "spell.h" #include "creature.h" #include "creature_attributes.h" +#include "square_type.h" void Technology::init() { Technology::set(TechId::ALCHEMY, new Technology( diff --git a/util.h b/util.h index 24e9b02f6..bb355ffd1 100644 --- a/util.h +++ b/util.h @@ -1122,6 +1122,42 @@ class DirSet { ContentType content = 0; }; +template +class EnumAll { + public: + class Iter { + public: + Iter(int num) : ind(num) { + } + + T operator* () const { + return T(ind); + } + + bool operator != (const Iter& other) const { + return ind != other.ind; + } + + const Iter& operator++ () { + ++ind; + return *this; + } + + private: + int ind; + }; + + Iter begin() { + return Iter(0); + } + + Iter end() { + return Iter(EnumInfo::size); + } +}; + +#define ENUM_ALL(X) EnumAll() + template class EnumMap { public: @@ -1175,6 +1211,14 @@ class EnumMap { return elems[int(elem)]; } + template + EnumMap mapValues(Fun fun) const { + EnumMap ret; + for (T t : EnumAll()) + ret[t] = fun((*this)[t]); + return ret; + } + template void serialize(Archive& ar, const unsigned int version) { vector SERIAL(tmp); @@ -1320,42 +1364,6 @@ class EnumSet { Bitset elems; }; -template -class EnumAll { - public: - class Iter { - public: - Iter(int num) : ind(num) { - } - - T operator* () const { - return T(ind); - } - - bool operator != (const Iter& other) const { - return ind != other.ind; - } - - const Iter& operator++ () { - ++ind; - return *this; - } - - private: - int ind; - }; - - Iter begin() { - return Iter(0); - } - - Iter end() { - return Iter(EnumInfo::size); - } -}; - -#define ENUM_ALL(X) EnumAll() - template class BiMap { public: diff --git a/village_behaviour.cpp b/village_behaviour.cpp index 8982a1fe9..6527cfe96 100644 --- a/village_behaviour.cpp +++ b/village_behaviour.cpp @@ -6,22 +6,27 @@ #include "collective.h" #include "game.h" #include "collective_name.h" +#include "attack_trigger.h" +#include "creature_factory.h" SERIALIZE_DEF(VillageBehaviour, minPopulation, minTeamSize, triggers, attackBehaviour, welcomeMessage, ransom); +VillageBehaviour::VillageBehaviour() {} + +VillageBehaviour::~VillageBehaviour() {} PTask VillageBehaviour::getAttackTask(VillageControl* self) { Collective* enemy = self->getEnemyCollective(); - switch (attackBehaviour.getId()) { + switch (attackBehaviour->getId()) { case AttackBehaviourId::KILL_LEADER: return Task::attackLeader(enemy); case AttackBehaviourId::KILL_MEMBERS: - return Task::killFighters(enemy, attackBehaviour.get()); + return Task::killFighters(enemy, attackBehaviour->get()); case AttackBehaviourId::STEAL_GOLD: return Task::stealFrom(enemy, self->getCollective()); case AttackBehaviourId::CAMP_AND_SPAWN: return Task::campAndSpawn(enemy, self->getCollective(), - attackBehaviour.get(), Random.get(3, 7), Range(3, 7), Random.get(3, 7)); + attackBehaviour->get(), Random.get(3, 7), Range(3, 7), Random.get(3, 7)); } } diff --git a/village_behaviour.h b/village_behaviour.h index 61f15cca3..9076152b4 100644 --- a/village_behaviour.h +++ b/village_behaviour.h @@ -3,7 +3,8 @@ #include "util.h" #include "enum_variant.h" #include "creature_factory.h" -#include "attack_trigger.h" + +class AttackTrigger; enum class AttackBehaviourId { KILL_LEADER, @@ -16,7 +17,10 @@ typedef EnumVariant AttackBehaviour; -struct VillageBehaviour { +class VillageBehaviour { + public: + VillageBehaviour(); + typedef AttackTrigger Trigger; enum WelcomeMessage { DRAGON_WELCOME, @@ -29,7 +33,7 @@ struct VillageBehaviour { int SERIAL(minPopulation); int SERIAL(minTeamSize); vector SERIAL(triggers); - AttackBehaviour SERIAL(attackBehaviour); + HeapAllocated SERIAL(attackBehaviour); optional SERIAL(welcomeMessage); optional> SERIAL(ransom); @@ -38,6 +42,8 @@ struct VillageBehaviour { double getTriggerValue(const Trigger&, const VillageControl* self) const; bool contains(const Creature*); + ~VillageBehaviour(); + template void serialize(Archive& ar, const unsigned int version); }; diff --git a/village_control.cpp b/village_control.cpp index 3f56c4df3..76401e69a 100644 --- a/village_control.cpp +++ b/village_control.cpp @@ -30,6 +30,7 @@ #include "lasting_effect.h" #include "body.h" #include "event_proxy.h" +#include "attack_trigger.h" typedef EnumVariant OldTrigger; diff --git a/village_control.h b/village_control.h index b77a9b7a9..d80211578 100644 --- a/village_control.h +++ b/village_control.h @@ -29,7 +29,7 @@ class EventProxy; class VillageControl : public CollectiveControl { public: - friend struct VillageBehaviour; + friend class VillageBehaviour; VillageControl(Collective*, optional); diff --git a/village_control_serial.cpp b/village_control_serial.cpp index ca8af1bce..b24d96318 100644 --- a/village_control_serial.cpp +++ b/village_control_serial.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "village_control.h" #include "event_proxy.h" +#include "attack_trigger.h" template void VillageControl::serialize(Archive& ar, const unsigned int version) { diff --git a/workshops.cpp b/workshops.cpp new file mode 100644 index 000000000..d2925d421 --- /dev/null +++ b/workshops.cpp @@ -0,0 +1,105 @@ +#include "stdafx.h" +#include "workshops.h" +#include "item_factory.h" +#include "view_object.h" + + +Workshops::Workshops(const EnumMap>& o) + : types(o.mapValues([] (const vector& v) { return Type(v);})) { +} + +Workshops::Type& Workshops::get(WorkshopType type) { + return types[type]; +} + +const Workshops::Type& Workshops::get(WorkshopType type) const { + return types[type]; +} + +SERIALIZATION_CONSTRUCTOR_IMPL(Workshops); +SERIALIZE_DEF(Workshops, types); + +Workshops::Type::Type(const vector& o) : options(o) {} + +SERIALIZATION_CONSTRUCTOR_IMPL2(Workshops::Type, Type); +SERIALIZE_DEF(Workshops::Type, options, queued); + + +const vector& Workshops::Type::getOptions() const { + return options; +} + +bool Workshops::Item::operator == (const Item& item) const { + return type == item.type; +} + +void Workshops::Type::stackQueue() { + vector tmp; + for (auto& elem : queued) + if (!tmp.empty() && elem == tmp.back()) + tmp.back().number += elem.number; + else + tmp.push_back(elem); + queued = tmp; +} + +void Workshops::Type::queue(int index) { + const Item& newElem = options[index]; + if (!queued.empty() && queued.back() == newElem) + queued.back().number += newElem.number; + else + queued.push_back(newElem); + stackQueue(); +} + +void Workshops::Type::unqueue(int index) { + if (index >= 0 && index < queued.size()) + queued.erase(queued.begin() + index); + stackQueue(); +} + +void Workshops::Type::changeNumber(int index, int number) { + if (number <= 0) + unqueue(index); + else { + auto& elems = queued; + if (index >= 0 && index < elems.size()) + elems[index].number = number; + } +} + +static const double prodMult = 0.1; + +vector Workshops::Type::addWork(double amount) { + if (!queued.empty()) { + auto& product = queued[0]; + product.state += amount * prodMult / product.workNeeded; + if (product.state >= 1) { + vector ret = ItemFactory::fromId(product.type, product.batchSize); + product.state = 0; + changeNumber(0, product.number - product.batchSize); + return ret; + } + } + return {}; +} + +const vector& Workshops::Type::getQueued() const { + return queued; +} + +Workshops::Item Workshops::Item::fromType(ItemType type, CostInfo cost, double workNeeded, int batchSize) { + PItem item = ItemFactory::fromId(type); + return { + type, + item->getName(), + item->getViewObject().id(), + cost, + true, + batchSize, + batchSize, + workNeeded, + 0 + }; +} + diff --git a/workshops.h b/workshops.h new file mode 100644 index 000000000..f9205e142 --- /dev/null +++ b/workshops.h @@ -0,0 +1,55 @@ +#pragma once + +#include "cost_info.h" +#include "item_type.h" + +RICH_ENUM(WorkshopType, + WORKSHOP, + FORGE, + LABORATORY, + JEWELER +); + +class Workshops { + public: + struct Item { + static Item fromType(ItemType, CostInfo, double workNeeded, int batchSize = 1); + bool operator == (const Item&) const; + ItemType SERIAL(type); + string SERIAL(name); + ViewId SERIAL(viewId); + CostInfo SERIAL(cost); + bool SERIAL(active); + int SERIAL(number); + int SERIAL(batchSize); + double SERIAL(workNeeded); + double SERIAL(state); + SERIALIZE_ALL(type, name, viewId, cost, active, number, batchSize, workNeeded, state); + }; + + class Type { + public: + Type(const vector& options); + const vector& getOptions() const; + const vector& getQueued() const; + vector addWork(double); + void queue(int); + void unqueue(int); + void changeNumber(int index, int number); + + SERIALIZATION_DECL(Type); + + private: + void stackQueue(); + vector SERIAL(options); + vector SERIAL(queued); + }; + + SERIALIZATION_DECL(Workshops); + Workshops(const EnumMap>&); + Type& get(WorkshopType); + const Type& get(WorkshopType) const; + + private: + EnumMap SERIAL(types); +}; From 90b079b70593158d7b5a2a305a2068d8e23912f7 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sat, 6 Aug 2016 08:15:48 +0200 Subject: [PATCH 010/148] Eliminate some header dependencies --- animation.cpp | 1 + animation.h | 3 +- body.cpp | 4 +++ collective.cpp | 2 ++ collective_config.cpp | 1 + corpse_info.h | 12 ++++++++ creature.cpp | 4 +++ creature_attributes.cpp | 20 ++++++++------ creature_attributes.h | 15 ++++------ creature_factory.cpp | 61 +++++++++++++++++++++-------------------- effect.cpp | 1 + game.cpp | 38 ++++++++++++++----------- game.h | 10 ++++--- game_info.h | 1 - inventory.cpp | 2 ++ item.cpp | 16 ++++------- item.h | 32 ++------------------- item_class.h | 19 +++++++++++++ item_factory.cpp | 18 ++++++++++-- item_factory.h | 19 ++++--------- lasting_effect.cpp | 1 + level.cpp | 12 ++++---- level.h | 7 ++--- level_builder.cpp | 1 + level_maker.cpp | 1 + level_maker.h | 1 + logging_view.h | 6 ++-- main_loop.cpp | 6 ++-- map_gui.h | 2 +- minimap_gui.cpp | 1 + minion_equipment.cpp | 4 +++ model.cpp | 2 ++ model_builder.cpp | 2 ++ monster_ai.cpp | 4 +++ player.cpp | 2 ++ player_control.cpp | 2 ++ position.cpp | 1 + position_map.cpp | 1 + singleton.cpp | 1 + spell.cpp | 15 +++++----- spell.h | 29 ++++---------------- spell_id.h | 24 ++++++++++++++++ spell_map.cpp | 1 + spell_map.h | 4 ++- square_factory.cpp | 3 ++ task.cpp | 2 ++ technology.cpp | 1 + tile.cpp | 1 + tile.h | 2 +- trigger.cpp | 10 +++++-- trigger.h | 5 ++-- util.h | 4 +-- view.cpp | 2 ++ view.h | 1 - view_index.h | 3 +- view_layer.h | 13 +++++++++ view_object.h | 11 +------- workshops.cpp | 2 +- 58 files changed, 279 insertions(+), 190 deletions(-) create mode 100644 corpse_info.h create mode 100644 item_class.h create mode 100644 spell_id.h create mode 100644 view_layer.h diff --git a/animation.cpp b/animation.cpp index 614d9b637..ea6f59cf5 100644 --- a/animation.cpp +++ b/animation.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "animation.h" +#include "view_object.h" Animation::Animation(double d) : duration(d) {} diff --git a/animation.h b/animation.h index e853ccd1f..892a36d77 100644 --- a/animation.h +++ b/animation.h @@ -3,7 +3,8 @@ #include "util.h" #include "renderer.h" -#include "view_object.h" + +class ViewObject; class Animation { public: diff --git a/body.cpp b/body.cpp index 148d16a07..106a2705d 100644 --- a/body.cpp +++ b/body.cpp @@ -11,6 +11,10 @@ #include "item_factory.h" #include "position.h" #include "player_message.h" +#include "view_object.h" +#include "item_type.h" +#include "effect.h" +#include "item.h" static double getDefaultWeight(Body::Size size) { switch (size) { diff --git a/collective.cpp b/collective.cpp index 4d22b1e0a..7e7421623 100644 --- a/collective.cpp +++ b/collective.cpp @@ -40,6 +40,8 @@ #include "villain_type.h" #include "workshops.h" #include "attack_trigger.h" +#include "spell_map.h" +#include "body.h" struct Collective::ItemFetchInfo { ItemIndex index; diff --git a/collective_config.cpp b/collective_config.cpp index a1b29dae2..4c461294c 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -13,6 +13,7 @@ #include "lasting_effect.h" #include "item.h" #include "square_type.h" +#include "view_id.h" AttractionInfo::AttractionInfo(MinionAttraction a, double cl, double min, bool mand) : attraction(a), amountClaimed(cl), minAmount(min), mandatory(mand) {} diff --git a/corpse_info.h b/corpse_info.h new file mode 100644 index 000000000..2d13f4b6c --- /dev/null +++ b/corpse_info.h @@ -0,0 +1,12 @@ +#pragma once +#include "unique_entity.h" + +struct CorpseInfo { + UniqueEntity::Id SERIAL(victim); + bool SERIAL(canBeRevived); + bool SERIAL(hasHead); + bool SERIAL(isSkeleton); + SERIALIZE_ALL(canBeRevived, hasHead, isSkeleton); +}; + + diff --git a/creature.cpp b/creature.cpp index 05362774c..4d4df7100 100644 --- a/creature.cpp +++ b/creature.cpp @@ -45,6 +45,10 @@ #include "attack_type.h" #include "attack_level.h" #include "model.h" +#include "view_object.h" +#include "spell.h" +#include "body.h" +#include "field_of_view.h" template void Creature::MoraleOverride::serialize(Archive& ar, const unsigned int version) { diff --git a/creature_attributes.cpp b/creature_attributes.cpp index b6c9f6a06..45d026d42 100644 --- a/creature_attributes.cpp +++ b/creature_attributes.cpp @@ -24,6 +24,10 @@ #include "body.h" #include "attack_level.h" #include "attack_type.h" +#include "view_object.h" +#include "spell_map.h" +#include "effect_type.h" +#include "effect.h" CreatureAttributes::CreatureAttributes(function fun) { fun(*this); @@ -106,7 +110,7 @@ void CreatureAttributes::exerciseAttr(AttrType t, double value) { } SpellMap& CreatureAttributes::getSpellMap() { - return spells; + return *spells; } Body& CreatureAttributes::getBody() { @@ -118,7 +122,7 @@ const Body& CreatureAttributes::getBody() const { } const SpellMap& CreatureAttributes::getSpellMap() const { - return spells; + return *spells; } optional CreatureAttributes::getAttackSound(AttackType type, bool damage) const { @@ -139,8 +143,8 @@ string CreatureAttributes::getDescription() const { if (!isSpecial) return ""; string attack; - if (attackEffect) - attack = " It has a " + Effect::getName(*attackEffect) + " attack."; + if (*attackEffect) + attack = " It has a " + Effect::getName(**attackEffect) + " attack."; return body->getDescription() + ". " + attack; } @@ -252,8 +256,8 @@ void CreatureAttributes::consume(Creature* self, const CreatureAttributes& other consumeAttr(attr[t], other.attr[t], adjectives, getAttrNameMore(t)); consumeAttr(barehandedDamage, other.barehandedDamage, adjectives, "more dangerous"); consumeAttr(barehandedAttack, other.barehandedAttack, adjectives, ""); - consumeAttr(attackEffect, other.attackEffect, adjectives, ""); - consumeAttr(passiveAttack, other.passiveAttack, adjectives, ""); + consumeAttr(*attackEffect, *other.attackEffect, adjectives, ""); + consumeAttr(*passiveAttack, *other.passiveAttack, adjectives, ""); consumeAttr(gender, other.gender, adjectives); consumeAttr(skills, other.skills, adjectives); if (!adjectives.empty()) { @@ -305,7 +309,7 @@ ViewObject CreatureAttributes::createViewObject() const { } const optional& CreatureAttributes::getIllusionViewObject() const { - return illusionViewObject; + return *illusionViewObject; } bool CreatureAttributes::canEquip() const { @@ -342,7 +346,7 @@ int CreatureAttributes::getBarehandedDamage() const { } optional CreatureAttributes::getAttackEffect() const { - return attackEffect; + return *attackEffect; } bool CreatureAttributes::isInnocent() const { diff --git a/creature_attributes.h b/creature_attributes.h index 276bb23ab..b6d240d87 100644 --- a/creature_attributes.h +++ b/creature_attributes.h @@ -22,17 +22,12 @@ #include "util.h" #include "skill.h" #include "gender.h" -#include "effect.h" #include "minion_task.h" #include "creature_name.h" -#include "view_object.h" -#include "spell_map.h" #include "minion_task_map.h" #include "skill.h" #include "modifier_type.h" #include "lasting_effect.h" -#include "body.h" - inline bool isLarger(CreatureSize s1, CreatureSize s2) { return int(s1) > int(s2); @@ -46,6 +41,8 @@ struct SpellInfo; class MinionTaskMap; class SpellMap; class Body; +class SpellMap; +class EffectType; class CreatureAttributes { public: @@ -107,7 +104,7 @@ class CreatureAttributes { private: void consumeEffects(const EnumMap&); MustInitialize SERIAL(viewId); - optional SERIAL(illusionViewObject); + HeapAllocated> SERIAL(illusionViewObject); MustInitialize SERIAL(name); EnumMap SERIAL(attr); HeapAllocated SERIAL(body); @@ -115,8 +112,8 @@ class CreatureAttributes { optional SERIAL(chatReactionHostile); int SERIAL(barehandedDamage) = 0; optional SERIAL(barehandedAttack); - optional SERIAL(attackEffect); - optional SERIAL(passiveAttack); + HeapAllocated> SERIAL(attackEffect); + HeapAllocated> SERIAL(passiveAttack); Gender SERIAL(gender) = Gender::male; optional SERIAL(spawnType); bool SERIAL(innocent) = false; @@ -131,7 +128,7 @@ class CreatureAttributes { double SERIAL(attributeGain) = 0.5; int SERIAL(recruitmentCost) = 0; Skillset SERIAL(skills); - SpellMap SERIAL(spells); + HeapAllocated SERIAL(spells); EnumMap SERIAL(permanentEffects); EnumMap SERIAL(lastingEffects); MinionTaskMap SERIAL(minionTasks); diff --git a/creature_factory.cpp b/creature_factory.cpp index 4915a2242..bab71ab1b 100644 --- a/creature_factory.cpp +++ b/creature_factory.cpp @@ -43,6 +43,9 @@ #include "attack_level.h" #include "attack.h" #include "event_proxy.h" +#include "spell_map.h" +#include "item_type.h" +#include "item.h" template void CreatureFactory::serialize(Archive& ar, const unsigned int version) { @@ -654,7 +657,7 @@ PCreature CreatureFactory::getIllusion(Creature* creature) { return PCreature(new Creature(viewObject, creature->getTribeId(), CATTR( c.viewId = ViewId::ROCK; //overriden anyway c.illusionViewObject = creature->getViewObject(); - c.illusionViewObject->removeModifier(ViewObject::Modifier::INVISIBLE); + (*c.illusionViewObject)->removeModifier(ViewObject::Modifier::INVISIBLE); c.attr[AttrType::SPEED] = 100; c.body = Body::nonHumanoidSpirit(Body::Size::LARGE).setDeathSound(SoundId::MISSED_ATTACK); c.attr[AttrType::STRENGTH] = 1; @@ -1091,7 +1094,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.name = "Keeper"; c.name->setFirst(NameGenerator::get(NameGeneratorId::FIRST)->getNext()); c.name->useFullTitle(); - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.attributeGain = 1; c.minionTasks.setValue(MinionTask::STUDY, 1); c.minionTasks.setValue(MinionTask::LABORATORY, 0.01); @@ -1355,12 +1358,12 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.skills.setValue(SkillId::WEAPON_MELEE, 0.3); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.courage = 3; - c.spells.add(SpellId::HEALING); - c.spells.add(SpellId::SPEED_SELF); - c.spells.add(SpellId::STR_BONUS); - c.spells.add(SpellId::SUMMON_SPIRIT); - c.spells.add(SpellId::STUN_RAY); - c.spells.add(SpellId::BLAST); + c.spells->add(SpellId::HEALING); + c.spells->add(SpellId::SPEED_SELF); + c.spells->add(SpellId::STR_BONUS); + c.spells->add(SpellId::SUMMON_SPIRIT); + c.spells->add(SpellId::STUN_RAY); + c.spells->add(SpellId::BLAST); c.skills.setValue(SkillId::SORCERY, 1); c.skills.insert(SkillId::HEALING); c.name = "shaman";); @@ -1521,7 +1524,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { for (SpellId id : Random.chooseN(Random.get(3, 6), {SpellId::WORD_OF_POWER, SpellId::DEX_BONUS, SpellId::STR_BONUS, SpellId::MAGIC_SHIELD, SpellId::STUN_RAY, SpellId::DECEPTION, SpellId::DECEPTION, SpellId::TELEPORT})) - c.spells.add(id); + c.spells->add(id); c.chatReactionFriendly = c.chatReactionHostile = "\"There are times when you simply cannot refuse a drink!\""; ); @@ -1812,7 +1815,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.insert(SkillId::ELF_VISION); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = CreatureName("elf", "elves");); @@ -1827,7 +1830,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.setValue(SkillId::ARCHERY, 1); c.skills.insert(SkillId::ELF_VISION); c.minionTasks.setValue(MinionTask::SLEEP, 1); @@ -1843,7 +1846,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.insert(SkillId::ELF_VISION); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = CreatureName("elf child", "elf children");); @@ -1858,17 +1861,17 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.setValue(SkillId::ARCHERY, 1); c.skills.setValue(SkillId::WEAPON_MELEE, 1); c.skills.setValue(SkillId::SORCERY, 1); c.skills.insert(SkillId::HEALING); c.skills.insert(SkillId::ELF_VISION); - c.spells.add(SpellId::HEALING); - c.spells.add(SpellId::SPEED_SELF); - c.spells.add(SpellId::STR_BONUS); - c.spells.add(SpellId::STUN_RAY); - c.spells.add(SpellId::BLAST); + c.spells->add(SpellId::HEALING); + c.spells->add(SpellId::SPEED_SELF); + c.spells->add(SpellId::STR_BONUS); + c.spells->add(SpellId::STUN_RAY); + c.spells->add(SpellId::BLAST); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = "elf lord";); case CreatureId::DARK_ELF: @@ -1882,7 +1885,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.insert(SkillId::NIGHT_VISION); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = CreatureName("dark elf", "dark elves");); @@ -1898,7 +1901,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.setValue(SkillId::ARCHERY, 0.5); c.skills.insert(SkillId::NIGHT_VISION); c.skills.setValue(SkillId::WEAPON_MELEE, 1); @@ -1919,7 +1922,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.insert(SkillId::NIGHT_VISION); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = CreatureName("dark elf child", "dark elf children");); @@ -1934,17 +1937,17 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.innocent = true; c.chatReactionFriendly = "curses all dwarves"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.setValue(SkillId::ARCHERY, 1); c.skills.setValue(SkillId::WEAPON_MELEE, 1); c.skills.setValue(SkillId::SORCERY, 1); c.skills.insert(SkillId::HEALING); c.skills.insert(SkillId::NIGHT_VISION); - c.spells.add(SpellId::HEALING); - c.spells.add(SpellId::SPEED_SELF); - c.spells.add(SpellId::STR_BONUS); - c.spells.add(SpellId::STUN_RAY); - c.spells.add(SpellId::BLAST); + c.spells->add(SpellId::HEALING); + c.spells->add(SpellId::SPEED_SELF); + c.spells->add(SpellId::STR_BONUS); + c.spells->add(SpellId::STUN_RAY); + c.spells->add(SpellId::BLAST); c.minionTasks.setValue(MinionTask::SLEEP, 1); c.name = "dark elf lord";); case CreatureId::DRIAD: @@ -1957,7 +1960,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.barehandedDamage = 3; c.chatReactionFriendly = "curses all humans"; c.chatReactionHostile = "\"Die!\""; - c.spells.add(SpellId::HEALING); + c.spells->add(SpellId::HEALING); c.skills.insert(SkillId::ELF_VISION); c.skills.setValue(SkillId::ARCHERY, 1); c.name = "driad";); @@ -2303,7 +2306,7 @@ CreatureAttributes CreatureFactory::getAttributes(CreatureId id) { c.barehandedAttack = AttackType::HIT; c.barehandedDamage = 10; c.permanentEffects[LastingEffect::FLYING] = 1; - c.spells.add(SpellId::AIR_BLAST); + c.spells->add(SpellId::AIR_BLAST); c.name = "air elemental";); case CreatureId::EARTH_ELEMENTAL: return CATTR( diff --git a/effect.cpp b/effect.cpp index 7023b3469..50e5311de 100644 --- a/effect.cpp +++ b/effect.cpp @@ -38,6 +38,7 @@ #include "attack_type.h" #include "body.h" #include "event_listener.h" +#include "item_class.h" vector healingPoints { 5, 15, 40}; vector sleepTime { 15, 80, 200}; diff --git a/game.cpp b/game.cpp index 89b392f28..f77576a63 100644 --- a/game.cpp +++ b/game.cpp @@ -27,6 +27,8 @@ #include "villain_type.h" #include "square_type.h" #include "attack_trigger.h" +#include "view_object.h" +#include "campaign.h" template void Game::serialize(Archive& ar, const unsigned int version) { @@ -91,13 +93,13 @@ PGame Game::campaignGame(Table&& models, Vec2 basePos, const string& pla PGame Game::singleMapGame(const string& worldName, const string& playerName, PModel&& model) { Table t(1, 1); t[0][0] = std::move(model); - return PGame(new Game(worldName, playerName, std::move(t), Vec2(0, 0))); + return PGame(new Game(worldName, playerName, std::move(t), Vec2(0, 0), none)); } PGame Game::splashScreen(PModel&& model) { Table t(1, 1); t[0][0] = std::move(model); - PGame game(new Game("", "", std::move(t), Vec2(0, 0))); + PGame game(new Game("", "", std::move(t), Vec2(0, 0), none)); game->spectator.reset(new Spectator(game->models[0][0]->getTopLevel())); game->turnEvents.clear(); return game; @@ -107,7 +109,11 @@ bool Game::isTurnBased() { return !spectator && (!playerControl || playerControl->isTurnBased()); } -const Campaign& Game::getCampaign() const { +const optional& Game::getCampaign() const { + return *campaign; +} + +optional& Game::getCampaign() { return *campaign; } @@ -277,15 +283,15 @@ optional Game::updateModel(Model* model, double totalTime) { bool Game::isVillainActive(const Collective* col) { const Model* m = col->getLevel()->getModel(); - return m == getMainModel().get() || campaign->isInInfluence(getModelCoords(m)); + return m == getMainModel().get() || getCampaign()->isInInfluence(getModelCoords(m)); } void Game::tick(double time) { if (!turnEvents.empty() && time > *turnEvents.begin()) { int turn = *turnEvents.begin(); if (turn == 0) { - if (campaign) - uploadEvent("campaignStarted", campaign->getParameters()); + if (getCampaign()) + uploadEvent("campaignStarted", getCampaign()->getParameters()); else uploadEvent("singleStarted", map()); } else @@ -374,13 +380,13 @@ Vec2 Game::getModelCoords(const Model* m) const { } void Game::presentWorldmap() { - view->presentWorldmap(*campaign); + view->presentWorldmap(*getCampaign()); } void Game::transferAction(vector creatures) { - if (!campaign) + if (!getCampaign()) return; - if (auto dest = view->chooseSite("Choose destination site:", *campaign, + if (auto dest = view->chooseSite("Choose destination site:", *getCampaign(), getModelCoords(creatures[0]->getLevel()->getModel()))) { Model* to = NOTNULL(models[*dest].get()); vector cant; @@ -396,7 +402,7 @@ void Game::transferAction(vector creatures) { transferCreature(c, models[*dest].get()); if (!visited[*dest]) { visited[*dest] = true; - if (auto retired = campaign->getSites()[*dest].getRetired()) + if (auto retired = getCampaign()->getSites()[*dest].getRetired()) uploadEvent("retiredLoaded", { {"retiredId", getGameId(retired->fileInfo)}, {"playerName", getPlayerName()}}); @@ -585,8 +591,8 @@ string Game::getPlayerName() const { SavedGameInfo Game::getSavedGameInfo() const { int numSites = 1; - if (campaign) - numSites = campaign->getNumNonEmpty(); + if (getCampaign()) + numSites = getCampaign()->getNumNonEmpty(); if (Collective* col = getPlayerCollective()) { vector creatures = col->getCreatures(); CHECK(!creatures.empty()); @@ -662,14 +668,14 @@ void Game::addEvent(const GameEvent& event) { switch (event.getId()) { case EventId::CONQUERED_ENEMY: { Collective* col = event.get(); - if (campaign && col->getVillainType()) { + if (getCampaign() && col->getVillainType()) { Vec2 coords = getModelCoords(col->getLevel()->getModel()); - if (!campaign->isDefeated(coords)) { - if (auto retired = campaign->getSites()[coords].getRetired()) + if (!getCampaign()->isDefeated(coords)) { + if (auto retired = getCampaign()->getSites()[coords].getRetired()) uploadEvent("retiredConquered", { {"retiredId", getGameId(retired->fileInfo)}, {"playerName", getPlayerName()}}); - campaign->setDefeated(coords); + getCampaign()->setDefeated(coords); } } if (col->getVillainType() == VillainType::MAIN && gameWon()) { diff --git a/game.h b/game.h index b4cea747e..ceac8548f 100644 --- a/game.h +++ b/game.h @@ -5,7 +5,6 @@ #include "sunlight_info.h" #include "tribe.h" #include "enum_variant.h" -#include "campaign.h" #include "position.h" class Options; @@ -18,6 +17,8 @@ class FileSharing; class Technology; class EventListener; class GameEvent; +class Campaign; +class SavedGameInfo; RICH_ENUM(GameSaveType, ADVENTURER, @@ -77,7 +78,6 @@ class Game { bool isGameOver() const; bool isTurnBased(); bool isSingleModel() const; - const Campaign& getCampaign() const; bool isVillainActive(const Collective*); SavedGameInfo getSavedGameInfo() const; @@ -101,7 +101,7 @@ class Game { SERIALIZATION_DECL(Game); private: - Game(const string& worldName, const string& playerName, Table&&, Vec2 basePos, optional = none); + Game(const string& worldName, const string& playerName, Table&&, Vec2 basePos, optional); void updateSunlightInfo(); void tick(double time); PCreature makeAdventurer(int handicap); @@ -110,6 +110,8 @@ class Game { optional updateModel(Model*, double totalTime); string getPlayerName() const; void uploadEvent(const string& name, const map&); + optional& getCampaign(); + const optional& getCampaign() const; string SERIAL(worldName); SunlightInfo sunlightInfo; @@ -136,7 +138,7 @@ class Game { double lastUpdate = -10; PlayerControl* SERIAL(playerControl) = nullptr; Collective* SERIAL(playerCollective) = nullptr; - optional SERIAL(campaign); + HeapAllocated> SERIAL(campaign); bool wasTransfered = false; Creature* SERIAL(player) = nullptr; FileSharing* fileSharing; diff --git a/game_info.h b/game_info.h index 5e3193499..fa0cb91d4 100644 --- a/game_info.h +++ b/game_info.h @@ -1,7 +1,6 @@ #ifndef _GAME_INFO_H #define _GAME_INFO_H -#include "view_object.h" #include "unique_entity.h" #include "minion_task.h" #include "item_action.h" diff --git a/inventory.cpp b/inventory.cpp index 6f8c4999f..2ecb8286d 100644 --- a/inventory.cpp +++ b/inventory.cpp @@ -19,6 +19,8 @@ #include "item.h" #include "minion_equipment.h" #include "resource_id.h" +#include "item_class.h" +#include "corpse_info.h" template void Inventory::serialize(Archive& ar, const unsigned int version) { diff --git a/item.cpp b/item.cpp index 7a06c5434..faf559944 100644 --- a/item.cpp +++ b/item.cpp @@ -27,6 +27,8 @@ #include "item_attributes.h" #include "view.h" #include "sound.h" +#include "item_class.h" +#include "corpse_info.h" template void Item::serialize(Archive& ar, const unsigned int version) { @@ -40,16 +42,6 @@ void Item::serialize(Archive& ar, const unsigned int version) { SERIALIZABLE(Item); SERIALIZATION_CONSTRUCTOR_IMPL(Item); -template -void Item::CorpseInfo::serialize(Archive& ar, const unsigned int version) { - ar& BOOST_SERIALIZATION_NVP(canBeRevived) - & BOOST_SERIALIZATION_NVP(hasHead) - & BOOST_SERIALIZATION_NVP(isSkeleton); -} - -SERIALIZABLE(Item::CorpseInfo); - - Item::Item(const ItemAttributes& attr) : Renderable(ViewObject(*attr.viewId, ViewLayer::ITEM, *attr.name)), attributes(attr), fire(*attr.weight, attr.flamability) { } @@ -444,3 +436,7 @@ int Item::getMinStrength() const { return 10 + getWeight(); } +optional Item::getCorpseInfo() const { + return none; +} + diff --git a/item.h b/item.h index 12c896e26..b60e6b742 100644 --- a/item.h +++ b/item.h @@ -20,13 +20,14 @@ #include "enums.h" #include "unique_entity.h" #include "renderable.h" -#include "effect_type.h" #include "position.h" class Level; class Attack; class Fire; class ItemAttributes; +class EffectType; +struct CorpseInfo; RICH_ENUM(TrapType, BOULDER, @@ -37,23 +38,6 @@ RICH_ENUM(TrapType, TERROR ); -RICH_ENUM(ItemClass, - WEAPON, - RANGED_WEAPON, - AMMO, - ARMOR, - SCROLL, - POTION, - BOOK, - AMULET, - RING, - TOOL, - OTHER, - GOLD, - FOOD, - CORPSE -); - class Item : public Renderable, public UniqueEntity { public: Item(const ItemAttributes&); @@ -126,17 +110,7 @@ class Item : public Renderable, public UniqueEntity { static vector>> stackItems(vector, function addSuffix = [](const Item*) { return ""; }); - struct CorpseInfo { - UniqueEntity::Id victim; - bool canBeRevived; - bool hasHead; - bool isSkeleton; - - template - void serialize(Archive& ar, const unsigned int version); - }; - - virtual optional getCorpseInfo() const { return none; } + virtual optional getCorpseInfo() const; SERIALIZATION_DECL(Item); diff --git a/item_class.h b/item_class.h new file mode 100644 index 000000000..52cf48a46 --- /dev/null +++ b/item_class.h @@ -0,0 +1,19 @@ +#pragma once + +RICH_ENUM(ItemClass, + WEAPON, + RANGED_WEAPON, + AMMO, + ARMOR, + SCROLL, + POTION, + BOOK, + AMULET, + RING, + TOOL, + OTHER, + GOLD, + FOOD, + CORPSE +); + diff --git a/item_factory.cpp b/item_factory.cpp index be0bee57f..11d72055e 100644 --- a/item_factory.cpp +++ b/item_factory.cpp @@ -38,6 +38,9 @@ #include "sound.h" #include "creature_attributes.h" #include "event_listener.h" +#include "item_type.h" +#include "body.h" +#include "item.h" template void ItemFactory::serialize(Archive& ar, const unsigned int version) { @@ -52,6 +55,17 @@ SERIALIZABLE(ItemFactory); SERIALIZATION_CONSTRUCTOR_IMPL(ItemFactory); +struct ItemFactory::ItemInfo { + ItemInfo(ItemType _id, double _weight) : id(_id), weight(_weight) {} + ItemInfo(ItemType _id, double _weight, int minC, int maxC) + : id(_id), weight(_weight), minCount(minC), maxCount(maxC) {} + + ItemType id; + double weight; + int minCount = 1; + int maxCount = 2; +}; + class FireScroll : public Item { public: FireScroll(const ItemAttributes& attr) : Item(attr) {} @@ -162,7 +176,7 @@ class ItemOfCreatureVision : public Item { class Corpse : public Item { public: Corpse(const ViewObject& obj2, const ItemAttributes& attr, const string& rottenN, - double rottingT, Item::CorpseInfo info) : + double rottingT, CorpseInfo info) : Item(attr), object2(obj2), rottingTime(rottingT), @@ -224,7 +238,7 @@ class Corpse : public Item { }; PItem ItemFactory::corpse(const string& name, const string& rottenName, double weight, ItemClass itemClass, - Item::CorpseInfo corpseInfo) { + CorpseInfo corpseInfo) { const double rotTime = 300; return PItem(new Corpse( ViewObject(ViewId::BONE, ViewLayer::ITEM, rottenName), diff --git a/item_factory.h b/item_factory.h index 07a6067b6..f3cd1d536 100644 --- a/item_factory.h +++ b/item_factory.h @@ -21,11 +21,13 @@ #include #include "util.h" -#include "item.h" -#include "item_type.h" +#include "corpse_info.h" +#include "item_class.h" class Item; class Technology; +class ItemType; +class ItemAttributes; class ItemFactory { public: @@ -50,7 +52,7 @@ class ItemFactory { static PItem fromId(ItemType); static vector fromId(ItemType, int num); static PItem corpse(const string& name, const string& rottenName, double weight, ItemClass = ItemClass::CORPSE, - Item::CorpseInfo corpseInfo = {UniqueEntity::Id(), false, false, false}); + CorpseInfo corpseInfo = {UniqueEntity::Id(), false, false, false}); static PItem trapItem(PTrigger trigger, string trapName); static void init(); @@ -61,16 +63,7 @@ class ItemFactory { SERIALIZATION_DECL(ItemFactory); private: - struct ItemInfo { - ItemInfo(ItemType _id, double _weight) : id(_id), weight(_weight) {} - ItemInfo(ItemType _id, double _weight, int minC, int maxC) - : id(_id), weight(_weight), minCount(minC), maxCount(maxC) {} - - ItemType id; - double weight; - int minCount = 1; - int maxCount = 2; - }; + struct ItemInfo; ItemFactory(const vector&, const vector& unique = vector()); static ItemAttributes getAttributes(ItemType); ItemFactory& addItem(ItemInfo); diff --git a/lasting_effect.cpp b/lasting_effect.cpp index ded1045ac..b46ed7cc6 100644 --- a/lasting_effect.cpp +++ b/lasting_effect.cpp @@ -4,6 +4,7 @@ #include "view_object.h" #include "player_message.h" #include "creature_attributes.h" +#include "body.h" void LastingEffects::onAffected(Creature* c, LastingEffect effect, bool msg) { switch (effect) { diff --git a/level.cpp b/level.cpp index 3c65f61d4..167910b45 100644 --- a/level.cpp +++ b/level.cpp @@ -35,6 +35,8 @@ #include "game.h" #include "creature_attributes.h" #include "square_array.h" +#include "view_object.h" +#include "field_of_view.h" template void Level::serialize(Archive& ar, const unsigned int version) { @@ -65,7 +67,7 @@ Level::Level(SquareArray s, Model* m, vector l, const string& n, for (Location *l : locations) l->setLevel(this); for (VisionId vision : ENUM_ALL(VisionId)) - fieldOfView[vision] = FieldOfView(*squares, vision); + (*fieldOfView)[vision] = FieldOfView(*squares, vision); for (Vec2 pos : squares->getBounds()) addLightSource(pos, squares->getReadonly(pos)->getLightEmission(), 1); } @@ -157,7 +159,7 @@ void Level::replaceSquare(Position position, PSquare newSquare, bool storePrevio for (PTrigger& t : oldSquare->removeTriggers(position)) newSquare->addTrigger(position, std::move(t)); if (auto backgroundObj = oldSquare->extractBackground()) - background[pos] = backgroundObj; + (*background)[pos] = backgroundObj; if (auto tribe = oldSquare->getForbiddenTribe()) newSquare->forbidMovementForTribe(position, *tribe); if (storePrevious) @@ -180,7 +182,7 @@ void Level::updateVisibility(Vec2 changedSquare) { addDarknessSource(pos, darknessRadius, -1); } for (VisionId vision : ENUM_ALL(VisionId)) - fieldOfView[vision].squareChanged(changedSquare); + getFieldOfView(vision).squareChanged(changedSquare); for (Vec2 pos : getVisibleTilesNoDarkness(changedSquare, VisionId::NORMAL)) { addLightSource(pos, squares->getReadonly(pos)->getLightEmission(), 1); if (Creature* c = squares->getReadonly(pos)->getCreature()) @@ -449,7 +451,7 @@ bool Level::isWithinVision(Vec2 from, Vec2 to, VisionId v) const { } FieldOfView& Level::getFieldOfView(VisionId vision) const { - return fieldOfView[vision]; + return (*fieldOfView)[vision]; } bool Level::canSee(Vec2 from, Vec2 to, VisionId vision) const { @@ -618,7 +620,7 @@ void Level::updateSunlightMovement() { } const optional& Level::getBackgroundObject(Vec2 pos) const { - return background[pos]; + return (*background)[pos]; } int Level::getNumModifiedSquares() const { diff --git a/level.h b/level.h index f53ecc2d8..e043d3fe2 100644 --- a/level.h +++ b/level.h @@ -18,13 +18,11 @@ #include "util.h" #include "debug.h" -#include "field_of_view.h" #include "unique_entity.h" #include "movement_type.h" #include "sectors.h" #include "stair_key.h" #include "entity_set.h" -#include "view_object.h" class Model; class Square; @@ -42,6 +40,7 @@ class CreatureBucketMap; class Position; class Game; class SquareArray; +class FieldOfView; RICH_ENUM(VisionId, ELF, @@ -232,7 +231,7 @@ class Level { Vec2 transform(Vec2); HeapAllocated SERIAL(squares); Table SERIAL(oldSquares); - Table> SERIAL(background); + HeapAllocated>> SERIAL(background); Table SERIAL(memoryUpdates); Table renderUpdates = Table(getMaxBounds(), true); Table SERIAL(unavailable); @@ -245,7 +244,7 @@ class Level { vector SERIAL(creatures); EntitySet SERIAL(creatureIds); Model* SERIAL(model) = nullptr; - mutable EnumMap SERIAL(fieldOfView); + mutable HeapAllocated> SERIAL(fieldOfView); string SERIAL(name); const Level* SERIAL(backgroundLevel) = nullptr; Vec2 SERIAL(backgroundOffset); diff --git a/level_builder.cpp b/level_builder.cpp index 119d37d74..ff4f9c50c 100644 --- a/level_builder.cpp +++ b/level_builder.cpp @@ -8,6 +8,7 @@ #include "level_maker.h" #include "collective_builder.h" #include "view_object.h" +#include "item.h" LevelBuilder::LevelBuilder(ProgressMeter* meter, RandomGen& r, int width, int height, const string& n, bool covered, optional defaultLight) diff --git a/level_maker.cpp b/level_maker.cpp index bbff291e3..6533f5ad1 100644 --- a/level_maker.cpp +++ b/level_maker.cpp @@ -27,6 +27,7 @@ #include "square_factory.h" #include "model.h" #include "monster_ai.h" +#include "item.h" namespace { diff --git a/level_maker.h b/level_maker.h index 885968e2c..cfeaa41ab 100644 --- a/level_maker.h +++ b/level_maker.h @@ -20,6 +20,7 @@ #include "creature_factory.h" #include "item_factory.h" #include "square_factory.h" +#include "item_type.h" enum class BuildingId { WOOD, MUD, BRICK, WOOD_CASTLE, DUNGEON}; diff --git a/logging_view.h b/logging_view.h index 65b521696..03d78b2e5 100644 --- a/logging_view.h +++ b/logging_view.h @@ -13,8 +13,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ . */ -#ifndef _LOGGING_VIEW -#define _LOGGING_VIEW +#pragma once + +#include "view_object.h" enum class LoggingToken { GET_TIME, @@ -203,4 +204,3 @@ class LoggingView : public View { View* delegate; }; -#endif diff --git a/main_loop.cpp b/main_loop.cpp index 8d18bcbfc..fecb810e0 100644 --- a/main_loop.cpp +++ b/main_loop.cpp @@ -169,8 +169,10 @@ void MainLoop::saveUI(PGame& game, GameSaveType type, SplashType splashType) { int saveTime = 0; if (game->isSingleModel() || type == GameSaveType::RETIRED_SITE) saveTime = singleModelGameSaveTime; - else - saveTime = game->getCampaign().getNumNonEmpty(); + else { + auto savedGameInfo = game->getSavedGameInfo(); + saveTime = savedGameInfo.getNumSites(); + } if (type == GameSaveType::RETIRED_SITE) doWithSplash(splashType, "Retiring site...", saveTime, [&] (ProgressMeter& meter) { diff --git a/map_gui.h b/map_gui.h index 9f2774c8b..55dfb41bf 100644 --- a/map_gui.h +++ b/map_gui.h @@ -22,10 +22,10 @@ #include "unique_entity.h" #include "view_index.h" #include "entity_map.h" +#include "view_object.h" class MapMemory; class MapLayout; -class ViewObject; class Renderer; class CreatureView; class Clock; diff --git a/minimap_gui.cpp b/minimap_gui.cpp index 089feb6f2..2f42191b5 100644 --- a/minimap_gui.cpp +++ b/minimap_gui.cpp @@ -23,6 +23,7 @@ #include "map_memory.h" #include "view_index.h" #include "sdl.h" +#include "view_object.h" void MinimapGui::renderMap(Renderer& renderer, Rectangle target) { if (!mapBufferTex) diff --git a/minion_equipment.cpp b/minion_equipment.cpp index 10e736ec0..8975be7cf 100644 --- a/minion_equipment.cpp +++ b/minion_equipment.cpp @@ -22,6 +22,10 @@ #include "equipment.h" #include "modifier_type.h" #include "creature_attributes.h" +#include "effect_type.h" +#include "body.h" +#include "item_class.h" +#include "corpse_info.h" static vector combatConsumables { EffectType(EffectId::LASTING, LastingEffect::SPEED), diff --git a/model.cpp b/model.cpp index 9ac03145e..094e7a333 100644 --- a/model.cpp +++ b/model.cpp @@ -45,6 +45,8 @@ #include "territory.h" #include "game.h" #include "progress_meter.h" +#include "view_object.h" +#include "item.h" template void Model::serialize(Archive& ar, const unsigned int version) { diff --git a/model_builder.cpp b/model_builder.cpp index 2b75ca8b6..fba89ca98 100644 --- a/model_builder.cpp +++ b/model_builder.cpp @@ -23,6 +23,8 @@ #include "enemy_factory.h" #include "location.h" #include "event_proxy.h" +#include "view_object.h" +#include "item.h" using namespace std::chrono; diff --git a/monster_ai.cpp b/monster_ai.cpp index a765a0306..70c588547 100644 --- a/monster_ai.cpp +++ b/monster_ai.cpp @@ -32,6 +32,10 @@ #include "game.h" #include "creature_attributes.h" #include "creature_factory.h" +#include "spell_map.h" +#include "effect_type.h" +#include "body.h" +#include "item_class.h" class Behaviour { public: diff --git a/player.cpp b/player.cpp index cc7e112aa..64af836c8 100644 --- a/player.cpp +++ b/player.cpp @@ -46,6 +46,8 @@ #include "event_proxy.h" #include "visibility_map.h" #include "collective_name.h" +#include "view_object.h" +#include "body.h" template void Player::serialize(Archive& ar, const unsigned int version) { diff --git a/player_control.cpp b/player_control.cpp index ee46a97d9..e0ff63982 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -63,6 +63,8 @@ #include "event_proxy.h" #include "workshops.h" #include "attack_trigger.h" +#include "view_object.h" +#include "body.h" template void PlayerControl::serialize(Archive& ar, const unsigned int version) { diff --git a/position.cpp b/position.cpp index 950c63f41..3dbd8ed3c 100644 --- a/position.cpp +++ b/position.cpp @@ -13,6 +13,7 @@ #include "game.h" #include "view.h" #include "square_interaction.h" +#include "view_object.h" template void Position::serialize(Archive& ar, const unsigned int version) { diff --git a/position_map.cpp b/position_map.cpp index 81b39d383..3114afb53 100644 --- a/position_map.cpp +++ b/position_map.cpp @@ -4,6 +4,7 @@ #include "task.h" #include "view_index.h" #include "model.h" +#include "view_object.h" template PositionMap::PositionMap(const T& def) : defaultVal(def) { diff --git a/singleton.cpp b/singleton.cpp index 3bc6e281d..274a9459b 100644 --- a/singleton.cpp +++ b/singleton.cpp @@ -23,6 +23,7 @@ #include "skill.h" #include "name_generator.h" #include "spell.h" +#include "effect_type.h" template EnumMap> Singleton::elems; diff --git a/spell.cpp b/spell.cpp index 5adf2c52e..1a3cdb4ab 100644 --- a/spell.cpp +++ b/spell.cpp @@ -7,31 +7,32 @@ #include "creature_factory.h" #include "sound.h" #include "lasting_effect.h" +#include "effect_type.h" const string& Spell::getName() const { return name; } bool Spell::isDirected() const { - return boost::get(&effect); + return boost::get(&*effect); } bool Spell::hasEffect(EffectType t) const { - return !isDirected() && boost::get(effect) == t; + return !isDirected() && boost::get(*effect) == t; } bool Spell::hasEffect(DirEffectType t) const { - return isDirected() && boost::get(effect) == t; + return isDirected() && boost::get(*effect) == t; } EffectType Spell::getEffectType() const { CHECK(!isDirected()); - return boost::get(effect); + return boost::get(*effect); } DirEffectType Spell::getDirEffectType() const{ CHECK(isDirected()); - return boost::get(effect); + return boost::get(*effect); } int Spell::getDifficulty() const { @@ -52,9 +53,9 @@ SoundId Spell::getSound() const { string Spell::getDescription() const { if (isDirected()) - return Effect::getDescription(boost::get(effect)); + return Effect::getDescription(boost::get(*effect)); else - return Effect::getDescription(boost::get(effect)); + return Effect::getDescription(boost::get(*effect)); } void Spell::addMessage(Creature* c) { diff --git a/spell.h b/spell.h index ec2753be0..b0b130ee7 100644 --- a/spell.h +++ b/spell.h @@ -19,34 +19,17 @@ #include "enums.h" #include "util.h" #include "singleton.h" -#include "effect_type.h" - -RICH_ENUM(SpellId, - HEALING, - SUMMON_INSECTS, - DECEPTION, - SPEED_SELF, - STR_BONUS, - DEX_BONUS, - FIRE_SPHERE_PET, - TELEPORT, - INVISIBILITY, - WORD_OF_POWER, - AIR_BLAST, - SUMMON_SPIRIT, - PORTAL, - CURE_POISON, - METEOR_SHOWER, - MAGIC_SHIELD, - BLAST, - STUN_RAY -); +#include "spell_id.h" enum class CastMessageType { STANDARD, AIR_BLAST }; +class EffectType; +class DirEffectType; +class EffectType; + class Spell : public Singleton { public: const string& getName() const; @@ -67,7 +50,7 @@ class Spell : public Singleton { Spell(const string&, DirEffectType, int difficulty, SoundId, CastMessageType = CastMessageType::STANDARD); const string name; - const variant effect; + const HeapAllocated> effect; const int difficulty; const CastMessageType castMessageType; const SoundId sound; diff --git a/spell_id.h b/spell_id.h new file mode 100644 index 000000000..75d420ff3 --- /dev/null +++ b/spell_id.h @@ -0,0 +1,24 @@ +#pragma once + +RICH_ENUM(SpellId, + HEALING, + SUMMON_INSECTS, + DECEPTION, + SPEED_SELF, + STR_BONUS, + DEX_BONUS, + FIRE_SPHERE_PET, + TELEPORT, + INVISIBILITY, + WORD_OF_POWER, + AIR_BLAST, + SUMMON_SPIRIT, + PORTAL, + CURE_POISON, + METEOR_SHOWER, + MAGIC_SHIELD, + BLAST, + STUN_RAY +); + + diff --git a/spell_map.cpp b/spell_map.cpp index 454f6dbfd..3d21dc125 100644 --- a/spell_map.cpp +++ b/spell_map.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "spell_map.h" +#include "spell.h" void SpellMap::add(Spell* spell) { add(spell->getId()); diff --git a/spell_map.h b/spell_map.h index 0606938fe..24ad17c9a 100644 --- a/spell_map.h +++ b/spell_map.h @@ -16,7 +16,9 @@ #ifndef _SPELL_MAP_H #define _SPELL_MAP_H -#include "spell.h" +#include "spell_id.h" + +class Spell; class SpellMap { public: diff --git a/square_factory.cpp b/square_factory.cpp index ded066d86..690468329 100644 --- a/square_factory.cpp +++ b/square_factory.cpp @@ -41,6 +41,9 @@ #include "game.h" #include "event_listener.h" #include "square_type.h" +#include "item_type.h" +#include "body.h" +#include "item.h" class Magma : public Square { public: diff --git a/task.cpp b/task.cpp index 768c27d9c..7dc1cb0de 100644 --- a/task.cpp +++ b/task.cpp @@ -39,6 +39,8 @@ #include "collective_name.h" #include "creature_attributes.h" #include "square_type.h" +#include "item_type.h" +#include "body.h" template void Task::serialize(Archive& ar, const unsigned int version) { diff --git a/technology.cpp b/technology.cpp index 52bece9e2..3dce9eb4d 100644 --- a/technology.cpp +++ b/technology.cpp @@ -26,6 +26,7 @@ #include "creature.h" #include "creature_attributes.h" #include "square_type.h" +#include "spell_map.h" void Technology::init() { Technology::set(TechId::ALCHEMY, new Technology( diff --git a/tile.cpp b/tile.cpp index d59b0d5c1..cbdd54cea 100644 --- a/tile.cpp +++ b/tile.cpp @@ -17,6 +17,7 @@ #include "tile.h" #include "gui_elem.h" #include "view_id.h" +#include "view_object.h" Tile::Tile() { } diff --git a/tile.h b/tile.h index 658e1fbab..a17a8c9f9 100644 --- a/tile.h +++ b/tile.h @@ -16,11 +16,11 @@ #ifndef _TILE_H #define _TILE_H -#include "view_object.h" #include "renderer.h" #include "util.h" enum class ViewId; +class ViewObject; class Tile { public: diff --git a/trigger.cpp b/trigger.cpp index 387402aa7..5027b7c5c 100644 --- a/trigger.cpp +++ b/trigger.cpp @@ -33,6 +33,7 @@ #include "attack_level.h" #include "attack_type.h" #include "event_listener.h" +#include "item_type.h" template void Trigger::serialize(Archive& ar, const unsigned int version) { @@ -49,11 +50,14 @@ Trigger::Trigger(Position p) : position(p) { Trigger::~Trigger() {} -Trigger::Trigger(const ViewObject& obj, Position p): viewObject(obj), position(p) { +Trigger::Trigger(const ViewObject& obj, Position p): viewObject(new ViewObject(obj)), position(p) { } optional Trigger::getViewObject(const Creature*) const { - return viewObject; + if (viewObject) + return *viewObject; + else + return none; } void Trigger::onCreatureEnter(Creature* c) {} @@ -159,7 +163,7 @@ class Trap : public Trigger { virtual optional getViewObject(const Creature* viewer) const override { if (!viewer || alwaysVisible || !viewer->getGame()->getTribe(tribe)->isEnemy(viewer) || viewer->getAttributes().getSkills().hasDiscrete(SkillId::DISARM_TRAPS)) - return viewObject; + return *viewObject; else return none; } diff --git a/trigger.h b/trigger.h index fa8120f9f..8e8aa4af5 100644 --- a/trigger.h +++ b/trigger.h @@ -17,8 +17,6 @@ #define _TRIGGER_H #include "util.h" -#include "effect_type.h" -#include "view_object.h" #include "position.h" class Creature; @@ -26,6 +24,7 @@ class CreatureView; class Attack; class Tribe; class ViewObject; +class EffectType; class Trigger { public: @@ -57,7 +56,7 @@ class Trigger { Trigger(Position); Trigger(const ViewObject& obj, Position); - optional SERIAL(viewObject); + unique_ptr SERIAL(viewObject); Position SERIAL(position); }; diff --git a/util.h b/util.h index bb355ffd1..34da1b790 100644 --- a/util.h +++ b/util.h @@ -1459,10 +1459,10 @@ class HeapAllocated { return elem.get(); } - HeapAllocated& operator = (const T& t) { + /*HeapAllocated& operator = (const T& t) { *elem.get() = t; return *this; - } + }*/ HeapAllocated& operator = (const HeapAllocated& t) { *elem.get() = *t; diff --git a/view.cpp b/view.cpp index 1feee4b6e..e96bf43d7 100644 --- a/view.cpp +++ b/view.cpp @@ -27,6 +27,8 @@ #include "level.h" #include "position.h" #include "creature_attributes.h" +#include "view_object.h" +#include "spell_map.h" ListElem::ListElem(const string& t, ElemMod m, optional a) : text(t), mod(m), action(a) { } diff --git a/view.h b/view.h index 972207d08..3a28122e0 100644 --- a/view.h +++ b/view.h @@ -18,7 +18,6 @@ #include "util.h" #include "debug.h" -#include "view_object.h" #include "user_input.h" #include "minion_task.h" #include "cost_info.h" diff --git a/view_index.h b/view_index.h index e35589ef5..91be14b2d 100644 --- a/view_index.h +++ b/view_index.h @@ -18,8 +18,9 @@ #include "enums.h" #include "util.h" -#include "view_object.h" +#include "view_layer.h" +class ViewObject; RICH_ENUM(HighlightType, DIG, diff --git a/view_layer.h b/view_layer.h new file mode 100644 index 000000000..5044489b9 --- /dev/null +++ b/view_layer.h @@ -0,0 +1,13 @@ +#pragma once + + +RICH_ENUM(ViewLayer, + FLOOR_BACKGROUND, + FLOOR, + ITEM, + LARGE_ITEM, + TORCH1, + CREATURE, + TORCH2 +); + diff --git a/view_object.h b/view_object.h index 23e95f6a0..7f963a40d 100644 --- a/view_object.h +++ b/view_object.h @@ -20,16 +20,7 @@ #include "enums.h" #include "util.h" #include "unique_entity.h" - -RICH_ENUM(ViewLayer, - FLOOR_BACKGROUND, - FLOOR, - ITEM, - LARGE_ITEM, - TORCH1, - CREATURE, - TORCH2 -); +#include "view_layer.h" RICH_ENUM(ViewObjectModifier, BLIND, PLAYER, HIDDEN, INVISIBLE, ILLUSION, POISONED, CASTS_SHADOW, PLANNED, LOCKED, ROUND_SHADOW, TEAM_LEADER_HIGHLIGHT, TEAM_HIGHLIGHT, DRAW_MORALE, SLEEPING, ROAD, NO_UP_MOVEMENT, REMEMBER, diff --git a/workshops.cpp b/workshops.cpp index d2925d421..ac661714a 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -2,7 +2,7 @@ #include "workshops.h" #include "item_factory.h" #include "view_object.h" - +#include "item.h" Workshops::Workshops(const EnumMap>& o) : types(o.mapValues([] (const vector& v) { return Type(v);})) { From d9fb118e09b29da990b2997eed82aff70b5587a6 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sat, 6 Aug 2016 11:08:49 +0200 Subject: [PATCH 011/148] Change how production of item batches is displayed --- collective_config.cpp | 2 +- gui_builder.cpp | 2 +- item.cpp | 7 +++++++ item.h | 1 + workshops.cpp | 6 +++--- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/collective_config.cpp b/collective_config.cpp index 4c461294c..2a4755f05 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -367,7 +367,7 @@ HeapAllocated CollectiveConfig::getWorkshops() const { Workshops::Item::fromType(ItemId::CLUB, {CollectiveResourceId::WOOD, 50}, 3), Workshops::Item::fromType(ItemId::HEAVY_CLUB, {CollectiveResourceId::WOOD, 100}, 5), Workshops::Item::fromType(ItemId::BOW, {CollectiveResourceId::WOOD, 100}, 13), - Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 2}, 5, 20), + Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 50}, 5, 20), Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, {CollectiveResourceId::STONE, 250}, 20), Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, {CollectiveResourceId::WOOD, 100}, 10), diff --git a/gui_builder.cpp b/gui_builder.cpp index 849a8ff68..da87c92f4 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -364,6 +364,7 @@ PGuiElem GuiBuilder::drawRightBandInfo(GameInfo& info) { auto bottomLine = gui.getListBuilder(); bottomLine.addElem(gui.stack( gui.getListBuilder() + .addElem(gui.label("speed:"), 60) .addElemAuto(gui.labelFun([this] { return getCurrentGameSpeedName();}, [this] { return colors[clock->isPaused() ? ColorId::RED : ColorId::WHITE]; })).buildHorizontalList(), gui.button([&] { gameSpeedDialogOpen = !gameSpeedDialogOpen; })), 160); @@ -1088,7 +1089,6 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& auto line = gui.getListBuilder(); line.addElem(gui.viewObject(elem.viewId), 35); line.addElem(gui.label(elem.name), 10); - line.addBackElem(gui.label(toString(elem.number) + "x"), 35); line.addBackElem(gui.alignment(GuiFactory::Alignment::RIGHT, drawCost(*elem.price)), 80); lines.addElem(gui.rightMargin(rightElemMargin, gui.stack( gui.uiHighlightMouseOver(colors[ColorId::GREEN]), diff --git a/item.cpp b/item.cpp index faf559944..39f5a16d1 100644 --- a/item.cpp +++ b/item.cpp @@ -289,6 +289,13 @@ string Item::getTheName(bool getPlural, const Creature* owner) const { return the + getName(getPlural, owner); } +string Item::getPluralName(int count) const { + if (count > 1) + return toString(count) + " " + getName(true); + else + return getTheName(false); +} + string Item::getPluralTheName(int count) const { if (count > 1) return toString(count) + " " + getTheName(true); diff --git a/item.h b/item.h index b60e6b742..9d27bd785 100644 --- a/item.h +++ b/item.h @@ -55,6 +55,7 @@ class Item : public Renderable, public UniqueEntity { string getNameAndModifiers(bool plural = false, const Creature* owner = nullptr) const; string getArtifactName() const; string getShortName(const Creature* owner = nullptr, bool noSuffix = false) const; + string getPluralName(int count) const; string getPluralTheName(int count) const; string getPluralTheNameAndVerb(int count, const string& verbSingle, const string& verbPlural) const; diff --git a/workshops.cpp b/workshops.cpp index ac661714a..dd809579f 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -77,7 +77,7 @@ vector Workshops::Type::addWork(double amount) { if (product.state >= 1) { vector ret = ItemFactory::fromId(product.type, product.batchSize); product.state = 0; - changeNumber(0, product.number - product.batchSize); + changeNumber(0, product.number - 1); return ret; } } @@ -92,11 +92,11 @@ Workshops::Item Workshops::Item::fromType(ItemType type, CostInfo cost, double w PItem item = ItemFactory::fromId(type); return { type, - item->getName(), + item->getPluralName(batchSize), item->getViewObject().id(), cost, true, - batchSize, + 1, batchSize, workNeeded, 0 From 3c88c23cb1d422630fcbd05a27dc7d545a209eac Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sat, 6 Aug 2016 12:41:45 +0200 Subject: [PATCH 012/148] Include workshop queues in collective debt calculation --- collective.cpp | 1 + collective.h | 4 ++-- collective_config.cpp | 6 +++--- collective_config.h | 2 +- corpse_info.h | 2 +- cost_info.cpp | 16 ++++++++++++++++ cost_info.h | 28 ++++++++++++---------------- game_info.h | 1 - player_control.h | 2 +- view.h | 1 - workshops.cpp | 29 +++++++++++++++++++++-------- workshops.h | 8 +++++++- 12 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 cost_info.cpp diff --git a/collective.cpp b/collective.cpp index 7e7421623..f8300b33b 100644 --- a/collective.cpp +++ b/collective.cpp @@ -1404,6 +1404,7 @@ int Collective::numResourcePlusDebt(ResourceId id) const { if (id == ResourceId::GOLD) for (auto& elem : minionPayment) ret -= elem.second.debt; + ret -= workshops->getDebt(id); return ret; } diff --git a/collective.h b/collective.h index 9bb193cf2..ac5d419c7 100644 --- a/collective.h +++ b/collective.h @@ -40,7 +40,7 @@ class ConstructionMap; class Technology; class CollectiveConfig; class MinionAttraction; -struct CostInfo; +class CostInfo; struct TriggerInfo; class Territory; class CollectiveName; @@ -361,5 +361,5 @@ class Collective : public TaskCallback { EntitySet SERIAL(banished); EntitySet SERIAL(equipmentUpdates); optional SERIAL(villainType); - HeapAllocated SERIAL(workshops); + unique_ptr SERIAL(workshops); }; diff --git a/collective_config.cpp b/collective_config.cpp index 2a4755f05..7edb9ce9f 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -356,8 +356,8 @@ const vector& CollectiveConfig::getWorkshopInfo() const { } -HeapAllocated CollectiveConfig::getWorkshops() const { - return Workshops({ +unique_ptr CollectiveConfig::getWorkshops() const { + return unique_ptr(new Workshops({ {WorkshopType::WORKSHOP, { Workshops::Item::fromType(ItemId::FIRST_AID_KIT, {CollectiveResourceId::WOOD, 20}, 1), Workshops::Item::fromType(ItemId::LEATHER_ARMOR, {CollectiveResourceId::WOOD, 100}, 6), @@ -419,6 +419,6 @@ HeapAllocated CollectiveConfig::getWorkshops() const { Workshops::Item::fromType(ItemId::DEFENSE_AMULET, {CollectiveResourceId::GOLD, 200}, 10), Workshops::Item::fromType(ItemId::HEALING_AMULET, {CollectiveResourceId::GOLD, 300}, 10), }}, - }); + })); } diff --git a/collective_config.h b/collective_config.h index 32c340ab6..ba48fe4b6 100644 --- a/collective_config.h +++ b/collective_config.h @@ -161,7 +161,7 @@ class CollectiveConfig { const vector& getPopulationIncreases() const; const optional& getGuardianInfo() const; vector getBirthSpawns() const; - HeapAllocated getWorkshops() const; + unique_ptr getWorkshops() const; const vector& getWorkshopInfo() const; bool activeImmigrantion(const Game*) const; diff --git a/corpse_info.h b/corpse_info.h index 2d13f4b6c..ac8056db5 100644 --- a/corpse_info.h +++ b/corpse_info.h @@ -6,7 +6,7 @@ struct CorpseInfo { bool SERIAL(canBeRevived); bool SERIAL(hasHead); bool SERIAL(isSkeleton); - SERIALIZE_ALL(canBeRevived, hasHead, isSkeleton); + SERIALIZE_ALL(victim, canBeRevived, hasHead, isSkeleton); }; diff --git a/cost_info.cpp b/cost_info.cpp new file mode 100644 index 000000000..8437cab1f --- /dev/null +++ b/cost_info.cpp @@ -0,0 +1,16 @@ +#include "stdafx.h" +#include "cost_info.h" + + +CostInfo::CostInfo(CollectiveResourceId i, int v) : id(i), value(v) {} + +SERIALIZATION_CONSTRUCTOR_IMPL(CostInfo); +SERIALIZE_DEF(CostInfo, id, value); + +CostInfo CostInfo::noCost() { + return CostInfo{CollectiveResourceId(0), 0}; +} + +CostInfo CostInfo::operator-() const { + return CostInfo(id, -value); +} diff --git a/cost_info.h b/cost_info.h index 1fb454476..1ef925ca3 100644 --- a/cost_info.h +++ b/cost_info.h @@ -1,22 +1,18 @@ -#ifndef _COST_INFO_H -#define _COST_INFO_H +#pragma once #include "enums.h" #include "util.h" -struct CostInfo { - CostInfo() : id(CollectiveResourceId(0)), value(0) {} - CostInfo(CollectiveResourceId i, int v) : id(i), value(v) {} - CollectiveResourceId SERIAL(id); - int SERIAL(value); - template - void serialize(Archive& ar, const unsigned int version) { - ar & SVAR(id) & SVAR(value); - } - static CostInfo noCost() { - return CostInfo{CollectiveResourceId(0), 0}; - } -}; +class CostInfo { + public: + CostInfo(CollectiveResourceId, int amount); + static CostInfo noCost(); + + CostInfo operator-() const; + SERIALIZATION_DECL(CostInfo); + + CollectiveResourceId SERIAL(id) = CollectiveResourceId(0); + int SERIAL(value) = 0; +}; -#endif diff --git a/game_info.h b/game_info.h index fa0cb91d4..e6a9fb608 100644 --- a/game_info.h +++ b/game_info.h @@ -5,7 +5,6 @@ #include "minion_task.h" #include "item_action.h" #include "village_action.h" -#include "cost_info.h" #include "view_id.h" #include "player_message.h" diff --git a/player_control.h b/player_control.h index 595434e0c..2be776406 100644 --- a/player_control.h +++ b/player_control.h @@ -19,7 +19,6 @@ #include "creature_view.h" #include "entity_set.h" #include "collective_control.h" -#include "cost_info.h" #include "game_info.h" #include "map_memory.h" #include "position.h" @@ -42,6 +41,7 @@ struct TeamCreatureInfo; template class EventProxy; class SquareType; +class CostInfo; class PlayerControl : public CreatureView, public CollectiveControl { public: diff --git a/view.h b/view.h index 3a28122e0..d800bcad8 100644 --- a/view.h +++ b/view.h @@ -20,7 +20,6 @@ #include "debug.h" #include "user_input.h" #include "minion_task.h" -#include "cost_info.h" class CreatureView; class Level; diff --git a/workshops.cpp b/workshops.cpp index dd809579f..6294028c2 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -5,7 +5,7 @@ #include "item.h" Workshops::Workshops(const EnumMap>& o) - : types(o.mapValues([] (const vector& v) { return Type(v);})) { + : types(o.mapValues([this] (const vector& v) { return Type(this, v);})) { } Workshops::Type& Workshops::get(WorkshopType type) { @@ -17,12 +17,12 @@ const Workshops::Type& Workshops::get(WorkshopType type) const { } SERIALIZATION_CONSTRUCTOR_IMPL(Workshops); -SERIALIZE_DEF(Workshops, types); +SERIALIZE_DEF(Workshops, types, debt); -Workshops::Type::Type(const vector& o) : options(o) {} +Workshops::Type::Type(Workshops* w, const vector& o) : options(o), workshops(w) {} SERIALIZATION_CONSTRUCTOR_IMPL2(Workshops::Type, Type); -SERIALIZE_DEF(Workshops::Type, options, queued); +SERIALIZE_DEF(Workshops::Type, options, queued, workshops); const vector& Workshops::Type::getOptions() const { @@ -43,8 +43,13 @@ void Workshops::Type::stackQueue() { queued = tmp; } +void Workshops::Type::addCost(CostInfo cost) { + workshops->debt[cost.id] += cost.value; +} + void Workshops::Type::queue(int index) { const Item& newElem = options[index]; + addCost(newElem.cost); if (!queued.empty() && queued.back() == newElem) queued.back().number += newElem.number; else @@ -53,8 +58,10 @@ void Workshops::Type::queue(int index) { } void Workshops::Type::unqueue(int index) { - if (index >= 0 && index < queued.size()) + if (index >= 0 && index < queued.size()) { + addCost(-queued[index].cost); queued.erase(queued.begin() + index); + } stackQueue(); } @@ -62,9 +69,11 @@ void Workshops::Type::changeNumber(int index, int number) { if (number <= 0) unqueue(index); else { - auto& elems = queued; - if (index >= 0 && index < elems.size()) - elems[index].number = number; + if (index >= 0 && index < queued.size()) { + auto& elem = queued[index]; + addCost(CostInfo(elem.cost.id, number - elem.number)); + elem.number = number; + } } } @@ -103,3 +112,7 @@ Workshops::Item Workshops::Item::fromType(ItemType type, CostInfo cost, double w }; } +int Workshops::getDebt(CollectiveResourceId resource) const { + return debt[resource]; +} + diff --git a/workshops.h b/workshops.h index f9205e142..fc923e520 100644 --- a/workshops.h +++ b/workshops.h @@ -2,6 +2,7 @@ #include "cost_info.h" #include "item_type.h" +#include "resource_id.h" RICH_ENUM(WorkshopType, WORKSHOP, @@ -29,7 +30,7 @@ class Workshops { class Type { public: - Type(const vector& options); + Type(Workshops*, const vector& options); const vector& getOptions() const; const vector& getQueued() const; vector addWork(double); @@ -41,15 +42,20 @@ class Workshops { private: void stackQueue(); + void addCost(CostInfo); vector SERIAL(options); vector SERIAL(queued); + Workshops* SERIAL(workshops) = nullptr; }; SERIALIZATION_DECL(Workshops); Workshops(const EnumMap>&); + Workshops(const Workshops&) = delete; Type& get(WorkshopType); const Type& get(WorkshopType) const; + int getDebt(CollectiveResourceId) const; private: EnumMap SERIAL(types); + EnumMap SERIAL(debt); }; From f409edf08999d634c64402e7bb1bf62cc2c36aef Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sat, 6 Aug 2016 20:24:08 +0200 Subject: [PATCH 013/148] Add workshop production cost; Make minions appear not working when workshop is idle --- collective.cpp | 32 ++++++++++------- collective_config.cpp | 83 +++++++++++++++++++++++++------------------ collective_config.h | 7 ++-- item.cpp | 2 +- minion_task.h | 4 --- minion_task_map.cpp | 5 +-- player_control.cpp | 2 +- task.cpp | 20 ++++++----- task.h | 3 +- workshops.cpp | 37 +++++++++++++++---- workshops.h | 7 +++- 11 files changed, 129 insertions(+), 73 deletions(-) diff --git a/collective.cpp b/collective.cpp index f8300b33b..a21beaf3d 100644 --- a/collective.cpp +++ b/collective.cpp @@ -366,7 +366,7 @@ bool Collective::isTaskGood(const Creature* c, MinionTask task, bool ignoreTaskL if (c->getAttributes().getMinionTasks().getValue(task, ignoreTaskLock) == 0) return false; if (auto elem = minionPayment.getMaybe(c)) - if (elem->debt > 0 && config->getTaskInfo().at(task).cost > 0) + if (elem->debt > 0 && config->getTaskInfo(task).cost > 0) return false; switch (task) { case MinionTask::CROPS: @@ -431,12 +431,17 @@ bool Collective::isMinionTaskPossible(Creature* c, MinionTask task) { } PTask Collective::generateMinionTask(Creature* c, MinionTask task) { - MinionTaskInfo info = config->getTaskInfo().at(task); + MinionTaskInfo info = config->getTaskInfo(task); switch (info.type) { case MinionTaskInfo::APPLY_SQUARE: { vector squares = getAllSquares(info.squares, info.centerOnly); - if (!squares.empty()) - return Task::applySquare(this, squares); + if (!squares.empty()) { + auto searchType = Task::RANDOM_CLOSE; + if (auto workshopType = config->getWorkshopType(task)) + if (workshops->get(*workshopType).isIdle()) + searchType = Task::LAZY; + return Task::applySquare(this, squares, searchType); + } break; } case MinionTaskInfo::EXPLORE: @@ -473,7 +478,7 @@ PTask Collective::getStandardTask(Creature* c) { } if (auto current = currentTasks.getMaybe(c)) { MinionTask task = current->task; - MinionTaskInfo info = config->getTaskInfo().at(task); + MinionTaskInfo info = config->getTaskInfo(task); PTask ret = generateMinionTask(c, task); if (info.warning && !territory->isEmpty()) setWarning(*info.warning, !ret); @@ -555,9 +560,9 @@ PTask Collective::getHealingTask(Creature* c) { if (c->getBody().canHeal() && !c->isAffected(LastingEffect::POISON)) for (MinionTask t : {MinionTask::SLEEP, MinionTask::GRAVE, MinionTask::LAIR}) if (c->getAttributes().getMinionTasks().getValue(t) > 0) { - vector positions = getAllSquares(config->getTaskInfo().at(t).squares); + vector positions = getAllSquares(config->getTaskInfo(t).squares); if (!positions.empty()) - return Task::applySquare(nullptr, positions); + return Task::applySquare(nullptr, positions, Task::LAZY); } return nullptr; } @@ -1009,9 +1014,11 @@ void Collective::tick() { if (info.warning && info.getBedType()) setWarning(*info.warning, !chooseBedPos(getSquares(info.dormType), getSquares(*info.getBedType()))); } - for (auto elem : config->getTaskInfo()) - if (!getAllSquares(elem.second.squares).empty() && elem.second.warning) - setWarning(*elem.second.warning, false); + for (auto minionTask : ENUM_ALL(MinionTask)) { + auto elem = config->getTaskInfo(minionTask); + if (!getAllSquares(elem.squares).empty() && elem.warning) + setWarning(*elem.warning, false); + } } if (config->getEnemyPositions() && Random.roll(5)) { vector enemyPos = getEnemyPositions(); @@ -1794,6 +1801,7 @@ void Collective::updateConstructions() { if (!isDelayed(elem.first) && !elem.second.hasTask() && !elem.second.isBuilt()) constructions->getTorch(elem.first).setTask(taskMap->addTask( Task::buildTorch(this, elem.first, elem.second.getAttachmentDir()), elem.first)->getUniqueId()); + workshops->scheduleItems(this); } void Collective::delayDangerousTasks(const vector& enemyPos1, double delayTime) { @@ -1941,10 +1949,10 @@ void Collective::addProducesMessage(const Creature* c, const vector& item void Collective::onAppliedSquare(Position pos) { Creature* c = NOTNULL(pos.getCreature()); MinionTask currentTask = currentTasks.getOrFail(c).task; - if (config->getTaskInfo().at(currentTask).cost > 0) { + if (config->getTaskInfo(currentTask).cost > 0) { if (nextPayoutTime == -1 && minionPayment.getMaybe(c) && minionPayment.getOrFail(c).salary > 0) nextPayoutTime = getLocalTime() + config->getPayoutTime(); - minionPayment.getOrInit(c).workAmount += config->getTaskInfo().at(currentTask).cost; + minionPayment.getOrInit(c).workAmount += config->getTaskInfo(currentTask).cost; } if (getSquares(SquareId::LIBRARY).count(pos)) { addMana(0.2); diff --git a/collective_config.cpp b/collective_config.cpp index 7edb9ce9f..df6cb2067 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -314,44 +314,57 @@ MinionTaskInfo::MinionTaskInfo(Type t, const string& desc, optional CollectiveConfig::getTaskInfo() const { - map ret { - {MinionTask::TRAIN, {{SquareId::TRAINING_ROOM}, "training", CollectiveWarning::TRAINING, 1}}, - {MinionTask::WORKSHOP, {{SquareId::WORKSHOP}, "workshop", CollectiveWarning::WORKSHOP, 1}}, - {MinionTask::FORGE, {{SquareId::FORGE}, "forge", none, 1}}, - {MinionTask::LABORATORY, {{SquareId::LABORATORY}, "lab", none, 1}}, - {MinionTask::JEWELER, {{SquareId::JEWELER}, "jewellery", none, 1}}, - {MinionTask::SLEEP, {{SquareId::BED}, "sleeping", CollectiveWarning::BEDS}}, - {MinionTask::EAT, {MinionTaskInfo::EAT, "eating"}}, - {MinionTask::GRAVE, {{SquareId::GRAVE}, "sleeping", CollectiveWarning::GRAVES}}, - {MinionTask::LAIR, {{SquareId::BEAST_CAGE}, "sleeping"}}, - {MinionTask::LAIR, {{SquareId::BEAST_CAGE}, "sleeping"}}, - {MinionTask::THRONE, {{SquareId::THRONE}, "throne"}}, - {MinionTask::STUDY, {{SquareId::LIBRARY}, "studying", CollectiveWarning::LIBRARY, 1}}, - {MinionTask::PRISON, {{SquareId::PRISON}, "prison", CollectiveWarning::NO_PRISON}}, - {MinionTask::TORTURE, {{SquareId::TORTURE_TABLE}, "torture ordered", - CollectiveWarning::TORTURE_ROOM, 0, true}}, - {MinionTask::CROPS, {{SquareId::CROPS}, "crops"}}, - {MinionTask::RITUAL, {{SquareId::RITUAL_ROOM}, "rituals"}}, - {MinionTask::COPULATE, {MinionTaskInfo::COPULATE, "copulation"}}, - {MinionTask::CONSUME, {MinionTaskInfo::CONSUME, "consumption"}}, - {MinionTask::EXPLORE, {MinionTaskInfo::EXPLORE, "spying"}}, - {MinionTask::SPIDER, {MinionTaskInfo::SPIDER, "spider"}}, - {MinionTask::EXPLORE_NOCTURNAL, {MinionTaskInfo::EXPLORE, "spying"}}, - {MinionTask::EXPLORE_CAVES, {MinionTaskInfo::EXPLORE, "spying"}}, - {MinionTask::EXECUTE, {{SquareId::PRISON}, "execution ordered", CollectiveWarning::NO_PRISON}}}; - return ret; -}; - static vector workshops { - {SquareId::WORKSHOP, WorkshopType::WORKSHOP}, - {SquareId::FORGE, WorkshopType::FORGE}, - {SquareId::LABORATORY, WorkshopType::LABORATORY}, - {SquareId::JEWELER, WorkshopType::JEWELER}, + {SquareId::WORKSHOP, WorkshopType::WORKSHOP, MinionTask::WORKSHOP, "workshop"}, + {SquareId::FORGE, WorkshopType::FORGE, MinionTask::FORGE, "forge"}, + {SquareId::LABORATORY, WorkshopType::LABORATORY, MinionTask::LABORATORY, "laboratory"}, + {SquareId::JEWELER, WorkshopType::JEWELER, MinionTask::JEWELER, "jeweler"}, }; -const vector& CollectiveConfig::getWorkshopInfo() const { +optional CollectiveConfig::getWorkshopType(MinionTask task) { + static optional>> map; + if (!map) { + map.emplace(); + for (auto& elem : workshops) + (*map)[elem.minionTask] = elem.workshopType; + } + return (*map)[task]; +} + +MinionTaskInfo CollectiveConfig::getTaskInfo(MinionTask task) const { + switch (task) { + case MinionTask::TRAIN: return {{SquareId::TRAINING_ROOM}, "training", CollectiveWarning::TRAINING, 1}; + case MinionTask::SLEEP: return {{SquareId::BED}, "sleeping", CollectiveWarning::BEDS}; + case MinionTask::EAT: return {MinionTaskInfo::EAT, "eating"}; + case MinionTask::GRAVE: return {{SquareId::GRAVE}, "sleeping", CollectiveWarning::GRAVES}; + case MinionTask::LAIR: return {{SquareId::BEAST_CAGE}, "sleeping"}; + case MinionTask::THRONE: return {{SquareId::THRONE}, "throne"}; + case MinionTask::STUDY: return {{SquareId::LIBRARY}, "studying", CollectiveWarning::LIBRARY, 1}; + case MinionTask::PRISON: return {{SquareId::PRISON}, "prison", CollectiveWarning::NO_PRISON}; + case MinionTask::TORTURE: return {{SquareId::TORTURE_TABLE}, "torture ordered", + CollectiveWarning::TORTURE_ROOM, 0, true}; + case MinionTask::CROPS: return {{SquareId::CROPS}, "crops"}; + case MinionTask::RITUAL: return {{SquareId::RITUAL_ROOM}, "rituals"}; + case MinionTask::COPULATE: return {MinionTaskInfo::COPULATE, "copulation"}; + case MinionTask::CONSUME: return {MinionTaskInfo::CONSUME, "consumption"}; + case MinionTask::EXPLORE: return {MinionTaskInfo::EXPLORE, "spying"}; + case MinionTask::SPIDER: return {MinionTaskInfo::SPIDER, "spider"}; + case MinionTask::EXPLORE_NOCTURNAL: return {MinionTaskInfo::EXPLORE, "spying"}; + case MinionTask::EXPLORE_CAVES: return {MinionTaskInfo::EXPLORE, "spying"}; + case MinionTask::EXECUTE: return {{SquareId::PRISON}, "execution ordered", CollectiveWarning::NO_PRISON}; + case MinionTask::WORKSHOP: + case MinionTask::FORGE: + case MinionTask::LABORATORY: + case MinionTask::JEWELER: + for (auto& elem : workshops) + if (task == elem.minionTask) + return MinionTaskInfo({elem.squareType}, elem.taskName, none); + break; + } + return getTaskInfo(task); +} + +const vector& CollectiveConfig::getWorkshopInfo() { return workshops; } diff --git a/collective_config.h b/collective_config.h index ba48fe4b6..07843230e 100644 --- a/collective_config.h +++ b/collective_config.h @@ -127,6 +127,8 @@ struct MinionTaskInfo { struct WorkshopInfo { SquareId squareType; WorkshopType workshopType; + MinionTask minionTask; + string taskName; }; class CollectiveConfig { @@ -162,7 +164,8 @@ class CollectiveConfig { const optional& getGuardianInfo() const; vector getBirthSpawns() const; unique_ptr getWorkshops() const; - const vector& getWorkshopInfo() const; + static const vector& getWorkshopInfo(); + static optional getWorkshopType(MinionTask); bool activeImmigrantion(const Game*) const; const EnumMap& getDormInfo() const; @@ -171,7 +174,7 @@ class CollectiveConfig { vector getRoomsNeedingLight() const; int getTaskDuration(const Creature*, MinionTask) const; static const map& getResourceInfo(); - map getTaskInfo() const; + MinionTaskInfo getTaskInfo(MinionTask) const; static const vector& getEquipmentStorage(); static const vector& getResourceStorage(); diff --git a/item.cpp b/item.cpp index 39f5a16d1..bd5e84bb3 100644 --- a/item.cpp +++ b/item.cpp @@ -293,7 +293,7 @@ string Item::getPluralName(int count) const { if (count > 1) return toString(count) + " " + getName(true); else - return getTheName(false); + return getName(false); } string Item::getPluralTheName(int count) const { diff --git a/minion_task.h b/minion_task.h index fd2c998dc..fbfb02125 100644 --- a/minion_task.h +++ b/minion_task.h @@ -30,10 +30,6 @@ RICH_ENUM(MinionTask, THRONE ); -inline vector getWorkshopTasks() { - return {MinionTask::WORKSHOP, MinionTask::FORGE, MinionTask::LABORATORY, MinionTask::JEWELER}; -} - RICH_ENUM(MinionTrait, LEADER, FIGHTER, diff --git a/minion_task_map.cpp b/minion_task_map.cpp index cfea25e34..33372787c 100644 --- a/minion_task_map.cpp +++ b/minion_task_map.cpp @@ -15,14 +15,15 @@ #include "stdafx.h" #include "minion_task_map.h" +#include "collective_config.h" void MinionTaskMap::setValue(MinionTask t, double v) { tasks[t] = v; } void MinionTaskMap::setWorkshopTasks(double v) { - for (auto task : getWorkshopTasks()) - setValue(task, v); + for (auto task : CollectiveConfig::getWorkshopInfo()) + setValue(task.minionTask, v); } void MinionTaskMap::clear() { diff --git a/player_control.cpp b/player_control.cpp index e0ff63982..211b26898 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -1084,7 +1084,7 @@ static ItemInfo getWorkshopItem(const Workshops::Item& option) { c.viewId = option.viewId; c.price = getCostObj(option.cost); c.unavailable = !option.active; - c.productionState = option.state; + c.productionState = option.state.get_value_or(0); c.actions = LIST(ItemAction::REMOVE, ItemAction::CHANGE_NUMBER); c.number = option.number; ); diff --git a/task.cpp b/task.cpp index 7dc1cb0de..6ad774dea 100644 --- a/task.cpp +++ b/task.cpp @@ -331,13 +331,16 @@ PTask Task::equipItem(Item* item) { return PTask(new EquipItem(item)); } -static Position chooseRandomClose(Position start, const vector& squares) { +static Position chooseRandomClose(Position start, const vector& squares, Task::SearchType type) { CHECK(!squares.empty()); int minD = 10000; int margin = 3; vector close; - for (Position v : squares) + for (Position v : squares) { + if (type == Task::LAZY && v == start) + return v; minD = min(minD, v.dist8(start)); + } for (Position v : squares) if (v.dist8(start) < minD + margin) close.push_back(v); @@ -350,10 +353,10 @@ static Position chooseRandomClose(Position start, const vector& square class BringItem : public PickItem { public: BringItem(TaskCallback* c, Position position, vector items, vector _target, int retries) - : PickItem(c, position, items, retries), target(chooseRandomClose(position, _target)) {} + : PickItem(c, position, items, retries), target(chooseRandomClose(position, _target, LAZY)) {} BringItem(TaskCallback* c, Position position, vector items, vector _target) - : PickItem(c, position, items), target(chooseRandomClose(position, _target)) {} + : PickItem(c, position, items), target(chooseRandomClose(position, _target, LAZY)) {} virtual CreatureAction getBroughtAction(Creature* c, vector it) { return c->drop(it).append([=](Creature* c) { @@ -448,7 +451,7 @@ PTask Task::applyItem(TaskCallback* c, Position position, Item* item, Position t class ApplySquare : public Task { public: - ApplySquare(TaskCallback* c, vector pos) : positions(pos), callback(c) {} + ApplySquare(TaskCallback* c, vector pos, SearchType t) : positions(pos), callback(c), searchType(t) {} virtual MoveInfo getMove(Creature* c) override { if (!position) { @@ -458,7 +461,7 @@ class ApplySquare : public Task { return false; return !rejectedPosition.count(pos);}); if (!candidates.empty()) - position = chooseRandomClose(c->getPosition(), candidates); + position = chooseRandomClose(c->getPosition(), candidates, searchType); else { setDone(); return NoMove; @@ -503,11 +506,12 @@ class ApplySquare : public Task { int SERIAL(invalidCount) = 5; optional SERIAL(position); TaskCallback* SERIAL(callback); + SearchType SERIAL(searchType); }; -PTask Task::applySquare(TaskCallback* c, vector position) { +PTask Task::applySquare(TaskCallback* c, vector position, SearchType searchType) { CHECK(position.size() > 0); - return PTask(new ApplySquare(c, position)); + return PTask(new ApplySquare(c, position, searchType)); } namespace { diff --git a/task.h b/task.h index e71a73f2f..7a02b8f24 100644 --- a/task.h +++ b/task.h @@ -45,7 +45,8 @@ class Task : public UniqueEntity { static PTask bringItem(TaskCallback*, Position position, vector, vector target, int numRetries = 10); static PTask applyItem(TaskCallback*, Position, Item*, Position target); - static PTask applySquare(TaskCallback*, vector); + enum SearchType { LAZY, RANDOM_CLOSE }; + static PTask applySquare(TaskCallback*, vector, SearchType); static PTask pickAndEquipItem(TaskCallback*, Position, Item*); static PTask equipItem(Item*); static PTask pickItem(TaskCallback*, Position, vector); diff --git a/workshops.cpp b/workshops.cpp index 6294028c2..a3f91acf1 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -3,6 +3,7 @@ #include "item_factory.h" #include "view_object.h" #include "item.h" +#include "collective.h" Workshops::Workshops(const EnumMap>& o) : types(o.mapValues([this] (const vector& v) { return Type(this, v);})) { @@ -79,14 +80,38 @@ void Workshops::Type::changeNumber(int index, int number) { static const double prodMult = 0.1; +bool Workshops::Type::isIdle() const { + return queued.empty() || !queued[0].state; +} + +void Workshops::scheduleItems(Collective* collective) { + for (auto type : ENUM_ALL(WorkshopType)) + types[type].scheduleItems(collective); +} + +void Workshops::Type::scheduleItems(Collective* collective) { + if (queued.empty() || queued[0].state) + return; + for (int i : All(queued)) + if (collective->hasResource(queued[i].cost)) { + if (i > 0) + swap(queued[0], queued[i]); + collective->takeResource(queued[0].cost); + addCost(-queued[0].cost); + queued[0].state = 0; + return; + } +} + vector Workshops::Type::addWork(double amount) { - if (!queued.empty()) { + if (!queued.empty() && queued[0].state) { auto& product = queued[0]; - product.state += amount * prodMult / product.workNeeded; - if (product.state >= 1) { + *product.state += amount * prodMult / product.workNeeded; + if (*product.state >= 1) { vector ret = ItemFactory::fromId(product.type, product.batchSize); - product.state = 0; - changeNumber(0, product.number - 1); + product.state = none; + if (!--product.number) + queued.erase(queued.begin()); return ret; } } @@ -108,7 +133,7 @@ Workshops::Item Workshops::Item::fromType(ItemType type, CostInfo cost, double w 1, batchSize, workNeeded, - 0 + none }; } diff --git a/workshops.h b/workshops.h index fc923e520..d94e3e50b 100644 --- a/workshops.h +++ b/workshops.h @@ -11,6 +11,8 @@ RICH_ENUM(WorkshopType, JEWELER ); +class Collective; + class Workshops { public: struct Item { @@ -24,7 +26,7 @@ class Workshops { int SERIAL(number); int SERIAL(batchSize); double SERIAL(workNeeded); - double SERIAL(state); + optional SERIAL(state); SERIALIZE_ALL(type, name, viewId, cost, active, number, batchSize, workNeeded, state); }; @@ -37,6 +39,8 @@ class Workshops { void queue(int); void unqueue(int); void changeNumber(int index, int number); + void scheduleItems(Collective*); + bool isIdle() const; SERIALIZATION_DECL(Type); @@ -54,6 +58,7 @@ class Workshops { Type& get(WorkshopType); const Type& get(WorkshopType) const; int getDebt(CollectiveResourceId) const; + void scheduleItems(Collective*); private: EnumMap SERIAL(types); From f003bde1a9d41c92c34588ca85e431ff85477efd Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sun, 7 Aug 2016 15:18:37 +0200 Subject: [PATCH 014/148] Set unavailable workshops and items due to unbuilt workshop room or missing tech --- collective.cpp | 6 ++- collective_config.cpp | 123 +++++++++++++++++++++++------------------- collective_config.h | 4 +- game_info.h | 6 ++- gui_builder.cpp | 55 ++++++++++++------- minion_task_map.cpp | 4 +- player_control.cpp | 19 ++++--- player_control.h | 2 + task.cpp | 2 +- workshop_item.cpp | 36 +++++++++++++ workshop_item.h | 23 ++++++++ workshop_type.h | 9 ++++ workshops.cpp | 22 +------- workshops.h | 27 ++-------- 14 files changed, 204 insertions(+), 134 deletions(-) create mode 100644 workshop_item.cpp create mode 100644 workshop_item.h create mode 100644 workshop_type.h diff --git a/collective.cpp b/collective.cpp index a21beaf3d..cf559db65 100644 --- a/collective.cpp +++ b/collective.cpp @@ -1973,10 +1973,11 @@ void Collective::onAppliedSquare(Position pos) { } if (getSquares(SquareId::TRAINING_ROOM).count(pos)) c->getAttributes().exerciseAttr(Random.choose(), getEfficiency(pos)); - for (auto& elem : config->getWorkshopInfo()) + for (auto workshopType : ENUM_ALL(WorkshopType)) { + auto& elem = config->getWorkshopInfo(workshopType); if (getSquares(elem.squareType).count(pos)) { vector items = - workshops->get(elem.workshopType).addWork(getEfficiency(pos) * (1 + c->getMorale()) / 2); + workshops->get(workshopType).addWork(getEfficiency(pos) * (1 + c->getMorale()) / 2); if (!items.empty()) { if (items[0]->getClass() == ItemClass::WEAPON) getGame()->getStatistics().add(StatId::WEAPON_PRODUCED); @@ -1988,6 +1989,7 @@ void Collective::onAppliedSquare(Position pos) { pos.dropItems(std::move(items)); } } + } } double Collective::getDangerLevel() const { diff --git a/collective_config.cpp b/collective_config.cpp index df6cb2067..dcb80ee62 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -314,19 +314,19 @@ MinionTaskInfo::MinionTaskInfo(Type t, const string& desc, optional workshops { - {SquareId::WORKSHOP, WorkshopType::WORKSHOP, MinionTask::WORKSHOP, "workshop"}, - {SquareId::FORGE, WorkshopType::FORGE, MinionTask::FORGE, "forge"}, - {SquareId::LABORATORY, WorkshopType::LABORATORY, MinionTask::LABORATORY, "laboratory"}, - {SquareId::JEWELER, WorkshopType::JEWELER, MinionTask::JEWELER, "jeweler"}, +static EnumMap workshops { + {WorkshopType::WORKSHOP, {SquareId::WORKSHOP, MinionTask::WORKSHOP, "workshop"}}, + {WorkshopType::FORGE, {SquareId::FORGE, MinionTask::FORGE, "forge"}}, + {WorkshopType::LABORATORY, {SquareId::LABORATORY, MinionTask::LABORATORY, "laboratory"}}, + {WorkshopType::JEWELER, {SquareId::JEWELER, MinionTask::JEWELER, "jeweler"}}, }; optional CollectiveConfig::getWorkshopType(MinionTask task) { static optional>> map; if (!map) { map.emplace(); - for (auto& elem : workshops) - (*map)[elem.minionTask] = elem.workshopType; + for (auto type : ENUM_ALL(WorkshopType)) + (*map)[workshops[type].minionTask] = type; } return (*map)[task]; } @@ -355,82 +355,93 @@ MinionTaskInfo CollectiveConfig::getTaskInfo(MinionTask task) const { case MinionTask::WORKSHOP: case MinionTask::FORGE: case MinionTask::LABORATORY: - case MinionTask::JEWELER: - for (auto& elem : workshops) - if (task == elem.minionTask) - return MinionTaskInfo({elem.squareType}, elem.taskName, none); - break; + case MinionTask::JEWELER: { + auto& info = workshops[*getWorkshopType(task)]; + return MinionTaskInfo({info.squareType}, info.taskName); + } } return getTaskInfo(task); } -const vector& CollectiveConfig::getWorkshopInfo() { - return workshops; +const WorkshopInfo& CollectiveConfig::getWorkshopInfo(WorkshopType type) { + return workshops[type]; } unique_ptr CollectiveConfig::getWorkshops() const { return unique_ptr(new Workshops({ {WorkshopType::WORKSHOP, { - Workshops::Item::fromType(ItemId::FIRST_AID_KIT, {CollectiveResourceId::WOOD, 20}, 1), - Workshops::Item::fromType(ItemId::LEATHER_ARMOR, {CollectiveResourceId::WOOD, 100}, 6), - Workshops::Item::fromType(ItemId::LEATHER_HELM, {CollectiveResourceId::WOOD, 30}, 1), - Workshops::Item::fromType(ItemId::LEATHER_BOOTS, {CollectiveResourceId::WOOD, 50}, 2), - Workshops::Item::fromType(ItemId::LEATHER_GLOVES, {CollectiveResourceId::WOOD, 10}, 1), - Workshops::Item::fromType(ItemId::CLUB, {CollectiveResourceId::WOOD, 50}, 3), - Workshops::Item::fromType(ItemId::HEAVY_CLUB, {CollectiveResourceId::WOOD, 100}, 5), - Workshops::Item::fromType(ItemId::BOW, {CollectiveResourceId::WOOD, 100}, 13), - Workshops::Item::fromType(ItemId::ARROW, {CollectiveResourceId::WOOD, 50}, 5, 20), - Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, {CollectiveResourceId::STONE, 250}, 20), + Workshops::Item::fromType(ItemId::FIRST_AID_KIT, 1, {CollectiveResourceId::WOOD, 20}), + Workshops::Item::fromType(ItemId::LEATHER_ARMOR, 6, {CollectiveResourceId::WOOD, 100}), + Workshops::Item::fromType(ItemId::LEATHER_HELM, 1, {CollectiveResourceId::WOOD, 30}), + Workshops::Item::fromType(ItemId::LEATHER_BOOTS, 2, {CollectiveResourceId::WOOD, 50}), + Workshops::Item::fromType(ItemId::LEATHER_GLOVES, 1, {CollectiveResourceId::WOOD, 10}), + Workshops::Item::fromType(ItemId::CLUB, 3, {CollectiveResourceId::WOOD, 50}), + Workshops::Item::fromType(ItemId::HEAVY_CLUB, 5, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TWO_H_WEAP), + Workshops::Item::fromType(ItemId::BOW, 13, {CollectiveResourceId::WOOD, 100}).setTechId(TechId::ARCHERY), + Workshops::Item::fromType(ItemId::ARROW, 5, {CollectiveResourceId::WOOD, 50}) + .setBatchSize(20).setTechId(TechId::ARCHERY), + Workshops::Item::fromType(ItemId::BOULDER_TRAP_ITEM, 20, {CollectiveResourceId::STONE, 250}) + .setTechId(TechId::TRAPS), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, {CollectiveResourceId::WOOD, 100}, 10), + TrapInfo({TrapType::POISON_GAS, EffectId::EMIT_POISON_GAS})}, 10, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TRAPS), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::ALARM, EffectId::ALARM})}, {CollectiveResourceId::WOOD, 100}, 8), + TrapInfo({TrapType::ALARM, EffectId::ALARM})}, 8, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TRAPS), Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::WEB, - EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, {CollectiveResourceId::WOOD, 100}, 8), + EffectType(EffectId::LASTING, LastingEffect::ENTANGLED)})}, 8, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TRAPS), Workshops::Item::fromType({ItemId::TRAP_ITEM, - TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, {CollectiveResourceId::WOOD, 100}, 8), + TrapInfo({TrapType::SURPRISE, EffectId::TELE_ENEMIES})}, 8, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TRAPS), Workshops::Item::fromType({ItemId::TRAP_ITEM, TrapInfo({TrapType::TERROR, - EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, {CollectiveResourceId::WOOD, 100}, 8), + EffectType(EffectId::LASTING, LastingEffect::PANIC)})}, 8, {CollectiveResourceId::WOOD, 100}) + .setTechId(TechId::TRAPS), }}, {WorkshopType::FORGE, { - Workshops::Item::fromType(ItemId::SWORD, {CollectiveResourceId::IRON, 100}, 10), - Workshops::Item::fromType(ItemId::SPECIAL_SWORD, {CollectiveResourceId::IRON, 1000}, 80), - Workshops::Item::fromType(ItemId::CHAIN_ARMOR, {CollectiveResourceId::IRON, 200}, 30), - Workshops::Item::fromType(ItemId::IRON_HELM, {CollectiveResourceId::IRON, 80}, 8), - Workshops::Item::fromType(ItemId::IRON_BOOTS, {CollectiveResourceId::IRON, 120}, 12), - Workshops::Item::fromType(ItemId::WAR_HAMMER, {CollectiveResourceId::IRON, 190}, 16), - Workshops::Item::fromType(ItemId::BATTLE_AXE, {CollectiveResourceId::IRON, 250}, 22), - Workshops::Item::fromType(ItemId::SPECIAL_WAR_HAMMER, {CollectiveResourceId::IRON, 1900}, 120), - Workshops::Item::fromType(ItemId::SPECIAL_BATTLE_AXE, {CollectiveResourceId::IRON, 2000}, 180), + Workshops::Item::fromType(ItemId::SWORD, 10, {CollectiveResourceId::IRON, 100}), + Workshops::Item::fromType(ItemId::SPECIAL_SWORD, 80, {CollectiveResourceId::IRON, 1000}), + Workshops::Item::fromType(ItemId::CHAIN_ARMOR, 30, {CollectiveResourceId::IRON, 200}), + Workshops::Item::fromType(ItemId::IRON_HELM, 8, {CollectiveResourceId::IRON, 80}), + Workshops::Item::fromType(ItemId::IRON_BOOTS, 12, {CollectiveResourceId::IRON, 120}), + Workshops::Item::fromType(ItemId::WAR_HAMMER, 16, {CollectiveResourceId::IRON, 190}), + Workshops::Item::fromType(ItemId::BATTLE_AXE, 22, {CollectiveResourceId::IRON, 250}), + Workshops::Item::fromType(ItemId::SPECIAL_WAR_HAMMER, 120, {CollectiveResourceId::IRON, 1900}), + Workshops::Item::fromType(ItemId::SPECIAL_BATTLE_AXE, 180, {CollectiveResourceId::IRON, 2000}), }}, {WorkshopType::LABORATORY, { - Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLOWED}}, - {CollectiveResourceId::MANA, 10}, 2), - Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLEEP}}, - {CollectiveResourceId::MANA, 10}, 2), - Workshops::Item::fromType({ItemId::POTION, EffectId::HEAL}, {CollectiveResourceId::MANA, 30}, 4), + Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLOWED}}, 2, + {CollectiveResourceId::MANA, 10}), + Workshops::Item::fromType({ItemId::POTION, EffectType{EffectId::LASTING, LastingEffect::SLEEP}}, 2, + {CollectiveResourceId::MANA, 10}), + Workshops::Item::fromType({ItemId::POTION, EffectId::HEAL}, 4, {CollectiveResourceId::MANA, 30}), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::POISON_RESISTANT}}, {CollectiveResourceId::MANA, 30}, 3), + EffectType{EffectId::LASTING, LastingEffect::POISON_RESISTANT}}, 3, {CollectiveResourceId::MANA, 30}), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::POISON}}, {CollectiveResourceId::MANA, 30}, 2), + EffectType{EffectId::LASTING, LastingEffect::POISON}}, 2, {CollectiveResourceId::MANA, 30}), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::SPEED}}, {CollectiveResourceId::MANA, 30}, 4), + EffectType{EffectId::LASTING, LastingEffect::SPEED}}, 4, {CollectiveResourceId::MANA, 30}) + .setTechId(TechId::ALCHEMY_ADV), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::BLIND}}, {CollectiveResourceId::MANA, 50}, 4), + EffectType{EffectId::LASTING, LastingEffect::BLIND}}, 4, {CollectiveResourceId::MANA, 50}) + .setTechId(TechId::ALCHEMY_ADV), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::FLYING}}, {CollectiveResourceId::MANA, 80}, 6), + EffectType{EffectId::LASTING, LastingEffect::FLYING}}, 6, {CollectiveResourceId::MANA, 80}) + .setTechId(TechId::ALCHEMY_ADV), Workshops::Item::fromType({ItemId::POTION, - EffectType{EffectId::LASTING, LastingEffect::INVISIBLE}}, {CollectiveResourceId::MANA, 200}, 6), + EffectType{EffectId::LASTING, LastingEffect::INVISIBLE}}, 6, {CollectiveResourceId::MANA, 200}) + .setTechId(TechId::ALCHEMY_ADV), }}, {WorkshopType::JEWELER, { - Workshops::Item::fromType({ItemId::RING, LastingEffect::POISON_RESISTANT}, - {CollectiveResourceId::GOLD, 100}, 10), - Workshops::Item::fromType({ItemId::RING, LastingEffect::FIRE_RESISTANT}, - {CollectiveResourceId::GOLD, 150}, 10), - Workshops::Item::fromType(ItemId::WARNING_AMULET, {CollectiveResourceId::GOLD, 150}, 10), - Workshops::Item::fromType(ItemId::DEFENSE_AMULET, {CollectiveResourceId::GOLD, 200}, 10), - Workshops::Item::fromType(ItemId::HEALING_AMULET, {CollectiveResourceId::GOLD, 300}, 10), + Workshops::Item::fromType({ItemId::RING, LastingEffect::POISON_RESISTANT}, 10, + {CollectiveResourceId::GOLD, 100}), + Workshops::Item::fromType({ItemId::RING, LastingEffect::FIRE_RESISTANT}, 10, + {CollectiveResourceId::GOLD, 150}), + Workshops::Item::fromType(ItemId::WARNING_AMULET, 10, {CollectiveResourceId::GOLD, 150}), + Workshops::Item::fromType(ItemId::DEFENSE_AMULET, 10, {CollectiveResourceId::GOLD, 200}), + Workshops::Item::fromType(ItemId::HEALING_AMULET, 10, {CollectiveResourceId::GOLD, 300}), }}, })); } diff --git a/collective_config.h b/collective_config.h index 07843230e..732ff8039 100644 --- a/collective_config.h +++ b/collective_config.h @@ -19,6 +19,7 @@ #include "square_type.h" #include "util.h" #include "minion_task.h" +#include "workshop_type.h" enum class ItemClass; @@ -126,7 +127,6 @@ struct MinionTaskInfo { struct WorkshopInfo { SquareId squareType; - WorkshopType workshopType; MinionTask minionTask; string taskName; }; @@ -164,7 +164,7 @@ class CollectiveConfig { const optional& getGuardianInfo() const; vector getBirthSpawns() const; unique_ptr getWorkshops() const; - static const vector& getWorkshopInfo(); + static const WorkshopInfo& getWorkshopInfo(WorkshopType); static optional getWorkshopType(MinionTask); bool activeImmigrantion(const Game*) const; diff --git a/game_info.h b/game_info.h index e6a9fb608..de83d1dde 100644 --- a/game_info.h +++ b/game_info.h @@ -37,12 +37,13 @@ struct ItemInfo { bool HASH(locked); bool HASH(pending); bool HASH(unavailable); + string HASH(unavailableReason); optional HASH(slot); optional HASH(owner); enum Type {EQUIPMENT, CONSUMABLE, OTHER} HASH(type); optional> HASH(price); double HASH(productionState); - HASH_ALL(name, fullName, description, number, viewId, ids, actions, equiped, locked, pending, unavailable, slot, owner, type, price, productionState); + HASH_ALL(name, fullName, description, number, viewId, ids, actions, equiped, locked, pending, unavailable, slot, owner, type, price, productionState, unavailableReason); }; @@ -161,7 +162,8 @@ class CollectiveInfo { string HASH(name); ViewId HASH(viewId); bool HASH(active); - HASH_ALL(name, viewId, active); + bool HASH(unavailable); + HASH_ALL(name, viewId, active, unavailable); }; vector HASH(workshopButtons); struct ChosenWorkshopInfo { diff --git a/gui_builder.cpp b/gui_builder.cpp index da87c92f4..82f7ff28f 100644 --- a/gui_builder.cpp +++ b/gui_builder.cpp @@ -198,19 +198,22 @@ PGuiElem GuiBuilder::drawTechnology(CollectiveInfo& info) { } lines.addSpace(legendLineHeight / 2); for (int i : All(info.workshopButtons)) { + auto& button = info.workshopButtons[i]; auto line = gui.getListBuilder(); - line.addElem(gui.viewObject(info.workshopButtons[i].viewId), 35); - line.addElemAuto(gui.label(info.workshopButtons[i].name, colors[ColorId::WHITE])); + line.addElem(gui.viewObject(button.viewId), 35); + line.addElemAuto(gui.label(button.name, colors[button.unavailable ? ColorId::GRAY : ColorId::WHITE])); PGuiElem elem = line.buildHorizontalList(); - if (info.workshopButtons[i].active) + if (button.active) elem = gui.stack( gui.uiHighlight(colors[ColorId::GREEN]), std::move(elem)); - lines.addElem(gui.stack( + if (!button.unavailable) + elem = gui.stack( gui.button([this, i] { workshopsScroll2 = workshopsScroll = 0; getButtonCallback(UserInput(UserInputId::WORKSHOP, i))(); }), - std::move(elem))); + std::move(elem)); + lines.addElem(std::move(elem)); } technologyCache = lines.buildVerticalList(); } @@ -1076,10 +1079,13 @@ void GuiBuilder::drawRansomOverlay(vector& ret, const CollectiveInf } void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& info) { - int margin = 20; - int rightElemMargin = 10; - Vec2 size(860, 600); if (info.chosenWorkshop) { + int newHash = info.getHash(); + if (newHash != workshopsOverlayHash) { + workshopsOverlayHash = newHash; + int margin = 20; + int rightElemMargin = 10; + Vec2 size(860, 600); auto& options = info.chosenWorkshop->options; auto& queued = info.chosenWorkshop->queued; auto lines = gui.getListBuilder(legendLineHeight); @@ -1088,12 +1094,20 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& auto& elem = options[i]; auto line = gui.getListBuilder(); line.addElem(gui.viewObject(elem.viewId), 35); - line.addElem(gui.label(elem.name), 10); + line.addElem(gui.label(elem.name, colors[elem.unavailable ? ColorId::GRAY : ColorId::WHITE]), 10); line.addBackElem(gui.alignment(GuiFactory::Alignment::RIGHT, drawCost(*elem.price)), 80); - lines.addElem(gui.rightMargin(rightElemMargin, gui.stack( + PGuiElem guiElem = line.buildHorizontalList(); + if (elem.unavailable) { + CHECK(!elem.unavailableReason.empty()); + guiElem = gui.stack(getTooltip({elem.unavailableReason}), std::move(guiElem)); + } + else + guiElem = gui.stack( + getTooltip({elem.description}), gui.uiHighlightMouseOver(colors[ColorId::GREEN]), - gui.button(getButtonCallback({UserInputId::WORKSHOP_ADD, i})), - line.buildHorizontalList()))); + std::move(guiElem), + gui.button(getButtonCallback({UserInputId::WORKSHOP_ADD, i}))); + lines.addElem(gui.rightMargin(rightElemMargin, std::move(guiElem))); } auto lines2 = gui.getListBuilder(legendLineHeight); lines2.addElem(gui.label("In production:", colors[ColorId::YELLOW])); @@ -1138,15 +1152,20 @@ void GuiBuilder::drawWorkshopsOverlay(vector& ret, CollectiveInfo& gui.rightMargin(rightElemMargin, line.buildHorizontalList()))); } size.y = min(600, max(lines.getSize(), lines2.getSize()) + 2 * margin); - ret.push_back({gui.miniWindow(gui.stack( + workshopsOverlayCache = gui.stack(gui.preferredSize(size.x, size.y), + gui.miniWindow(gui.stack( gui.keyHandler(getButtonCallback({UserInputId::WORKSHOP, info.chosenWorkshop->index}), {gui.getKey(SDL::SDLK_ESCAPE)}, true), gui.getListBuilder(430) - .addElem(gui.margins(gui.scrollable(lines.buildVerticalList(), &workshopsScroll, &scrollbarsHeld), - margin)) - .addElem(gui.margins(gui.scrollable(lines2.buildVerticalList(), &workshopsScroll2, &scrollbarsHeld), - margin)).buildHorizontalList())), - size, OverlayInfo::MINIONS}); + .addElem(gui.margins(gui.scrollable( + lines.buildVerticalList(), &workshopsScroll, &scrollbarsHeld), margin)) + .addElem(gui.margins( + gui.scrollable(lines2.buildVerticalList(), &workshopsScroll2, &scrollbarsHeld), + margin)).buildHorizontalList()))); + } + ret.push_back({gui.external(workshopsOverlayCache.get()), + Vec2(*workshopsOverlayCache->getPreferredWidth(), *workshopsOverlayCache->getPreferredHeight()), + OverlayInfo::MINIONS}); } } diff --git a/minion_task_map.cpp b/minion_task_map.cpp index 33372787c..2c369356b 100644 --- a/minion_task_map.cpp +++ b/minion_task_map.cpp @@ -22,8 +22,8 @@ void MinionTaskMap::setValue(MinionTask t, double v) { } void MinionTaskMap::setWorkshopTasks(double v) { - for (auto task : CollectiveConfig::getWorkshopInfo()) - setValue(task.minionTask, v); + for (auto type : ENUM_ALL(WorkshopType)) + setValue(CollectiveConfig::getWorkshopInfo(type).minionTask, v); } void MinionTaskMap::clear() { diff --git a/player_control.cpp b/player_control.cpp index 211b26898..2d754bf2f 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -1078,12 +1078,15 @@ void PlayerControl::fillMinions(CollectiveInfo& info) const { info.minionLimit = getCollective()->getMaxPopulation(); } -static ItemInfo getWorkshopItem(const Workshops::Item& option) { +ItemInfo PlayerControl::getWorkshopItem(const WorkshopItem& option) const { return CONSTRUCT(ItemInfo, c.name = option.name; c.viewId = option.viewId; c.price = getCostObj(option.cost); - c.unavailable = !option.active; + if (option.techId && !getCollective()->hasTech(*option.techId)) { + c.unavailable = true; + c.unavailableReason = "Requires technology: " + Technology::get(*option.techId)->getName(); + } c.productionState = option.state.get_value_or(0); c.actions = LIST(ItemAction::REMOVE, ItemAction::CHANGE_NUMBER); c.number = option.number; @@ -1103,21 +1106,25 @@ void PlayerControl::fillWorkshopInfo(CollectiveInfo& info) const { ++i; } if (chosenWorkshop) { + auto transFun = [this](const WorkshopItem& item) { return getWorkshopItem(item); }; info.chosenWorkshop = CollectiveInfo::ChosenWorkshopInfo { - transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getOptions(), getWorkshopItem), - transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getQueued(), getWorkshopItem), - index + transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getOptions(), transFun), + transform2(getCollective()->getWorkshops().get(*chosenWorkshop).getQueued(), transFun), }; } } vector PlayerControl::getWorkshopInfo() const { - return { + vector ret { {{"Workshop", ViewId::WORKSHOP, false}, WorkshopType::WORKSHOP}, {{"Forge", ViewId::FORGE, false}, WorkshopType::FORGE}, {{"Laboratory", ViewId::LABORATORY, false}, WorkshopType::LABORATORY}, {{"Jeweler", ViewId::JEWELER, false}, WorkshopType::JEWELER}, }; + for (auto& elem : ret) + elem.button.unavailable = getCollective()->getSquares( + CollectiveConfig::getWorkshopInfo(elem.workshopType).squareType).empty(); + return ret; } void PlayerControl::refreshGameInfo(GameInfo& gameInfo) const { diff --git a/player_control.h b/player_control.h index 2be776406..436ae3dbc 100644 --- a/player_control.h +++ b/player_control.h @@ -42,6 +42,7 @@ template class EventProxy; class SquareType; class CostInfo; +struct WorkshopItem; class PlayerControl : public CreatureView, public CollectiveControl { public: @@ -168,6 +169,7 @@ class PlayerControl : public CreatureView, public CollectiveControl { bool canBuildDoor(Position) const; bool canPlacePost(Position) const; void getEquipmentItem(View* view, ItemPredicate predicate); + ItemInfo getWorkshopItem(const WorkshopItem&) const; Item* chooseEquipmentItem(Creature* creature, vector currentItems, ItemPredicate predicate, double* scrollPos = nullptr); diff --git a/task.cpp b/task.cpp index 6ad774dea..a74cf0a57 100644 --- a/task.cpp +++ b/task.cpp @@ -497,7 +497,7 @@ class ApplySquare : public Task { return "Apply square " + (position ? toString(*position) : ""); } - SERIALIZE_ALL2(Task, positions, rejectedPosition, invalidCount, position, callback); + SERIALIZE_ALL2(Task, positions, rejectedPosition, invalidCount, position, callback, searchType); SERIALIZATION_CONSTRUCTOR(ApplySquare); private: diff --git a/workshop_item.cpp b/workshop_item.cpp new file mode 100644 index 000000000..a34518d64 --- /dev/null +++ b/workshop_item.cpp @@ -0,0 +1,36 @@ +#include "stdafx.h" +#include "workshop_item.h" +#include "item_factory.h" +#include "item.h" +#include "view_object.h" + +WorkshopItem WorkshopItem::fromType(ItemType type, double workNeeded, CostInfo cost) { + PItem item = ItemFactory::fromId(type); + return { + type, + item->getName(), + item->getViewObject().id(), + cost, + 1, + 1, + workNeeded, + none + }; +} + +WorkshopItem& WorkshopItem::setBatchSize(int size) { + batchSize = size; + name = ItemFactory::fromId(type)->getPluralName(size); + return *this; +} + +WorkshopItem& WorkshopItem::setTechId(TechId id) { + techId = id; + return *this; +} + +bool WorkshopItem::operator == (const WorkshopItem& item) const { + return type == item.type; +} + + diff --git a/workshop_item.h b/workshop_item.h new file mode 100644 index 000000000..e41645a0f --- /dev/null +++ b/workshop_item.h @@ -0,0 +1,23 @@ +#pragma once + +#include "item_type.h" +#include "cost_info.h" + +struct WorkshopItem { + static WorkshopItem fromType(ItemType, double workNeeded, CostInfo); + WorkshopItem& setBatchSize(int); + WorkshopItem& setTechId(TechId); + bool operator == (const WorkshopItem&) const; + ItemType SERIAL(type); + string SERIAL(name); + ViewId SERIAL(viewId); + CostInfo SERIAL(cost); + int SERIAL(number); + int SERIAL(batchSize); + double SERIAL(workNeeded); + optional SERIAL(state); + optional SERIAL(techId); + SERIALIZE_ALL(type, name, viewId, cost, number, batchSize, workNeeded, state, techId); +}; + + diff --git a/workshop_type.h b/workshop_type.h new file mode 100644 index 000000000..2dbeb90c4 --- /dev/null +++ b/workshop_type.h @@ -0,0 +1,9 @@ +#pragma once + +RICH_ENUM(WorkshopType, + WORKSHOP, + FORGE, + LABORATORY, + JEWELER +); + diff --git a/workshops.cpp b/workshops.cpp index a3f91acf1..be9fa27c9 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -1,9 +1,8 @@ #include "stdafx.h" #include "workshops.h" +#include "collective.h" #include "item_factory.h" -#include "view_object.h" #include "item.h" -#include "collective.h" Workshops::Workshops(const EnumMap>& o) : types(o.mapValues([this] (const vector& v) { return Type(this, v);})) { @@ -30,10 +29,6 @@ const vector& Workshops::Type::getOptions() const { return options; } -bool Workshops::Item::operator == (const Item& item) const { - return type == item.type; -} - void Workshops::Type::stackQueue() { vector tmp; for (auto& elem : queued) @@ -122,21 +117,6 @@ const vector& Workshops::Type::getQueued() const { return queued; } -Workshops::Item Workshops::Item::fromType(ItemType type, CostInfo cost, double workNeeded, int batchSize) { - PItem item = ItemFactory::fromId(type); - return { - type, - item->getPluralName(batchSize), - item->getViewObject().id(), - cost, - true, - 1, - batchSize, - workNeeded, - none - }; -} - int Workshops::getDebt(CollectiveResourceId resource) const { return debt[resource]; } diff --git a/workshops.h b/workshops.h index d94e3e50b..b37dae10c 100644 --- a/workshops.h +++ b/workshops.h @@ -1,35 +1,14 @@ #pragma once -#include "cost_info.h" -#include "item_type.h" #include "resource_id.h" - -RICH_ENUM(WorkshopType, - WORKSHOP, - FORGE, - LABORATORY, - JEWELER -); +#include "workshop_type.h" +#include "workshop_item.h" class Collective; class Workshops { public: - struct Item { - static Item fromType(ItemType, CostInfo, double workNeeded, int batchSize = 1); - bool operator == (const Item&) const; - ItemType SERIAL(type); - string SERIAL(name); - ViewId SERIAL(viewId); - CostInfo SERIAL(cost); - bool SERIAL(active); - int SERIAL(number); - int SERIAL(batchSize); - double SERIAL(workNeeded); - optional SERIAL(state); - SERIALIZE_ALL(type, name, viewId, cost, active, number, batchSize, workNeeded, state); - }; - + typedef WorkshopItem Item; class Type { public: Type(Workshops*, const vector& options); From 6068c50728b21db92b7a119a413a8173c5518e6b Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sun, 7 Aug 2016 15:44:45 +0200 Subject: [PATCH 015/148] Change debt and cost calculation bugs in Workshops --- cost_info.cpp | 6 +++++- cost_info.h | 3 ++- player_control.cpp | 8 ++++++-- workshops.cpp | 22 ++++++++++------------ workshops.h | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cost_info.cpp b/cost_info.cpp index 8437cab1f..0089e14b8 100644 --- a/cost_info.cpp +++ b/cost_info.cpp @@ -11,6 +11,10 @@ CostInfo CostInfo::noCost() { return CostInfo{CollectiveResourceId(0), 0}; } -CostInfo CostInfo::operator-() const { +CostInfo CostInfo::operator - () const { return CostInfo(id, -value); } + +CostInfo CostInfo::operator * (int a) const { + return CostInfo(id, a * value); +} diff --git a/cost_info.h b/cost_info.h index 1ef925ca3..2202c59cf 100644 --- a/cost_info.h +++ b/cost_info.h @@ -8,7 +8,8 @@ class CostInfo { CostInfo(CollectiveResourceId, int amount); static CostInfo noCost(); - CostInfo operator-() const; + CostInfo operator - () const; + CostInfo operator * (int) const; SERIALIZATION_DECL(CostInfo); diff --git a/player_control.cpp b/player_control.cpp index 2d754bf2f..5ae1de9c2 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -1647,8 +1647,12 @@ void PlayerControl::processInput(View* view, UserInput input) { getCollective()->getWorkshops().get(*chosenWorkshop).unqueue(info.itemIndex); break; case ItemAction::CHANGE_NUMBER: - if (auto number = getView()->getNumber("Change the number of items:", 0, 300, 1)) - getCollective()->getWorkshops().get(*chosenWorkshop).changeNumber(info.itemIndex, *number); + if (auto number = getView()->getNumber("Change the number of items:", 0, 300, 1)) { + if (*number > 0) + getCollective()->getWorkshops().get(*chosenWorkshop).changeNumber(info.itemIndex, *number); + else + getCollective()->getWorkshops().get(*chosenWorkshop).unqueue(info.itemIndex); + } default: break; } diff --git a/workshops.cpp b/workshops.cpp index be9fa27c9..1c9990b66 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -39,13 +39,13 @@ void Workshops::Type::stackQueue() { queued = tmp; } -void Workshops::Type::addCost(CostInfo cost) { +void Workshops::Type::addDebt(CostInfo cost) { workshops->debt[cost.id] += cost.value; } void Workshops::Type::queue(int index) { const Item& newElem = options[index]; - addCost(newElem.cost); + addDebt(newElem.cost); if (!queued.empty() && queued.back() == newElem) queued.back().number += newElem.number; else @@ -55,21 +55,19 @@ void Workshops::Type::queue(int index) { void Workshops::Type::unqueue(int index) { if (index >= 0 && index < queued.size()) { - addCost(-queued[index].cost); + if (!queued[index].state) + addDebt(-queued[index].cost); queued.erase(queued.begin() + index); } stackQueue(); } void Workshops::Type::changeNumber(int index, int number) { - if (number <= 0) - unqueue(index); - else { - if (index >= 0 && index < queued.size()) { - auto& elem = queued[index]; - addCost(CostInfo(elem.cost.id, number - elem.number)); - elem.number = number; - } + CHECK(number > 0); + if (index >= 0 && index < queued.size()) { + auto& elem = queued[index]; + addDebt(CostInfo(elem.cost.id, elem.cost.value) * (number - elem.number)); + elem.number = number; } } @@ -92,7 +90,7 @@ void Workshops::Type::scheduleItems(Collective* collective) { if (i > 0) swap(queued[0], queued[i]); collective->takeResource(queued[0].cost); - addCost(-queued[0].cost); + addDebt(-queued[0].cost); queued[0].state = 0; return; } diff --git a/workshops.h b/workshops.h index b37dae10c..9e43d2862 100644 --- a/workshops.h +++ b/workshops.h @@ -25,7 +25,7 @@ class Workshops { private: void stackQueue(); - void addCost(CostInfo); + void addDebt(CostInfo); vector SERIAL(options); vector SERIAL(queued); Workshops* SERIAL(workshops) = nullptr; From 5df814603556e2ccef143c1c51f0f3619b051b3b Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Sun, 7 Aug 2016 20:59:01 +0200 Subject: [PATCH 016/148] Auto-schedule trap production with trap is placed. Fix some workshop queueing bugs --- collective.cpp | 44 +++++++++++++++++++++++++++++++------------- collective.h | 1 + util.h | 13 +++++-------- workshops.cpp | 21 +++++++++++++-------- workshops.h | 4 ++-- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/collective.cpp b/collective.cpp index cf559db65..2e027923c 100644 --- a/collective.cpp +++ b/collective.cpp @@ -1769,23 +1769,41 @@ bool Collective::tryLockingDoor(Position pos) { return false; } +void Collective::scheduleTrapProduction(TrapType trapType, int count) { + if (count > 0) + for (auto workshopType : ENUM_ALL(WorkshopType)) + for (auto& item : workshops->get(workshopType).getQueued()) + if (ItemFactory::fromId(item.type)->getTrapType() == trapType) + count -= item.number; + if (count > 0) + for (auto workshopType : ENUM_ALL(WorkshopType)) { + auto& options = workshops->get(workshopType).getOptions(); + for (int index : All(options)) + if (ItemFactory::fromId(options[index].type)->getTrapType() == trapType) { + workshops->get(workshopType).queue(index, count); + return; + } + } +} + void Collective::updateConstructions() { - map>> trapItems; - for (TrapType type : ENUM_ALL(TrapType)) - trapItems[type] = getTrapItems(type, territory->getAll()); + EnumMap>> trapItems( + [this] (TrapType type) { return getTrapItems(type, territory->getAll());}); + EnumMap missingTraps; for (auto elem : constructions->getTraps()) - if (!isDelayed(elem.first)) { - vector>& items = trapItems.at(elem.second.getType()); + if (!elem.second.isArmed() && !elem.second.isMarked() && !isDelayed(elem.first)) { + vector>& items = trapItems[elem.second.getType()]; if (!items.empty()) { - if (!elem.second.isArmed() && !elem.second.isMarked()) { - Position pos = items.back().second; - taskMap->addTask(Task::applyItem(this, pos, items.back().first, elem.first), pos); - markItem(items.back().first); - items.pop_back(); - constructions->getTrap(elem.first).setMarked(); - } - } + Position pos = items.back().second; + taskMap->addTask(Task::applyItem(this, pos, items.back().first, elem.first), pos); + markItem(items.back().first); + items.pop_back(); + constructions->getTrap(elem.first).setMarked(); + } else + ++missingTraps[elem.second.getType()]; } + for (TrapType type : ENUM_ALL(TrapType)) + scheduleTrapProduction(type, missingTraps[type]); for (Position pos : constructions->getSquares()) { auto& construction = constructions->getSquare(pos); if (!isDelayed(pos) && !construction.hasTask() && !construction.isBuilt()) { diff --git a/collective.h b/collective.h index ac5d419c7..87f0120e9 100644 --- a/collective.h +++ b/collective.h @@ -338,6 +338,7 @@ class Collective : public TaskCallback { ItemPredicate unMarkedItems() const; EntitySet SERIAL(surrendering); void updateConstructions(); + void scheduleTrapProduction(TrapType, int count); void delayDangerousTasks(const vector& enemyPos, double delayTime); bool isDelayed(Position); unordered_map> SERIAL(delayedPos); diff --git a/util.h b/util.h index 34da1b790..411c73523 100644 --- a/util.h +++ b/util.h @@ -1168,6 +1168,11 @@ class EnumMap { EnumMap(const EnumMap& o) : elems(o.elems) {} EnumMap(EnumMap&& o) : elems(std::move(o.elems)) {} + EnumMap(function f) { + for (T t : EnumAll()) + (*this)[t] = f(t); + } + bool operator == (const EnumMap& other) const { return elems == other.elems; } @@ -1211,14 +1216,6 @@ class EnumMap { return elems[int(elem)]; } - template - EnumMap mapValues(Fun fun) const { - EnumMap ret; - for (T t : EnumAll()) - ret[t] = fun((*this)[t]); - return ret; - } - template void serialize(Archive& ar, const unsigned int version) { vector SERIAL(tmp); diff --git a/workshops.cpp b/workshops.cpp index 1c9990b66..b542e60cd 100644 --- a/workshops.cpp +++ b/workshops.cpp @@ -4,8 +4,8 @@ #include "item_factory.h" #include "item.h" -Workshops::Workshops(const EnumMap>& o) - : types(o.mapValues([this] (const vector& v) { return Type(this, v);})) { +Workshops::Workshops(const EnumMap>& options) + : types([&options, this] (WorkshopType t) { return Type(this, options[t]);}) { } Workshops::Type& Workshops::get(WorkshopType type) { @@ -43,20 +43,25 @@ void Workshops::Type::addDebt(CostInfo cost) { workshops->debt[cost.id] += cost.value; } -void Workshops::Type::queue(int index) { +void Workshops::Type::queue(int index, int count) { + CHECK(count > 0); const Item& newElem = options[index]; - addDebt(newElem.cost); + addDebt(newElem.cost * count); if (!queued.empty() && queued.back() == newElem) - queued.back().number += newElem.number; - else + queued.back().number += count; + else { queued.push_back(newElem); + queued.back().number = count; + } stackQueue(); } void Workshops::Type::unqueue(int index) { if (index >= 0 && index < queued.size()) { - if (!queued[index].state) - addDebt(-queued[index].cost); + if (queued[index].state.get_value_or(0) == 0) + addDebt(-queued[index].cost * queued[index].number); + else + addDebt(-queued[index].cost * (queued[index].number - 1)); queued.erase(queued.begin() + index); } stackQueue(); diff --git a/workshops.h b/workshops.h index 9e43d2862..57e49ec74 100644 --- a/workshops.h +++ b/workshops.h @@ -15,8 +15,8 @@ class Workshops { const vector& getOptions() const; const vector& getQueued() const; vector addWork(double); - void queue(int); - void unqueue(int); + void queue(int index, int count = 1); + void unqueue(int index); void changeNumber(int index, int number); void scheduleItems(Collective*); bool isIdle() const; From 0d9c743b885b9b908b69a30bb4f75642060005dd Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Mon, 8 Aug 2016 08:05:19 +0200 Subject: [PATCH 017/148] Open up workshop queue when clicking on workshop tile --- player_control.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/player_control.cpp b/player_control.cpp index 5ae1de9c2..ddfc171d4 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -1976,6 +1976,9 @@ void PlayerControl::handleSelection(Vec2 pos, const BuildInfo& building, bool re void PlayerControl::tryLockingDoor(Position pos) { if (getCollective()->tryLockingDoor(pos)) updateSquareMemory(pos); + for (auto workshopType : ENUM_ALL(WorkshopType)) + if (getCollective()->getSquares(CollectiveConfig::getWorkshopInfo(workshopType).squareType).count(pos)) + chosenWorkshop = workshopType; } double PlayerControl::getLocalTime() const { From c90c80b8e6bbdbee796071ec1d31e0796c31d956 Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Mon, 8 Aug 2016 14:15:29 +0200 Subject: [PATCH 018/148] Add free floor placement --- player_control.cpp | 12 ++++++++++++ position.cpp | 4 ++++ position.h | 1 + square.cpp | 5 +++++ square.h | 2 ++ square_factory.cpp | 8 ++++++++ square_type.h | 14 +++++++++++++- task.cpp | 6 +++--- tile.cpp | 46 ++++++++++++++++++++++------------------------ view_id.h | 6 ++++++ 10 files changed, 76 insertions(+), 28 deletions(-) diff --git a/player_control.cpp b/player_control.cpp index ddfc171d4..05b8c1809 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -156,6 +156,18 @@ vector PlayerControl::getBuildInfo(TribeId tribe) { "All equipment for your minions can be stored here.", 0, "Storage"), BuildInfo({SquareId::STOCKPILE_RES, {ResourceId::GOLD, 0}, "Resources", true}, {}, "Only wood, iron and granite can be stored here.", 0, "Storage"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::WOOD_FLOOR1, "wooden floor"}}, + {ResourceId::WOOD, 10}, "Wooden"}, {}, "Wooden floor.", 0, "Floors"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::WOOD_FLOOR2, "wooden floor"}}, + {ResourceId::WOOD, 10}, "Wooden"}, {}, "Wooden floor.", 0, "Floors"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::STONE_FLOOR1, "stone floor"}}, + {ResourceId::STONE, 10}, "Stone"}, {}, "Stone floor.", 0, "Floors"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::STONE_FLOOR2, "stone floor"}}, + {ResourceId::STONE, 10}, "Stone"}, {}, "Stone floor.", 0, "Floors"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::CARPET_FLOOR1, "carpet floor"}}, + {ResourceId::GOLD, 10}, "Carpet"}, {}, "Carpet.", 0, "Floors"), + BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::CARPET_FLOOR2, "carpet floor"}}, + {ResourceId::GOLD, 10}, "Carpet"}, {}, "Carpet.", 0, "Floors"), BuildInfo({SquareId::LIBRARY, {ResourceId::WOOD, 20}, "Library"}, {}, "Mana is regenerated here.", 'y'), BuildInfo({SquareId::THRONE, {ResourceId::GOLD, 800}, "Throne", false, false, 0, 1}, diff --git a/position.cpp b/position.cpp index 3dbd8ed3c..468d4d70a 100644 --- a/position.cpp +++ b/position.cpp @@ -384,6 +384,10 @@ bool Position::construct(const SquareType& type) { return !isUnavailable() && modSquare()->construct(*this, type); } +bool Position::isActiveConstruction() const { + return !isUnavailable() && getSquare()->isActiveConstruction(); +} + bool Position::isBurning() const { return isValid() && getSquare()->isBurning(); } diff --git a/position.h b/position.h index 6a7c78c22..2577b497b 100644 --- a/position.h +++ b/position.h @@ -85,6 +85,7 @@ class Position { void destroyBy(Creature* c); void destroy(); bool construct(const SquareType&); + bool isActiveConstruction() const; bool isBurning() const; void setOnFire(double amount); bool needsRenderUpdate() const; diff --git a/square.cpp b/square.cpp index 561bbb0df..e1d510f43 100644 --- a/square.cpp +++ b/square.cpp @@ -136,6 +136,7 @@ static optional getConstructionTime(ConstructionsId id, SquareId squa } case ConstructionsId::DUNGEON_ROOMS: switch (square) { + case SquareId::CUSTOM_FLOOR: return 3; case SquareId::TREASURE_CHEST: return 10; case SquareId::DORM: return 10; case SquareId::TRIBE_DOOR: return 10; @@ -173,6 +174,10 @@ bool Square::canConstruct(const SquareType& type) const { return constructions && getConstructionTime(*constructions, type.getId()); } +bool Square::isActiveConstruction() const { + return !!currentConstruction; +} + bool Square::construct(Position position, const SquareType& type) { setDirty(position); CHECK(canConstruct(type)); diff --git a/square.h b/square.h index d914baaf2..ebf09fac8 100644 --- a/square.h +++ b/square.h @@ -191,6 +191,8 @@ class Square : public Renderable { /** Called just before swapping the old square for the new constructed one.*/ virtual void onConstructNewSquare(Position, Square* newSquare) const {} + bool isActiveConstruction() const; + /** Triggers all time-dependent processes like burning. Calls tick() for items if present. For this method to be called, the square coordinates must be added with Level::addTickingSquare().*/ void tick(Position); diff --git a/square_factory.cpp b/square_factory.cpp index 690468329..8a4332c72 100644 --- a/square_factory.cpp +++ b/square_factory.cpp @@ -731,6 +731,14 @@ Square* SquareFactory::getPtr(SquareType s) { c.movementSet = MovementSet().addTrait(MovementTrait::WALK); c.constructions = ConstructionsId::DUNGEON_ROOMS; )); + case SquareId::CUSTOM_FLOOR: + return new Square(ViewObject(s.get().viewId, ViewLayer::FLOOR_BACKGROUND), + CONSTRUCT(Square::Params, + c.name = s.get().name; + c.vision = VisionId::NORMAL; + c.movementSet = MovementSet().addTrait(MovementTrait::WALK); + c.constructions = ConstructionsId::DUNGEON_ROOMS; + )); case SquareId::BLACK_FLOOR: return new Square(ViewObject(ViewId::EMPTY, ViewLayer::FLOOR_BACKGROUND, "Floor"), CONSTRUCT(Square::Params, diff --git a/square_type.h b/square_type.h index 0da12cb9f..a73d8a245 100644 --- a/square_type.h +++ b/square_type.h @@ -10,6 +10,7 @@ RICH_ENUM(SquareId, FLOOR, BLACK_FLOOR, + CUSTOM_FLOOR, BRIDGE, ROAD, GRASS, @@ -107,8 +108,17 @@ struct ChestInfo { } }; +struct CustomFloorInfo { + ViewId SERIAL(viewId); + string SERIAL(name); + SERIALIZE_ALL(viewId, name); + bool operator == (const CustomFloorInfo& o) const { + return viewId == o.viewId && name == o.name; + } +}; + class SquareType : public EnumVariant { using EnumVariant::EnumVariant; diff --git a/task.cpp b/task.cpp index a74cf0a57..7b0f45632 100644 --- a/task.cpp +++ b/task.cpp @@ -106,9 +106,9 @@ class Construction : public Task { Vec2 dir = c->getPosition().getDir(position); if (auto action = c->construct(dir, type)) return {1.0, action.append([=](Creature* c) { - if (!c->construct(dir, type)) { - setDone(); - callback->onConstructed(position, type); + if (!position.isActiveConstruction()) { + setDone(); + callback->onConstructed(position, type); } })}; else { diff --git a/tile.cpp b/tile.cpp index cbdd54cea..06b7e4e45 100644 --- a/tile.cpp +++ b/tile.cpp @@ -323,6 +323,12 @@ class TileCoordLookup { Tile::addTile(ViewId::ROAD, getRoadTile("road")); Tile::addTile(ViewId::FLOOR, sprite("floor")); Tile::addTile(ViewId::KEEPER_FLOOR, sprite("floor_keeper")); + Tile::addTile(ViewId::WOOD_FLOOR1, sprite("floor_wood1")); + Tile::addTile(ViewId::WOOD_FLOOR2, sprite("floor_wood2")); + Tile::addTile(ViewId::STONE_FLOOR1, sprite("floor_stone1")); + Tile::addTile(ViewId::STONE_FLOOR2, sprite("floor_stone2")); + Tile::addTile(ViewId::CARPET_FLOOR1, sprite("floor_carpet1")); + Tile::addTile(ViewId::CARPET_FLOOR2, sprite("floor_carpet2")); Tile::addTile(ViewId::SAND, getExtraBorderTile("sand") .addExtraBorderId(ViewId::WATER)); Tile::addTile(ViewId::MUD, getExtraBorderTile("mud") @@ -478,7 +484,7 @@ class TileCoordLookup { Tile::addTile(ViewId::KEY, sprite("key")); Tile::addTile(ViewId::FOUNTAIN, sprite("fountain").setNoShadow()); Tile::addTile(ViewId::GOLD, sprite("gold").setNoShadow()); - Tile::addTile(ViewId::TREASURE_CHEST, sprite("treasurydeco").setNoShadow().addBackground(byName("treasury"))); + Tile::addTile(ViewId::TREASURE_CHEST, sprite("treasurydeco").setNoShadow()); Tile::addTile(ViewId::CHEST, sprite("chest").setNoShadow()); Tile::addTile(ViewId::OPENED_CHEST, sprite("chest_opened").setNoShadow()); Tile::addTile(ViewId::COFFIN, sprite("coffin").setNoShadow()); @@ -502,35 +508,21 @@ class TileCoordLookup { Tile::addTile(ViewId::TORCH, sprite("torch").setNoShadow().setTranslucent(0.35)); Tile::addTile(ViewId::ALTAR, sprite("altar").setNoShadow()); Tile::addTile(ViewId::CREATURE_ALTAR, sprite("altar2").setNoShadow()); - Tile::addTile(ViewId::TORTURE_TABLE, empty().addConnection(DirSet::fullSet(), byName("torturedeco")) - .addBackground(byName("torture")) - .setFloorBorders()); + Tile::addTile(ViewId::TORTURE_TABLE, byName("torturedeco")); Tile::addTile(ViewId::IMPALED_HEAD, sprite("impaledhead").setNoShadow()); Tile::addTile(ViewId::WHIPPING_POST, sprite("whipping_post").setNoShadow()); Tile::addTile(ViewId::NOTICE_BOARD, sprite("board").setNoShadow()); Tile::addTile(ViewId::SOKOBAN_HOLE, sprite("hole").setNoShadow()); - Tile::addTile(ViewId::TRAINING_ROOM, empty().addConnection(DirSet::fullSet(), byName("traindeco")) - .addBackground(byName("train")).setFloorBorders()); - Tile::addTile(ViewId::RITUAL_ROOM, empty().addConnection(DirSet::fullSet(), byName("ritualroomdeco")) - .addBackground(byName("ritualroom")) - .setFloorBorders()); - Tile::addTile(ViewId::LIBRARY, empty().addConnection(DirSet::fullSet(), byName("libdeco")) - .addBackground(byName("lib")).setFloorBorders()); - Tile::addTile(ViewId::LABORATORY, empty().addConnection(DirSet::fullSet(), byName("labdeco")) - .addBackground(byName("lab")).setFloorBorders()); + Tile::addTile(ViewId::TRAINING_ROOM, byName("traindeco")); + Tile::addTile(ViewId::RITUAL_ROOM, byName("ritualroomdeco")); + Tile::addTile(ViewId::LIBRARY, byName("libdeco")); + Tile::addTile(ViewId::LABORATORY, byName("labdeco")); Tile::addTile(ViewId::CAULDRON, sprite("labdeco").setNoShadow()); Tile::addTile(ViewId::BEAST_LAIR, sprite("lair").setFloorBorders()); - Tile::addTile(ViewId::BEAST_CAGE, sprite("lairdeco").setNoShadow() - .addBackground(byName("lair"))); - Tile::addTile(ViewId::FORGE, empty().addConnection(DirSet::fullSet(), byName("forgedeco")) - .addBackground(byName("forge")) - .setFloorBorders()); - Tile::addTile(ViewId::WORKSHOP, empty().addConnection(DirSet::fullSet(), byName("workshopdeco")) - .addBackground(byName("workshop")) - .setFloorBorders()); - Tile::addTile(ViewId::JEWELER, empty().addConnection(DirSet::fullSet(), byName("jewelerdeco")) - .addBackground(byName("jeweler")) - .setFloorBorders()); + Tile::addTile(ViewId::BEAST_CAGE, sprite("lairdeco").setNoShadow()); + Tile::addTile(ViewId::FORGE, byName("forgedeco")); + Tile::addTile(ViewId::WORKSHOP, byName("workshopdeco")); + Tile::addTile(ViewId::JEWELER, byName("jewelerdeco")); Tile::addTile(ViewId::CEMETERY, sprite("graveyard").setFloorBorders()); Tile::addTile(ViewId::GRAVE, sprite("RIP").setNoShadow()); Tile::addTile(ViewId::ROBE, sprite("robe")); @@ -614,6 +606,12 @@ class TileCoordLookup { Tile::addSymbol(ViewId::DWARF_FEMALE, symbol(u8"h", ColorId::LIGHT_BLUE)); Tile::addSymbol(ViewId::FLOOR, symbol(u8".", ColorId::LIGHT_GRAY)); Tile::addSymbol(ViewId::KEEPER_FLOOR, symbol(u8".", ColorId::WHITE)); + Tile::addSymbol(ViewId::WOOD_FLOOR1, symbol(u8".", ColorId::LIGHT_BROWN)); + Tile::addSymbol(ViewId::WOOD_FLOOR2, symbol(u8".", ColorId::BROWN)); + Tile::addSymbol(ViewId::STONE_FLOOR1, symbol(u8".", ColorId::LIGHT_GRAY)); + Tile::addSymbol(ViewId::STONE_FLOOR2, symbol(u8".", ColorId::GRAY)); + Tile::addSymbol(ViewId::CARPET_FLOOR1, symbol(u8".", ColorId::PURPLE)); + Tile::addSymbol(ViewId::CARPET_FLOOR2, symbol(u8".", ColorId::PINK)); Tile::addSymbol(ViewId::BRIDGE, symbol(u8"_", ColorId::BROWN)); Tile::addSymbol(ViewId::ROAD, symbol(u8".", ColorId::LIGHT_GRAY)); Tile::addSymbol(ViewId::SAND, symbol(u8".", ColorId::YELLOW)); diff --git a/view_id.h b/view_id.h index 69087a424..326660336 100644 --- a/view_id.h +++ b/view_id.h @@ -125,6 +125,12 @@ RICH_ENUM(ViewId, FLOOR, KEEPER_FLOOR, + WOOD_FLOOR1, + WOOD_FLOOR2, + STONE_FLOOR1, + STONE_FLOOR2, + CARPET_FLOOR1, + CARPET_FLOOR2, SAND, BRIDGE, ROAD, From 994bdb854fa4fd51285f57e938db6837affc3a4f Mon Sep 17 00:00:00 2001 From: Michal Brzozowski Date: Thu, 11 Aug 2016 23:13:40 +0200 Subject: [PATCH 019/148] Add movement blocking furniture --- attack_trigger.h | 6 +- collective.cpp | 219 ++++++++++++++++++++++++------------------ collective.h | 5 +- collective_config.cpp | 102 ++++++++------------ collective_config.h | 25 ++--- collective_control.h | 1 + construction_map.cpp | 99 ++++++++++++++++++- construction_map.h | 37 +++++++ creature.cpp | 29 ++++-- creature.h | 4 +- enemy_factory.cpp | 18 ++-- enums.h | 1 + furniture.cpp | 34 +++++++ furniture.h | 24 +++++ furniture_factory.cpp | 61 ++++++++++++ furniture_factory.h | 6 ++ furniture_type.h | 19 ++++ game.cpp | 13 ++- level.cpp | 25 +---- level.h | 15 ++- level_maker.cpp | 7 +- map_gui.cpp | 5 +- model_builder.cpp | 28 +++--- player.cpp | 2 +- player_control.cpp | 209 +++++++++++++++++++++++++++++----------- player_control.h | 5 +- position.cpp | 100 +++++++++++++++---- position.h | 7 +- square.cpp | 23 +---- square.h | 2 - square_factory.cpp | 117 +++++----------------- square_type.h | 15 --- task.cpp | 100 ++++++++++++++----- task.h | 3 +- task_callback.h | 3 +- technology.cpp | 3 +- tile.cpp | 66 +++++++------ tile.h | 2 + util.h | 1 + village_behaviour.cpp | 11 ++- 40 files changed, 941 insertions(+), 511 deletions(-) create mode 100644 furniture.cpp create mode 100644 furniture.h create mode 100644 furniture_factory.cpp create mode 100644 furniture_factory.h create mode 100644 furniture_type.h diff --git a/attack_trigger.h b/attack_trigger.h index 08d2dfa67..d0323f3f7 100644 --- a/attack_trigger.h +++ b/attack_trigger.h @@ -2,7 +2,7 @@ #define _ATTACK_TRIGGER_H #include "util.h" -#include "square_type.h" +#include "furniture_type.h" RICH_ENUM(AttackTriggerId, POWER, @@ -17,9 +17,9 @@ RICH_ENUM(AttackTriggerId, PROXIMITY ); -class AttackTrigger : public EnumVariant { + ASSIGN(FurnitureType, AttackTriggerId::ROOM_BUILT)> { using EnumVariant::EnumVariant; }; diff --git a/collective.cpp b/collective.cpp index 2e027923c..cc2bbec03 100644 --- a/collective.cpp +++ b/collective.cpp @@ -46,9 +46,9 @@ struct Collective::ItemFetchInfo { ItemIndex index; ItemPredicate predicate; - vector destination; + FurnitureType destination; bool oneAtATime; - vector additionalPos; + optional additionalPos; Warning warning; }; @@ -82,14 +82,15 @@ ItemPredicate Collective::unMarkedItems() const { const vector& Collective::getFetchInfo() const { if (itemFetchInfo.empty()) itemFetchInfo = { - {ItemIndex::CORPSE, unMarkedItems(), {SquareId::CEMETERY}, true, {}, Warning::GRAVES}, - {ItemIndex::GOLD, unMarkedItems(), {SquareId::TREASURE_CHEST}, false, {}, Warning::CHESTS}, + {ItemIndex::CORPSE, unMarkedItems(), FurnitureType::GRAVE, true, none, Warning::GRAVES}, + {ItemIndex::GOLD, unMarkedItems(), FurnitureType::TREASURE_CHEST, false, none, Warning::CHESTS}, {ItemIndex::MINION_EQUIPMENT, [this](const Item* it) { return it->getClass() != ItemClass::GOLD && !isItemMarked(it);}, - config->getEquipmentStorage(), false, {}, Warning::EQUIPMENT_STORAGE}, - {ItemIndex::WOOD, unMarkedItems(), config->getResourceStorage(), false, {SquareId::TREE_TRUNK}, Warning::RESOURCE_STORAGE}, - {ItemIndex::IRON, unMarkedItems(), config->getResourceStorage(), false, {}, Warning::RESOURCE_STORAGE}, - {ItemIndex::STONE, unMarkedItems(), config->getResourceStorage(), false, {}, Warning::RESOURCE_STORAGE}, + config->getEquipmentStorage(), false, none, Warning::EQUIPMENT_STORAGE}, + {ItemIndex::WOOD, unMarkedItems(), config->getResourceStorage(), false, SquareType{SquareId::TREE_TRUNK}, + Warning::RESOURCE_STORAGE}, + {ItemIndex::IRON, unMarkedItems(), config->getResourceStorage(), false, none, Warning::RESOURCE_STORAGE}, + {ItemIndex::STONE, unMarkedItems(), config->getResourceStorage(), false, none, Warning::RESOURCE_STORAGE}, }; return itemFetchInfo; } @@ -126,8 +127,8 @@ double Collective::getAttractionOccupation(const MinionAttraction& attraction) { double Collective::getAttractionValue(const MinionAttraction& attraction) { switch (attraction.getId()) { - case AttractionId::SQUARE: - return getSquares(attraction.get()).size(); + case AttractionId::FURNITURE: + return constructions->getFurnitureCount(attraction.get()); case AttractionId::ITEM_INDEX: return getAllItems(attraction.get(), true).size(); } @@ -444,6 +445,17 @@ PTask Collective::generateMinionTask(Creature* c, MinionTask task) { } break; } + case MinionTaskInfo::FURNITURE: { + set squares = constructions->getFurniturePositions(info.furniture); + if (!squares.empty()) { + auto searchType = Task::RANDOM_CLOSE; + if (auto workshopType = config->getWorkshopType(task)) + if (workshops->get(*workshopType).isIdle()) + searchType = Task::LAZY; + return Task::applySquare(this, vector(squares.begin(), squares.end()), searchType); + } + break; + } case MinionTaskInfo::EXPLORE: if (auto pos = getTileToExplore(c, task)) return Task::explore(*pos); @@ -541,7 +553,8 @@ PTask Collective::getEquipmentTask(Creature* c) { for (Item* it : c->getEquipment().getItems()) if (!c->getEquipment().isEquipped(it) && c->getEquipment().canEquip(it)) tasks.push_back(Task::equipItem(it)); - for (Position v : getAllSquares(config->getEquipmentStorage())) { + auto storageType = config->getEquipmentStorage(); + for (Position v : constructions->getFurniturePositions(storageType)) { vector it = filter(v.getItems(ItemIndex::MINION_EQUIPMENT), [this, c] (const Item* it) { return minionEquipment->isOwner(it, c) && it->canEquip(); }); if (!it.empty()) @@ -750,7 +763,7 @@ static CostInfo getSpawnCost(SpawnType type, int howMany) { } } -void Collective::considerBuildingBeds() { +/*void Collective::considerBuildingBeds() { bool bedsWarning = false; for (auto spawnType : ENUM_ALL(SpawnType)) if (auto bedType = config->getDormInfo()[spawnType].getBedType()) { @@ -759,7 +772,7 @@ void Collective::considerBuildingBeds() { bedsWarning |= tryBuildingBeds(spawnType, neededBeds) < neededBeds; } warnings.set(Warning::BEDS, bedsWarning); -} +}*/ bool Collective::considerImmigrant(const ImmigrantInfo& info) { if (info.techId && !hasTech(*info.techId)) @@ -772,12 +785,12 @@ bool Collective::considerImmigrant(const ImmigrantInfo& info) { if (!immigrants[0]->getAttributes().getSpawnType() || info.ignoreSpawnType) return considerNonSpawnImmigrant(info, std::move(immigrants)); SpawnType spawnType = *immigrants[0]->getAttributes().getSpawnType(); - SquareType dormType = config->getDormInfo()[spawnType].dormType; + FurnitureType bedType = config->getDormInfo()[spawnType].bedType; if (!hasResource(getSpawnCost(spawnType, groupSize))) return false; vector spawnPos; if (info.spawnAtDorm) { - for (Position v : Random.permutation(getSquares(dormType))) + for (Position v : Random.permutation(constructions->getFurniturePositions(bedType))) if (v.canEnter(immigrants[spawnPos.size()].get())) { spawnPos.push_back(v); if (spawnPos.size() >= immigrants.size()) @@ -786,15 +799,9 @@ bool Collective::considerImmigrant(const ImmigrantInfo& info) { } else spawnPos = getSpawnPos(extractRefs(immigrants)); groupSize = min(groupSize, spawnPos.size()); - if (auto bedType = config->getDormInfo()[spawnType].getBedType()) { - int neededBeds = bySpawnType[spawnType].size() + groupSize - constructions->getSquareCount(*bedType); - if (neededBeds > 0) { - int numBuilt = tryBuildingBeds(spawnType, neededBeds); - if (numBuilt == 0) - return false; - groupSize -= neededBeds - numBuilt; - } - } + int neededBeds = bySpawnType[spawnType].size() + groupSize + - constructions->getFurnitureCount(config->getDormInfo()[spawnType].bedType); + groupSize -= neededBeds; if (groupSize < 1) return false; if (immigrants.size() > groupSize) @@ -814,29 +821,6 @@ bool Collective::considerImmigrant(const ImmigrantInfo& info) { return true; } -int Collective::tryBuildingBeds(SpawnType spawnType, int numBeds) { - int numBuilt = 0; - SquareType bedType = *config->getDormInfo()[spawnType].getBedType(); - SquareType dormType = config->getDormInfo()[spawnType].dormType; - set bedPos = getSquares(bedType); - set dormPos = getSquares(dormType); - for (Position v : copyOf(dormPos)) - if (constructions->containsSquare(v) && !constructions->getSquare(v).isBuilt()) { - // this means there is a bed planned here already - dormPos.erase(v); - bedPos.insert(v); - } - for (int i : Range(numBeds)) - if (auto newPos = chooseBedPos(dormPos, bedPos)) { - ++numBuilt; - addConstruction(*newPos, bedType, CostInfo::noCost(), false, false); - bedPos.insert(*newPos); - dormPos.erase(*newPos); - } else - break; - return numBuilt; -} - void Collective::addNewCreatureMessage(const vector& immigrants) { if (immigrants.size() == 1) control->addMessage(PlayerMessage(immigrants[0]->getName().a() + " joins your forces.") @@ -996,7 +980,7 @@ void Collective::tick() { control->tick(); considerBirths(); decayMorale(); - considerBuildingBeds(); + //considerBuildingBeds(); /* if (nextPayoutTime > -1 && time > nextPayoutTime) { nextPayoutTime += config->getPayoutTime(); makePayouts(); @@ -1007,12 +991,12 @@ void Collective::tick() { considerMoraleWarning(); setWarning(Warning::MANA, numResource(ResourceId::MANA) < 100); setWarning(Warning::DIGGING, getSquares(SquareId::FLOOR).empty()); - setWarning(Warning::MORE_LIGHTS, - constructions->getTorches().size() * 25 < getAllSquares(config->getRoomsNeedingLight()).size()); +/* setWarning(Warning::MORE_LIGHTS, + constructions->getTorches().size() * 25 < getAllSquares(config->getRoomsNeedingLight()).size());*/ for (SpawnType spawnType : ENUM_ALL(SpawnType)) { DormInfo info = config->getDormInfo()[spawnType]; - if (info.warning && info.getBedType()) - setWarning(*info.warning, !chooseBedPos(getSquares(info.dormType), getSquares(*info.getBedType()))); + if (info.warning) + setWarning(*info.warning, constructions->getFurnitureCount(info.bedType) < bySpawnType[spawnType].size()); } for (auto minionTask : ENUM_ALL(MinionTask)) { auto elem = config->getTaskInfo(minionTask); @@ -1059,8 +1043,8 @@ void Collective::tick() { for (const ItemFetchInfo& elem : getFetchInfo()) { for (Position pos : territory->getAll()) fetchItems(pos, elem); - for (SquareType type : elem.additionalPos) - for (Position pos : getSquares(type)) + if (auto type = elem.additionalPos) + for (Position pos : getSquares(*type)) fetchItems(pos, elem); } if (config->getManageEquipment() && Random.roll(10)) @@ -1215,8 +1199,8 @@ void Collective::onEvent(const GameEvent& event) { for (auto& elem : *mySquares) if (elem.second.count(pos)) { elem.second.erase(pos); - if (config->getEfficiencySquares().count(elem.first)) - updateEfficiency(pos, elem.first); +/* if (config->getEfficiencySquares().count(elem.first)) + updateEfficiency(pos, elem.first);*/ } for (auto& elem : mySquares2) if (elem.second.count(pos)) @@ -1347,7 +1331,11 @@ const double lightBase = 0.5; const double flattenVal = 0.9; double Collective::getEfficiency(Position pos) const { - double base = squareEfficiency.at(pos) == 8 ? 1 : 0.5; + double base; + if (squareEfficiency.count(pos)) + base =squareEfficiency.at(pos) == 8 ? 1 : 0.5; + else + base = 1; return base * min(1.0, (lightBase + pos.getLight() * (1 - lightBase)) / flattenVal); } @@ -1391,10 +1379,10 @@ double Collective::getGlobalTime() const { int Collective::numResource(ResourceId id) const { int ret = credit[id]; - if (config->getResourceInfo().at(id).itemIndex) - for (SquareType type : config->getResourceInfo().at(id).storageType) - for (Position pos : getSquares(type)) - ret += pos.getItems(*config->getResourceInfo().at(id).itemIndex).size(); + if (auto itemIndex = config->getResourceInfo(id).itemIndex) + if (auto storageType = config->getResourceInfo(id).storageType) + for (Position pos : constructions->getFurniturePositions(*storageType)) + ret += pos.getItems(*itemIndex).size(); return ret; } @@ -1405,6 +1393,11 @@ int Collective::numResourcePlusDebt(ResourceId id) const { if (!construction.isBuilt() && construction.getCost().id == id) ret -= construction.getCost().value; } + for (Position pos : constructions->getAllFurniture()) { + auto& furniture = constructions->getFurniture(pos); + if (!furniture.isBuilt() && furniture.getCost().id == id) + ret -= furniture.getCost().value; + } for (auto& elem : taskMap->getCompletionCosts()) if (elem.second.id == id && !elem.first->isDone()) ret += elem.second.value; @@ -1433,28 +1426,32 @@ void Collective::takeResource(const CostInfo& cost) { credit[cost.id] = 0; } } - if (config->getResourceInfo().at(cost.id).itemIndex) - for (Position pos : Random.permutation(getAllSquares(config->getResourceInfo().at(cost.id).storageType))) { - vector goldHere = pos.getItems(*config->getResourceInfo().at(cost.id).itemIndex); - for (Item* it : goldHere) { - pos.removeItem(it); - if (--num == 0) - return; + if (auto itemIndex = config->getResourceInfo(cost.id).itemIndex) + if (auto storageType = config->getResourceInfo(cost.id).storageType) + for (Position pos : Random.permutation(constructions->getFurniturePositions(*storageType))) { + vector goldHere = pos.getItems(*itemIndex); + for (Item* it : goldHere) { + pos.removeItem(it); + if (--num == 0) + return; + } } - } - FAIL << "Not enough " << config->getResourceInfo().at(cost.id).name << " missing " << num << " of " << cost.value; + FAIL << "Not enough " << config->getResourceInfo(cost.id).name << " missing " << num << " of " << cost.value; } void Collective::returnResource(const CostInfo& amount) { if (amount.value == 0) return; CHECK(amount.value > 0); - vector destination = getAllSquares(config->getResourceInfo().at(amount.id).storageType); - if (!destination.empty()) { - Random.choose(destination).dropItems(ItemFactory::fromId( - config->getResourceInfo().at(amount.id).itemId, amount.value)); - } else - credit[amount.id] += amount.value; + if (auto storageType = config->getResourceInfo(amount.id).storageType) { + const set& destination = constructions->getFurniturePositions(*storageType); + if (!destination.empty()) { + Random.choose(destination).dropItems(ItemFactory::fromId( + config->getResourceInfo(amount.id).itemId, amount.value)); + return; + } + } + credit[amount.id] += amount.value; } vector> Collective::getTrapItems(TrapType type, const vector& squares) const { @@ -1628,6 +1625,12 @@ void Collective::removeConstruction(Position pos) { constructions->removeSquare(pos); } +void Collective::removeFurniture(Position pos) { + if (constructions->getFurniture(pos).hasTask()) + returnResource(taskMap->removeTask(constructions->getFurniture(pos).getTask())); + constructions->removeFurniture(pos); +} + void Collective::destroySquare(Position pos) { if (pos.isDestroyable() && territory->contains(pos)) pos.destroy(); @@ -1637,6 +1640,8 @@ void Collective::destroySquare(Position pos) { if (constructions->containsTrap(pos)) { removeTrap(pos); } + if (constructions->containsFurniture(pos)) + removeFurniture(pos); if (constructions->containsSquare(pos)) removeConstruction(pos); if (constructions->containsTorch(pos)) @@ -1644,6 +1649,17 @@ void Collective::destroySquare(Position pos) { pos.removeTriggers(); } +void Collective::addFurniture(Position pos, FurnitureType type, const CostInfo& cost, bool immediately, + bool noCredit) { + if (immediately && hasResource(cost)) { + while (!pos.construct(type)) {} + onConstructed(pos, type); + } else if (!noCredit || hasResource(cost)) { + constructions->addFurniture(pos, ConstructionMap::FurnitureInfo(type, cost)); + updateConstructions(); + } +} + void Collective::addConstruction(Position pos, SquareType type, const CostInfo& cost, bool immediately, bool noCredit) { if (type.getId() == SquareId::MOUNTAIN && (pos.isChokePoint({MovementTrait::WALK}) || @@ -1731,6 +1747,13 @@ bool Collective::isConstructionReachable(Position pos) { return false; } +void Collective::onConstructed(Position pos, FurnitureType type) { + constructions->onConstructed(pos, type); + control->onConstructed(pos, type); + if (Task* task = taskMap->getMarked(pos)) + taskMap->removeTask(task); +} + void Collective::onConstructed(Position pos, const SquareType& type) { CHECK(!getSquares(type).count(pos)); for (auto& elem : *mySquares) @@ -1744,8 +1767,8 @@ void Collective::onConstructed(Position pos, const SquareType& type) { if (!contains({SquareId::TREE_TRUNK}, type.getId())) territory->insert(pos); (*mySquares)[type].insert(pos); - if (config->getEfficiencySquares().count(type)) - updateEfficiency(pos, type); +/* if (config->getEfficiencySquares().count(type)) + updateEfficiency(pos, type);*/ if (constructions->containsSquare(pos) && !constructions->getSquare(pos).isBuilt()) constructions->getSquare(pos).setBuilt(); if (type == SquareId::FLOOR) { @@ -1815,6 +1838,17 @@ void Collective::updateConstructions() { takeResource(construction.getCost()); } } + for (Position pos : constructions->getAllFurniture()) { + auto& construction = constructions->getFurniture(pos); + if (!isDelayed(pos) && !construction.hasTask() && !construction.isBuilt()) { + if (!hasResource(construction.getCost())) + continue; + construction.setTask( + taskMap->addTaskCost(Task::construction(this, pos, construction.getFurnitureType()), pos, + construction.getCost())->getUniqueId()); + takeResource(construction.getCost()); + } + } for (auto& elem : constructions->getTorches()) if (!isDelayed(elem.first) && !elem.second.hasTask() && !elem.second.isBuilt()) constructions->getTorch(elem.first).setTask(taskMap->addTask( @@ -1855,12 +1889,12 @@ bool Collective::isDelayed(Position pos) { } void Collective::fetchAllItems(Position pos) { - if (isKnownSquare(pos)) { + if (isKnownSquare(pos) && pos.canEnterEmpty(MovementTrait::WALK)) { vector tasks; for (const ItemFetchInfo& elem : getFetchInfo()) { vector equipment = filter(pos.getItems(elem.index), elem.predicate); if (!equipment.empty()) { - vector destination = getAllSquares(elem.destination); + const set& destination = constructions->getFurniturePositions(elem.destination); if (!destination.empty()) { setWarning(elem.warning, false); if (elem.oneAtATime) @@ -1880,16 +1914,12 @@ void Collective::fetchAllItems(Position pos) { } void Collective::fetchItems(Position pos, const ItemFetchInfo& elem) { - if (isDelayed(pos) || (constructions->containsTrap(pos) && - constructions->getTrap(pos).getType() == TrapType::BOULDER && - constructions->getTrap(pos).isArmed())) + if (isDelayed(pos) || !pos.canEnterEmpty(MovementTrait::WALK) + || constructions->getFurniturePositions(elem.destination).count(pos)) return; - for (SquareType type : elem.destination) - if (getSquares(type).count(pos)) - return; vector equipment = filter(pos.getItems(elem.index), elem.predicate); if (!equipment.empty()) { - vector destination = getAllSquares(elem.destination); + const set& destination = constructions->getFurniturePositions(elem.destination); if (!destination.empty()) { setWarning(elem.warning, false); if (elem.oneAtATime) @@ -1964,15 +1994,14 @@ void Collective::addProducesMessage(const Creature* c, const vector& item control->addMessage(c->getName().a() + " produces " + items[0]->getAName()); } -void Collective::onAppliedSquare(Position pos) { - Creature* c = NOTNULL(pos.getCreature()); +void Collective::onAppliedSquare(Creature* c, Position pos) { MinionTask currentTask = currentTasks.getOrFail(c).task; if (config->getTaskInfo(currentTask).cost > 0) { if (nextPayoutTime == -1 && minionPayment.getMaybe(c) && minionPayment.getOrFail(c).salary > 0) nextPayoutTime = getLocalTime() + config->getPayoutTime(); minionPayment.getOrInit(c).workAmount += config->getTaskInfo(currentTask).cost; } - if (getSquares(SquareId::LIBRARY).count(pos)) { + if (constructions->getFurniturePositions(FurnitureType::BOOK_SHELF).count(pos)) { addMana(0.2); auto availableSpells = Technology::getAvailableSpells(this); if (Random.rollD(60.0 / (getEfficiency(pos))) && !availableSpells.empty()) { @@ -1986,14 +2015,14 @@ void Collective::onAppliedSquare(Position pos) { } } } - if (getSquares(SquareId::THRONE).count(pos) && c == getLeader()) { + if (constructions->getFurniturePositions(FurnitureType::THRONE).count(pos) && c == getLeader()) { addMana(0.2); } - if (getSquares(SquareId::TRAINING_ROOM).count(pos)) + if (constructions->getFurniturePositions(FurnitureType::TRAINING_DUMMY).count(pos)) c->getAttributes().exerciseAttr(Random.choose(), getEfficiency(pos)); for (auto workshopType : ENUM_ALL(WorkshopType)) { auto& elem = config->getWorkshopInfo(workshopType); - if (getSquares(elem.squareType).count(pos)) { + if (constructions->getFurniturePositions(elem.furniture).count(pos)) { vector items = workshops->get(workshopType).addWork(getEfficiency(pos) * (1 + c->getMorale()) / 2); if (!items.empty()) { @@ -2004,7 +2033,7 @@ void Collective::onAppliedSquare(Position pos) { if (items[0]->getClass() == ItemClass::POTION) getGame()->getStatistics().add(StatId::POTION_PRODUCED); addProducesMessage(c, items); - pos.dropItems(std::move(items)); + c->getPosition().dropItems(std::move(items)); } } } @@ -2014,7 +2043,7 @@ double Collective::getDangerLevel() const { double ret = 0; for (const Creature* c : getCreatures(MinionTrait::FIGHTER)) ret += c->getDifficultyPoints(); - ret += getSquares(SquareId::IMPALED_HEAD).size() * 150; + ret += constructions->getFurnitureCount(FurnitureType::IMPALED_HEAD) * 150; return ret; } diff --git a/collective.h b/collective.h index 87f0120e9..ad9a850e2 100644 --- a/collective.h +++ b/collective.h @@ -158,6 +158,8 @@ class Collective : public TaskCallback { void removeTrap(Position); void addConstruction(Position, SquareType, const CostInfo&, bool immediately, bool noCredit); void removeConstruction(Position); + void addFurniture(Position, FurnitureType, const CostInfo&, bool immediately, bool noCredit); + void removeFurniture(Position); void destroySquare(Position); bool isPlannedTorch(Position) const; bool canPlaceTorch(Position) const; @@ -229,8 +231,9 @@ class Collective : public TaskCallback { virtual void onTaskPickedUp(Position, EntitySet) override; virtual void onCantPickItem(EntitySet items) override; virtual void onConstructed(Position, const SquareType&) override; + virtual void onConstructed(Position, FurnitureType) override; virtual void onTorchBuilt(Position, Trigger*) override; - virtual void onAppliedSquare(Position) override; + virtual void onAppliedSquare(Creature*, Position) override; virtual void onKillCancelled(Creature*) override; virtual void onBedCreated(Position, const SquareType& fromType, const SquareType& toType) override; virtual void onCopulated(Creature* who, Creature* with) override; diff --git a/collective_config.cpp b/collective_config.cpp index dcb80ee62..14eb8b360 100644 --- a/collective_config.cpp +++ b/collective_config.cpp @@ -14,6 +14,7 @@ #include "item.h" #include "square_type.h" #include "view_id.h" +#include "furniture_type.h" AttractionInfo::AttractionInfo(MinionAttraction a, double cl, double min, bool mand) : attraction(a), amountClaimed(cl), minAmount(min), mandatory(mand) {} @@ -218,51 +219,24 @@ vector CollectiveConfig::getBirthSpawns() const { return {}; } -optional CollectiveConfig::getSecondarySquare(SquareType type) { - switch (type.getId()) { - case SquareId::DORM: return SquareType(SquareId::BED); - case SquareId::BEAST_LAIR: return SquareType(SquareId::BEAST_CAGE); - case SquareId::CEMETERY: return SquareType(SquareId::GRAVE); - default: return none; - } -} - -optional DormInfo::getBedType() const { - return CollectiveConfig::getSecondarySquare(dormType); -} - const EnumMap& CollectiveConfig::getDormInfo() const { static EnumMap dormInfo { - {SpawnType::HUMANOID, {SquareId::DORM, CollectiveWarning::BEDS}}, - {SpawnType::UNDEAD, {SquareId::CEMETERY}}, - {SpawnType::BEAST, {SquareId::BEAST_LAIR}}, - {SpawnType::DEMON, {SquareId::RITUAL_ROOM}}, + {SpawnType::HUMANOID, {FurnitureType::BED, CollectiveWarning::BEDS}}, + {SpawnType::UNDEAD, {FurnitureType::GRAVE}}, + {SpawnType::BEAST, {FurnitureType::BEAST_CAGE}}, + {SpawnType::DEMON, {FurnitureType::DEMON_SHRINE}}, }; return dormInfo; } -const static unordered_set efficiencySquares { - SquareId::TRAINING_ROOM, - SquareId::TORTURE_TABLE, - SquareId::WORKSHOP, - SquareId::FORGE, - SquareId::LABORATORY, - SquareId::JEWELER, - SquareId::LIBRARY, -}; - -unordered_set CollectiveConfig::getEfficiencySquares() const { - return efficiencySquares; -} - -vector CollectiveConfig::getRoomsNeedingLight() const { - static vector ret { - SquareId::WORKSHOP, - SquareId::FORGE, - SquareId::LABORATORY, - SquareId::JEWELER, - SquareId::TRAINING_ROOM, - SquareId::LIBRARY}; +const vector& CollectiveConfig::getRoomsNeedingLight() const { + static vector ret { + FurnitureType::WORKSHOP, + FurnitureType::FORGE, + FurnitureType::LABORATORY, + FurnitureType::JEWELER, + FurnitureType::TRAINING_DUMMY, + FurnitureType::BOOK_SHELF}; return ret; }; @@ -278,30 +252,30 @@ int CollectiveConfig::getTaskDuration(const Creature* c, MinionTask task) const } } -const static vector resourceStorage {SquareId::STOCKPILE, SquareId::STOCKPILE_RES}; -const static vector equipmentStorage {SquareId::STOCKPILE, SquareId::STOCKPILE_EQUIP}; +const static auto resourceStorage = FurnitureType::STOCKPILE_RES; +const static auto equipmentStorage = FurnitureType::STOCKPILE_EQUIP; -const vector& CollectiveConfig::getEquipmentStorage() { +const FurnitureType& CollectiveConfig::getEquipmentStorage() { return equipmentStorage; } -const vector& CollectiveConfig::getResourceStorage() { +const FurnitureType& CollectiveConfig::getResourceStorage() { return resourceStorage; } -const map& CollectiveConfig::getResourceInfo() { - static map ret = { - {CollectiveResourceId::MANA, { {}, none, ItemId::GOLD_PIECE, "mana", ViewId::MANA}}, - {CollectiveResourceId::PRISONER_HEAD, { {}, none, ItemId::GOLD_PIECE, "", ViewId::IMPALED_HEAD, true}}, +const ResourceInfo& CollectiveConfig::getResourceInfo(CollectiveResourceId id) { + static EnumMap ret = { + {CollectiveResourceId::MANA, { none, none, ItemId::GOLD_PIECE, "mana", ViewId::MANA}}, + {CollectiveResourceId::PRISONER_HEAD, { none, none, ItemId::GOLD_PIECE, "", ViewId::IMPALED_HEAD, true}}, {CollectiveResourceId::GOLD, - {{SquareId::TREASURE_CHEST}, ItemIndex::GOLD, ItemId::GOLD_PIECE, "gold", ViewId::GOLD}}, + {FurnitureType::TREASURE_CHEST, ItemIndex::GOLD, ItemId::GOLD_PIECE, "gold", ViewId::GOLD}}, {CollectiveResourceId::WOOD, { resourceStorage, ItemIndex::WOOD, ItemId::WOOD_PLANK, "wood", ViewId::WOOD_PLANK}}, {CollectiveResourceId::IRON, { resourceStorage, ItemIndex::IRON, ItemId::IRON_ORE, "iron", ViewId::IRON_ROCK}}, {CollectiveResourceId::STONE, { resourceStorage, ItemIndex::STONE, ItemId::ROCK, "granite", ViewId::ROCK}}, {CollectiveResourceId::CORPSE, - { {SquareId::CEMETERY}, ItemIndex::REVIVABLE_CORPSE, ItemId::GOLD_PIECE, "corpses", ViewId::BODY_PART, true}} + { FurnitureType::GRAVE, ItemIndex::REVIVABLE_CORPSE, ItemId::GOLD_PIECE, "corpses", ViewId::BODY_PART, true}} }; - return ret; + return ret[id]; } MinionTaskInfo::MinionTaskInfo(vector s, const string& desc, optional w, @@ -314,11 +288,15 @@ MinionTaskInfo::MinionTaskInfo(Type t, const string& desc, optional workshops { - {WorkshopType::WORKSHOP, {SquareId::WORKSHOP, MinionTask::WORKSHOP, "workshop"}}, - {WorkshopType::FORGE, {SquareId::FORGE, MinionTask::FORGE, "forge"}}, - {WorkshopType::LABORATORY, {SquareId::LABORATORY, MinionTask::LABORATORY, "laboratory"}}, - {WorkshopType::JEWELER, {SquareId::JEWELER, MinionTask::JEWELER, "jeweler"}}, + {WorkshopType::WORKSHOP, {FurnitureType::WORKSHOP, MinionTask::WORKSHOP, "workshop"}}, + {WorkshopType::FORGE, {FurnitureType::FORGE, MinionTask::FORGE, "forge"}}, + {WorkshopType::LABORATORY, {FurnitureType::LABORATORY, MinionTask::LABORATORY, "laboratory"}}, + {WorkshopType::JEWELER, {FurnitureType::JEWELER, MinionTask::JEWELER, "jeweler"}}, }; optional CollectiveConfig::getWorkshopType(MinionTask task) { @@ -333,18 +311,18 @@ optional CollectiveConfig::getWorkshopType(MinionTask task) { MinionTaskInfo CollectiveConfig::getTaskInfo(MinionTask task) const { switch (task) { - case MinionTask::TRAIN: return {{SquareId::TRAINING_ROOM}, "training", CollectiveWarning::TRAINING, 1}; - case MinionTask::SLEEP: return {{SquareId::BED}, "sleeping", CollectiveWarning::BEDS}; + case MinionTask::TRAIN: return {FurnitureType::TRAINING_DUMMY, "training"}; + case MinionTask::SLEEP: return {FurnitureType::BED, "sleeping"}; case MinionTask::EAT: return {MinionTaskInfo::EAT, "eating"}; - case MinionTask::GRAVE: return {{SquareId::GRAVE}, "sleeping", CollectiveWarning::GRAVES}; - case MinionTask::LAIR: return {{SquareId::BEAST_CAGE}, "sleeping"}; - case MinionTask::THRONE: return {{SquareId::THRONE}, "throne"}; - case MinionTask::STUDY: return {{SquareId::LIBRARY}, "studying", CollectiveWarning::LIBRARY, 1}; + case MinionTask::GRAVE: return {FurnitureType::GRAVE, "sleeping"}; + case MinionTask::LAIR: return {FurnitureType::BEAST_CAGE, "sleeping"}; + case MinionTask::THRONE: return {FurnitureType::THRONE, "throne"}; + case MinionTask::STUDY: return {FurnitureType::BOOK_SHELF, "studying"}; case MinionTask::PRISON: return {{SquareId::PRISON}, "prison", CollectiveWarning::NO_PRISON}; case MinionTask::TORTURE: return {{SquareId::TORTURE_TABLE}, "torture ordered", CollectiveWarning::TORTURE_ROOM, 0, true}; case MinionTask::CROPS: return {{SquareId::CROPS}, "crops"}; - case MinionTask::RITUAL: return {{SquareId::RITUAL_ROOM}, "rituals"}; + case MinionTask::RITUAL: return {FurnitureType::DEMON_SHRINE, "rituals"}; case MinionTask::COPULATE: return {MinionTaskInfo::COPULATE, "copulation"}; case MinionTask::CONSUME: return {MinionTaskInfo::CONSUME, "consumption"}; case MinionTask::EXPLORE: return {MinionTaskInfo::EXPLORE, "spying"}; @@ -357,7 +335,7 @@ MinionTaskInfo CollectiveConfig::getTaskInfo(MinionTask task) const { case MinionTask::LABORATORY: case MinionTask::JEWELER: { auto& info = workshops[*getWorkshopType(task)]; - return MinionTaskInfo({info.squareType}, info.taskName); + return MinionTaskInfo(info.furniture, info.taskName); } } return getTaskInfo(task); diff --git a/collective_config.h b/collective_config.h index 732ff8039..0b7cf0d81 100644 --- a/collective_config.h +++ b/collective_config.h @@ -27,12 +27,12 @@ class Game; class Workshops; enum class AttractionId { - SQUARE, + FURNITURE, ITEM_INDEX, }; -class MinionAttraction : public EnumVariant { using EnumVariant::EnumVariant; }; @@ -99,13 +99,12 @@ struct GuardianInfo { }; struct DormInfo { - SquareType dormType; - optional getBedType() const; + FurnitureType bedType; optional warning; }; struct ResourceInfo { - vector storageType; + optional storageType; optional itemIndex; ItemId itemId; string name; @@ -114,11 +113,13 @@ struct ResourceInfo { }; struct MinionTaskInfo { - enum Type { APPLY_SQUARE, EXPLORE, COPULATE, CONSUME, EAT, SPIDER } type; + enum Type { APPLY_SQUARE, FURNITURE, EXPLORE, COPULATE, CONSUME, EAT, SPIDER } type; MinionTaskInfo(vector, const string& description, optional = none, double cost = 0, bool centerOnly = false); + MinionTaskInfo(FurnitureType, const string& description); MinionTaskInfo(Type, const string& description, optional = none); vector squares; + FurnitureType furniture; string description; optional warning; double cost = 0; @@ -126,7 +127,7 @@ struct MinionTaskInfo { }; struct WorkshopInfo { - SquareId squareType; + FurnitureType furniture; MinionTask minionTask; string taskName; }; @@ -171,12 +172,12 @@ class CollectiveConfig { const EnumMap& getDormInfo() const; static optional getSecondarySquare(SquareType); unordered_set getEfficiencySquares() const; - vector getRoomsNeedingLight() const; + const vector& getRoomsNeedingLight() const; int getTaskDuration(const Creature*, MinionTask) const; - static const map& getResourceInfo(); + static const ResourceInfo& getResourceInfo(CollectiveResourceId); MinionTaskInfo getTaskInfo(MinionTask) const; - static const vector& getEquipmentStorage(); - static const vector& getResourceStorage(); + static const FurnitureType& getEquipmentStorage(); + static const FurnitureType& getResourceStorage(); SERIALIZATION_DECL(CollectiveConfig); diff --git a/collective_control.h b/collective_control.h index c9e4146b8..efee88bba 100644 --- a/collective_control.h +++ b/collective_control.h @@ -21,6 +21,7 @@ class CollectiveControl { virtual void addMessage(const PlayerMessage&) {} virtual void addAttack(const CollectiveAttack&) {} virtual void onConstructed(Position, const SquareType&) {} + virtual void onConstructed(Position, FurnitureType) {}; virtual void onNoEnemies() {} virtual void onRansomPaid() {} virtual vector getTriggers(const Collective* against) const; diff --git a/construction_map.cpp b/construction_map.cpp index 95a680dad..0467661ae 100644 --- a/construction_map.cpp +++ b/construction_map.cpp @@ -4,6 +4,99 @@ #include "trigger.h" #include "tribe.h" +SERIALIZATION_CONSTRUCTOR_IMPL2(ConstructionMap::FurnitureInfo, FurnitureInfo); + +SERIALIZE_DEF(ConstructionMap::FurnitureInfo, cost, built, type, task); + +ConstructionMap::FurnitureInfo::FurnitureInfo(FurnitureType t, CostInfo c) : cost(c), type(t) { +} + +void ConstructionMap::FurnitureInfo::setBuilt() { + built = true; + task = none; +} + +void ConstructionMap::FurnitureInfo::reset() { + built = false; + task = none; +} + +void ConstructionMap::FurnitureInfo::setTask(UniqueEntity::Id id) { + task = id; +} + +CostInfo ConstructionMap::FurnitureInfo::getCost() const { + return cost; +} + +bool ConstructionMap::FurnitureInfo::isBuilt() const { + return built; +} + +bool ConstructionMap::FurnitureInfo::hasTask() const { + return !!task; +} + +UniqueEntity::Id ConstructionMap::FurnitureInfo::getTask() const { + return *task; +} + +const FurnitureType& ConstructionMap::FurnitureInfo::getFurnitureType() const { + return type; +} + +const ConstructionMap::FurnitureInfo& ConstructionMap::getFurniture(Position pos) const { + return furniture.at(pos); +} + +ConstructionMap::FurnitureInfo& ConstructionMap::getFurniture(Position pos) { + return furniture.at(pos); +} + +void ConstructionMap::removeFurniture(Position pos) { + auto type = furniture.at(pos).getFurnitureType(); + --furnitureCounts[type]; + furniturePositions[type].erase(pos); + furniture.erase(pos); + removeElement(allFurniture, pos); + pos.setNeedsRenderUpdate(true); +} + +void ConstructionMap::onFurnitureDestroyed(Position pos) { + getFurniture(pos).reset(); +} + +void ConstructionMap::addFurniture(Position pos, const FurnitureInfo& info) { + CHECK(!furniture.count(pos)); + allFurniture.push_back(pos); + furniture.emplace(pos, info); + ++furnitureCounts[info.getFurnitureType()]; + pos.setNeedsRenderUpdate(true); +} + +bool ConstructionMap::containsFurniture(Position pos) const { + return furniture.count(pos); +} + +int ConstructionMap::getFurnitureCount(FurnitureType type) const { + return furnitureCounts[type]; +} + +const set& ConstructionMap::getFurniturePositions(FurnitureType type) const { + return furniturePositions[type]; +} + +const vector& ConstructionMap::getAllFurniture() const { + return allFurniture; +} + +void ConstructionMap::onConstructed(Position pos, FurnitureType type) { + furniturePositions[type].insert(pos); + if (furniture.count(pos)) + furniture.at(pos).setBuilt(); +} + + const ConstructionMap::SquareInfo& ConstructionMap::getSquare(Position pos) const { for (auto& info : squares.at(pos)) if (!info.isBuilt()) @@ -235,9 +328,11 @@ SERIALIZATION_CONSTRUCTOR_IMPL2(ConstructionMap::TorchInfo, TorchInfo); template void ConstructionMap::serialize(Archive& ar, const unsigned int version) { - serializeAll(ar, squares, typeCounts, traps, torches); - if (Archive::is_loading::value) + serializeAll(ar, squares, typeCounts, traps, torches, furniture, furniturePositions, furnitureCounts); + if (Archive::is_loading::value) { squarePos = getKeys(squares); + allFurniture = getKeys(furniture); + } } SERIALIZABLE(ConstructionMap); diff --git a/construction_map.h b/construction_map.h index 204f9b535..bd560e29f 100644 --- a/construction_map.h +++ b/construction_map.h @@ -6,6 +6,7 @@ #include "square_type.h" #include "unique_entity.h" #include "position.h" +#include "furniture_type.h" class ConstructionMap { public: @@ -31,6 +32,27 @@ class ConstructionMap { optional::Id> SERIAL(task); }; + class FurnitureInfo { + public: + FurnitureInfo(FurnitureType, CostInfo); + void setBuilt(); + void reset(); + void setTask(UniqueEntity::Id); + CostInfo getCost() const; + bool isBuilt() const; + UniqueEntity::Id getTask() const; + bool hasTask() const; + const FurnitureType& getFurnitureType() const; + + SERIALIZATION_DECL(FurnitureInfo); + + private: + CostInfo SERIAL(cost); + bool SERIAL(built) = false; + FurnitureType SERIAL(type); + optional::Id> SERIAL(task); + }; + class TrapInfo { public: TrapInfo(TrapType); @@ -77,6 +99,16 @@ class ConstructionMap { bool containsSquare(Position) const; int getSquareCount(SquareType) const; + const FurnitureInfo& getFurniture(Position) const; + FurnitureInfo& getFurniture(Position); + void removeFurniture(Position); + void onFurnitureDestroyed(Position); + void addFurniture(Position, const FurnitureInfo&); + bool containsFurniture(Position) const; + int getFurnitureCount(FurnitureType) const; + const set& getFurniturePositions(FurnitureType) const; + void onConstructed(Position, FurnitureType); + const TrapInfo& getTrap(Position) const; TrapInfo& getTrap(Position); void removeTrap(Position); @@ -89,6 +121,7 @@ class ConstructionMap { void addTorch(Position, const TorchInfo&); bool containsTorch(Position) const; const vector& getSquares() const; + const vector& getAllFurniture() const; const map& getTraps() const; const map& getTorches() const; @@ -97,7 +130,11 @@ class ConstructionMap { private: map> SERIAL(squares); + map SERIAL(furniture); + EnumMap> SERIAL(furniturePositions); + EnumMap SERIAL(furnitureCounts); vector squarePos; + vector allFurniture; unordered_map SERIAL(typeCounts); map SERIAL(traps); map SERIAL(torches); diff --git a/creature.cpp b/creature.cpp index 4d4df7100..933861401 100644 --- a/creature.cpp +++ b/creature.cpp @@ -49,6 +49,7 @@ #include "spell.h" #include "body.h" #include "field_of_view.h" +#include "furniture.h" template void Creature::MoraleOverride::serialize(Archive& ar, const unsigned int version) { @@ -620,15 +621,16 @@ CreatureAction Creature::bumpInto(Vec2 direction) const { return CreatureAction(); } -CreatureAction Creature::applySquare() const { - if (getPosition().getApplyType(this)) +CreatureAction Creature::applySquare(Position pos) const { + CHECK(pos.dist8(getPosition()) <= 1); +// if (pos.getApplyType(this)) return CreatureAction(this, [=](Creature* self) { Debug() << getName().the() << " applying " << getPosition().getName(); - self->getPosition().apply(self); + pos.apply(self); self->spendTime(self->getPosition().getApplyTime()); }); - else - return CreatureAction(); +// else +// return CreatureAction(); } CreatureAction Creature::hide() const { @@ -1357,10 +1359,25 @@ CreatureAction Creature::construct(Vec2 direction, const SquareType& type) const return CreatureAction(); } +CreatureAction Creature::construct(Vec2 direction, FurnitureType type) const { + if (getPosition().plus(direction).canConstruct(type) && canConstruct(type)) + return CreatureAction(this, [=](Creature* self) { + addSound(Sound(SoundId::DIGGING).setPitch(0.5)); + if (getPosition().plus(direction).construct(type, self)) { + } + self->spendTime(1); + }); + return CreatureAction(); +} + bool Creature::canConstruct(const SquareType& type) const { return attributes->getSkills().hasDiscrete(SkillId::CONSTRUCTION); } +bool Creature::canConstruct(FurnitureType type) const { + return attributes->getSkills().hasDiscrete(SkillId::CONSTRUCTION); +} + CreatureAction Creature::eat(Item* item) const { return CreatureAction(this, [=](Creature* self) { monsterMessage(getName().the() + " eats " + item->getAName()); @@ -1579,7 +1596,7 @@ CreatureAction Creature::moveTowards(Position pos, bool stepOnTile) { return moveTowards(pos, false, stepOnTile); else if (auto stairs = position.getStairsTo(pos)) { if (stairs == position) - return applySquare(); + return applySquare(position); else return moveTowards(*stairs, false, true); } else diff --git a/creature.h b/creature.h index b67856410..39f258f18 100644 --- a/creature.h +++ b/creature.h @@ -172,7 +172,7 @@ class Creature : public Renderable, public UniqueEntity { bool canEquip(const Item* item) const; CreatureAction throwItem(Item*, Vec2 direction) const; CreatureAction heal(Vec2 direction) const; - CreatureAction applySquare() const; + CreatureAction applySquare(Position) const; CreatureAction hide() const; bool isHidden() const; bool knowsHiding(const Creature*) const; @@ -184,9 +184,11 @@ class Creature : public Renderable, public UniqueEntity { CreatureAction give(Creature* whom, vector items); CreatureAction fire(Vec2 direction) const; CreatureAction construct(Vec2 direction, const SquareType&) const; + CreatureAction construct(Vec2 direction, FurnitureType) const; CreatureAction placeTorch(Dir attachmentDir, function builtCallback) const; CreatureAction whip(const Position&) const; bool canConstruct(const SquareType&) const; + bool canConstruct(FurnitureType) const; CreatureAction eat(Item*) const; enum DestroyAction { BASH, EAT, DESTROY }; CreatureAction destroy(Vec2 direction, DestroyAction) const; diff --git a/enemy_factory.cpp b/enemy_factory.cpp index 811c42175..309407236 100644 --- a/enemy_factory.cpp +++ b/enemy_factory.cpp @@ -160,8 +160,8 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { CONSTRUCT(VillageBehaviour, c.minPopulation = 6; c.minTeamSize = 5; - c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, {AttackTriggerId::SELF_VICTIMS}, - AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}, + c.triggers = LIST({AttackTriggerId::ROOM_BUILT, FurnitureType::THRONE}, {AttackTriggerId::SELF_VICTIMS}, + AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF, AttackTriggerId::PROXIMITY); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER); c.ransom = make_pair(0.8, random.get(500, 700));)); @@ -192,8 +192,8 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { CONSTRUCT(VillageBehaviour, c.minPopulation = 12; c.minTeamSize = 10; - c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, {AttackTriggerId::SELF_VICTIMS}, - AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}, + c.triggers = LIST({AttackTriggerId::ROOM_BUILT, FurnitureType::THRONE}, {AttackTriggerId::SELF_VICTIMS}, + AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF, AttackTriggerId::PROXIMITY); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER); c.ransom = make_pair(0.9, random.get(1400, 2000));), @@ -266,8 +266,8 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { CONSTRUCT(VillageBehaviour, c.minPopulation = 3; c.minTeamSize = 4; - c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, {AttackTriggerId::SELF_VICTIMS}, - AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}, + c.triggers = LIST({AttackTriggerId::ROOM_BUILT, FurnitureType::THRONE}, {AttackTriggerId::SELF_VICTIMS}, + AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF, AttackTriggerId::PROXIMITY); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_MEMBERS, 3); c.ransom = make_pair(0.8, random.get(1200, 1600));)); @@ -314,8 +314,8 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { CONSTRUCT(VillageBehaviour, c.minPopulation = 0; c.minTeamSize = 1; - c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, AttackTriggerId::PROXIMITY, - {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF); + c.triggers = LIST({AttackTriggerId::ROOM_BUILT, FurnitureType::THRONE}, AttackTriggerId::PROXIMITY, + {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::CAMP_AND_SPAWN, CreatureFactory::elementals(TribeId::getHuman())); c.ransom = make_pair(0.5, random.get(200, 400));), @@ -364,7 +364,7 @@ EnemyInfo EnemyFactory::get(EnemyId enemyId) { c.minPopulation = 4; c.minTeamSize = 4; c.triggers = LIST({AttackTriggerId::POWER}, {AttackTriggerId::SELF_VICTIMS}, - AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}, + AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}, AttackTriggerId::FINISH_OFF, AttackTriggerId::PROXIMITY); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER);)); case EnemyId::DARK_ELVES: diff --git a/enums.h b/enums.h index 56f267476..7b91ecd84 100644 --- a/enums.h +++ b/enums.h @@ -46,6 +46,7 @@ enum class CreatureSize; enum class SquareApplyType; enum class SquareId; +enum class FurnitureType; enum class ItemAction; enum class WorshipType; enum class SquareInteraction; diff --git a/furniture.cpp b/furniture.cpp new file mode 100644 index 000000000..0f8b35deb --- /dev/null +++ b/furniture.cpp @@ -0,0 +1,34 @@ +#include "stdafx.h" +#include "furniture.h" +#include "player_message.h" + +Furniture::Furniture(const string& n, const ViewObject& o, Type t) : Renderable(o), name(n), type(t) {} + +SERIALIZATION_CONSTRUCTOR_IMPL(Furniture); + +template +void Furniture::serialize(Archive& ar, const unsigned) { + ar & SUBCLASS(Renderable); + serializeAll(ar, name, type); +} + +SERIALIZABLE(Furniture); + +void Furniture::apply(Position, Creature*) { +} + +const string& Furniture::getName() const { + return name; +} + +bool Furniture::isWalkable() const { + return type == NON_BLOCKING; +} + +double Furniture::getApplyTime() const { + return 1; +} + +void Furniture::onDestroy(Position pos) { + pos.globalMessage("The " + name + " is destroyed."); +} diff --git a/furniture.h b/furniture.h new file mode 100644 index 000000000..50a2017da --- /dev/null +++ b/furniture.h @@ -0,0 +1,24 @@ +#pragma once + +#include "position.h" +#include "renderable.h" + +class Creature; +class MovementType; + +class Furniture : public Renderable { + public: + enum Type { BLOCKING, NON_BLOCKING }; + Furniture(const string& name, const ViewObject&, Type); + void apply(Position, Creature*); + double getApplyTime() const; + const string& getName() const; + bool isWalkable() const; + void onDestroy(Position); + + SERIALIZATION_DECL(Furniture); + + private: + string SERIAL(name); + Type SERIAL(type); +}; diff --git a/furniture_factory.cpp b/furniture_factory.cpp new file mode 100644 index 000000000..d06760258 --- /dev/null +++ b/furniture_factory.cpp @@ -0,0 +1,61 @@ +#include "stdafx.h" +#include "furniture_factory.h" +#include "furniture.h" +#include "furniture_type.h" +#include "view_id.h" +#include "view_layer.h" +#include "view_object.h" + +static Furniture* get(FurnitureType type) { + switch (type) { + case FurnitureType::TRAINING_DUMMY: + return new Furniture("Training dummy", ViewObject(ViewId::TRAINING_ROOM, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::WORKSHOP: + return new Furniture("Workshop", ViewObject(ViewId::WORKSHOP, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::FORGE: + return new Furniture("Forge", ViewObject(ViewId::FORGE, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::LABORATORY: + return new Furniture("Laboratory", ViewObject(ViewId::LABORATORY, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::JEWELER: + return new Furniture("Jeweler", ViewObject(ViewId::JEWELER, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::BOOK_SHELF: + return new Furniture("Book shelf", ViewObject(ViewId::LIBRARY, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::THRONE: + return new Furniture("Throne", ViewObject(ViewId::THRONE, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::IMPALED_HEAD: + return new Furniture("Impaled head", ViewObject(ViewId::IMPALED_HEAD, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::BEAST_CAGE: + return new Furniture("Beast cage", ViewObject(ViewId::BEAST_CAGE, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::BED: + return new Furniture("Bed", ViewObject(ViewId::BED, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::GRAVE: + return new Furniture("Grave", ViewObject(ViewId::GRAVE, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::DEMON_SHRINE: + return new Furniture("Demon shrine", ViewObject(ViewId::RITUAL_ROOM, ViewLayer::FLOOR), + Furniture::BLOCKING); + case FurnitureType::STOCKPILE_RES: + return new Furniture("Resource stockpile", ViewObject(ViewId::STOCKPILE1, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::STOCKPILE_EQUIP: + return new Furniture("Equipment stockpile", ViewObject(ViewId::STOCKPILE2, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + case FurnitureType::TREASURE_CHEST: + return new Furniture("Treasure chest", ViewObject(ViewId::TREASURE_CHEST, ViewLayer::FLOOR), + Furniture::NON_BLOCKING); + } +} + +PFurniture FurnitureFactory::get(FurnitureType type) { + return PFurniture(::get(type)); +} diff --git a/furniture_factory.h b/furniture_factory.h new file mode 100644 index 000000000..f84cb08ba --- /dev/null +++ b/furniture_factory.h @@ -0,0 +1,6 @@ +#pragma once + +class FurnitureFactory { + public: + static PFurniture get(FurnitureType); +}; diff --git a/furniture_type.h b/furniture_type.h new file mode 100644 index 000000000..4cac32275 --- /dev/null +++ b/furniture_type.h @@ -0,0 +1,19 @@ +#pragma once + +RICH_ENUM(FurnitureType, + TRAINING_DUMMY, + WORKSHOP, + FORGE, + LABORATORY, + JEWELER, + BOOK_SHELF, + THRONE, + DEMON_SHRINE, + BEAST_CAGE, + GRAVE, + BED, + IMPALED_HEAD, + TREASURE_CHEST, + STOCKPILE_EQUIP, + STOCKPILE_RES +); diff --git a/game.cpp b/game.cpp index f77576a63..acfcf788c 100644 --- a/game.cpp +++ b/game.cpp @@ -29,6 +29,7 @@ #include "attack_trigger.h" #include "view_object.h" #include "campaign.h" +#include "construction_map.h" template void Game::serialize(Archive& ar, const unsigned int version) { @@ -161,7 +162,9 @@ void Game::prepareSiteRetirement() { } playerCollective->setVillainType(VillainType::MAIN); playerCollective->limitKnownTilesToModel(); - vector locationPos = playerCollective->getAllSquares({SquareId::LIBRARY}); + set locationPosTmp = + playerCollective->getConstructions().getFurniturePositions(FurnitureType::BOOK_SHELF); + vector locationPos(locationPosTmp.begin(), locationPosTmp.end()); if (locationPos.empty()) locationPos = playerCollective->getTerritory().getAll(); if (!locationPos.empty()) @@ -173,8 +176,8 @@ void Game::prepareSiteRetirement() { new VillageControl(playerCollective, CONSTRUCT(VillageBehaviour, c.minPopulation = 6; c.minTeamSize = 5; - c.triggers = LIST({AttackTriggerId::ROOM_BUILT, SquareId::THRONE}, {AttackTriggerId::SELF_VICTIMS}, - AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, SquareId::IMPALED_HEAD}); + c.triggers = LIST({AttackTriggerId::ROOM_BUILT, FurnitureType::THRONE}, {AttackTriggerId::SELF_VICTIMS}, + AttackTriggerId::STOLEN_ITEMS, {AttackTriggerId::ROOM_BUILT, FurnitureType::IMPALED_HEAD}); c.attackBehaviour = AttackBehaviour(AttackBehaviourId::KILL_LEADER); c.ransom = make_pair(0.8, Random.get(500, 700));)))); for (Collective* col : models[baseModel]->getCollectives()) @@ -197,7 +200,9 @@ void Game::prepareSiteRetirement() { void Game::prepareSingleMapRetirement() { CHECK(isSingleModel()); playerCollective->getLevel()->clearLocations(); - vector locationPos = playerCollective->getAllSquares({SquareId::LIBRARY}); + set locationPosTmp = + playerCollective->getConstructions().getFurniturePositions(FurnitureType::BOOK_SHELF); + vector locationPos(locationPosTmp.begin(), locationPosTmp.end()); if (locationPos.empty()) locationPos = playerCollective->getTerritory().getAll(); if (!locationPos.empty()) diff --git a/level.cpp b/level.cpp index 167910b45..9e2c8620b 100644 --- a/level.cpp +++ b/level.cpp @@ -37,12 +37,14 @@ #include "square_array.h" #include "view_object.h" #include "field_of_view.h" +#include "furniture.h" template void Level::serialize(Archive& ar, const unsigned int version) { serializeAll(ar, squares, oldSquares, landingSquares, locations, tickingSquares, creatures, model, fieldOfView); serializeAll(ar, name, backgroundLevel, backgroundOffset, sunlight, bucketMap, sectors, lightAmount, unavailable); serializeAll(ar, levelId, noDiagonalPassing, lightCapAmount, creatureIds, background, memoryUpdates); + serializeAll(ar, furnitureInfo); } SERIALIZABLE(Level); @@ -53,8 +55,8 @@ Level::~Level() {} Level::Level(SquareArray s, Model* m, vector l, const string& n, Table sun, LevelId id) - : squares(std::move(s)), oldSquares(squares->getBounds()), memoryUpdates(squares->getBounds(), true), - locations(l), model(m), + : squares(std::move(s)), oldSquares(squares->getBounds()), furnitureInfo(squares->getBounds()), + memoryUpdates(squares->getBounds(), true), locations(l), model(m), name(n), sunlight(sun), bucketMap(squares->getBounds().width(), squares->getBounds().height(), FieldOfView::sightRange), lightAmount(squares->getBounds(), 0), lightCapAmount(squares->getBounds(), 1), levelId(id) { @@ -476,25 +478,8 @@ bool Level::playerCanSee(const Creature* c) const { return false; } -static bool canPass(const Square* square, const Creature* c) { - return square->canEnterEmpty(c) && (!square->getCreature() || - !square->getCreature()->getAttributes().isStationary()); -} - -bool Level::canMoveCreature(const Creature* creature, Vec2 direction) const { - Vec2 position = creature->getPosition().getCoord(); - Vec2 destination = position + direction; - if (!inBounds(destination) || unavailable[destination]) - return false; - if (noDiagonalPassing && direction.isCardinal8() && !direction.isCardinal4() && - !canPass(getSafeSquare(position + Vec2(direction.x, 0)), creature) && - !canPass(getSafeSquare(position + Vec2(0, direction.y)), creature)) - return false; - return getSafeSquare(destination)->canEnter(creature); -} - void Level::moveCreature(Creature* creature, Vec2 direction) { - CHECK(canMoveCreature(creature, direction)); +// CHECK(canMoveCreature(creature, direction)); Vec2 position = creature->getPosition().getCoord(); unplaceCreature(creature, position); placeCreature(creature, position + direction); diff --git a/level.h b/level.h index e043d3fe2..e846c568f 100644 --- a/level.h +++ b/level.h @@ -57,10 +57,6 @@ class Level { static Rectangle getSplashBounds(); static Rectangle getSplashVisibleBounds(); - /** Checks if the creature can move to \paramname{direction}. This ensures - * that a subsequent call to #moveCreature will not fail.*/ - bool canMoveCreature(const Creature*, Vec2 direction) const; - /** Moves the creature. Updates the creature's position.*/ void moveCreature(Creature*, Vec2 direction); @@ -231,6 +227,17 @@ class Level { Vec2 transform(Vec2); HeapAllocated SERIAL(squares); Table SERIAL(oldSquares); + struct FurnitureInfo { + PFurniture SERIAL(furniture); + struct FurnitureConstruction { + FurnitureType SERIAL(type); + int SERIAL(time); + SERIALIZE_ALL(type, time); + }; + optional SERIAL(construction); + SERIALIZE_ALL(furniture, construction); + }; + Table SERIAL(furnitureInfo); HeapAllocated>> SERIAL(background); Table SERIAL(memoryUpdates); Table renderUpdates = Table(getMaxBounds(), true); diff --git a/level_maker.cpp b/level_maker.cpp index 6533f5ad1..44e089fd2 100644 --- a/level_maker.cpp +++ b/level_maker.cpp @@ -28,6 +28,7 @@ #include "model.h" #include "monster_ai.h" #include "item.h" +#include "view_id.h" namespace { @@ -1590,11 +1591,7 @@ class AddMapBorder : public LevelMaker { } static MakerQueue* stockpileMaker(StockpileInfo info) { - SquareId square = SquareId(0); - switch (info.type) { - case StockpileInfo::GOLD: square = SquareId::TREASURE_CHEST; break; - case StockpileInfo::MINERALS: square = SquareId::STOCKPILE_RES; break; - } + SquareType square {SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::STOCKPILE1, "stockpile"}}; ItemFactory items; switch (info.type) { case StockpileInfo::GOLD: items = ItemFactory::singleType(ItemId::GOLD_PIECE); break; diff --git a/map_gui.cpp b/map_gui.cpp index dbcf13253..2be321763 100644 --- a/map_gui.cpp +++ b/map_gui.cpp @@ -473,7 +473,7 @@ void MapGui::drawObjectAbs(Renderer& renderer, Vec2 pos, const ViewObject& objec Vec2 movement = getMovementOffset(object, size, currentTimeGame, curTimeReal); drawCreatureHighlights(renderer, object, pos + movement, size, curTimeReal); if ((object.layer() == ViewLayer::CREATURE && object.id() != ViewId::BOULDER) - || object.hasModifier(ViewObject::Modifier::ROUND_SHADOW)) { + || tile.roundShadow) { static auto coord = renderer.getTileCoord("round_shadow"); renderer.drawTile(pos + movement, coord, size, Color(255, 255, 255, 160)); move.y = -4* size.y / renderer.getNominalSize().y; @@ -505,8 +505,7 @@ void MapGui::drawObjectAbs(Renderer& renderer, Vec2 pos, const ViewObject& objec /* if (tile.floorBorders) { drawFloorBorders(renderer, borderDirs, x, y); }*/ - if ((object.layer() == ViewLayer::FLOOR || object.layer() == ViewLayer::FLOOR_BACKGROUND) && - shadowed.count(tilePos) && !tile.noShadow) + if (object.layer() == ViewLayer::FLOOR_BACKGROUND && shadowed.count(tilePos)) renderer.drawTile(pos, shortShadow, size, Color(255, 255, 255, 170)); if (auto burningVal = object.getAttribute(ViewObject::Attribute::BURNING)) if (*burningVal > 0) { diff --git a/model_builder.cpp b/model_builder.cpp index fba89ca98..35981d803 100644 --- a/model_builder.cpp +++ b/model_builder.cpp @@ -72,9 +72,9 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::GOBLIN; c.frequency = 1; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::WORKSHOP}, 1.0, 12.0}, - {{AttractionId::SQUARE, SquareId::JEWELER}, 1.0, 9.0}, - {{AttractionId::SQUARE, SquareId::FORGE}, 1.0, 9.0}, + {{AttractionId::FURNITURE, FurnitureType::WORKSHOP}, 0.33, 4.0}, + {{AttractionId::FURNITURE, FurnitureType::JEWELER}, 0.33, 3.0}, + {{AttractionId::FURNITURE, FurnitureType::FORGE}, 0.33, 3.0}, ); c.traits = LIST(MinionTrait::FIGHTER, MinionTrait::NO_EQUIPMENT); c.salary = 10;), @@ -82,7 +82,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::ORC; c.frequency = 0.7; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 1.0, 12.0}, + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 0.33, 4.0}, ); c.traits = {MinionTrait::FIGHTER}; c.salary = 20;), @@ -90,8 +90,8 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::ORC_SHAMAN; c.frequency = 0.10; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::LIBRARY}, 1.0, 16.0}, - {{AttractionId::SQUARE, SquareId::LABORATORY}, 1.0, 9.0}, + {{AttractionId::FURNITURE, FurnitureType::BOOK_SHELF}, 0.33, 5.0}, + {{AttractionId::FURNITURE, FurnitureType::LABORATORY}, 0.33, 3.0}, ); c.traits = {MinionTrait::FIGHTER}; c.salary = 20;), @@ -99,7 +99,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::OGRE; c.frequency = 0.3; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 3.0, 16.0} + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 1.0, 5.0} ); c.traits = {MinionTrait::FIGHTER}; c.salary = 40;), @@ -107,7 +107,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::HARPY; c.frequency = 0.3; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 3.0, 16.0}, + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 1.0, 5.0}, {{AttractionId::ITEM_INDEX, ItemIndex::RANGED_WEAPON}, 1.0, 3.0, true} ); c.traits = {MinionTrait::FIGHTER}; @@ -116,7 +116,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.id = CreatureId::SPECIAL_HUMANOID; c.frequency = 0.1; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 3.0, 16.0}, + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 3.0, 16.0}, ); c.traits = {MinionTrait::FIGHTER}; c.spawnAtDorm = true; @@ -135,7 +135,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.salary = 40; c.spawnAtDorm = true; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 2.0, 12.0} + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 0.66, 4.0} );), CONSTRUCT(ImmigrantInfo, c.id = CreatureId::LOST_SOUL; @@ -143,7 +143,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.traits = {MinionTrait::FIGHTER}; c.salary = 0; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::RITUAL_ROOM}, 1.0, 9.0} + {{AttractionId::FURNITURE, FurnitureType::DEMON_SHRINE}, 0.66, 3.0} ); c.spawnAtDorm = true;), CONSTRUCT(ImmigrantInfo, @@ -152,7 +152,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.traits = LIST(MinionTrait::FIGHTER, MinionTrait::NO_EQUIPMENT); c.salary = 0; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::RITUAL_ROOM}, 2.0, 12.0} + {{AttractionId::FURNITURE, FurnitureType::DEMON_SHRINE}, 0.66, 3.0} ); c.spawnAtDorm = true;), CONSTRUCT(ImmigrantInfo, @@ -161,7 +161,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.traits = {MinionTrait::FIGHTER}; c.salary = 0; c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::RITUAL_ROOM}, 4.0, 12.0} + {{AttractionId::FURNITURE, FurnitureType::DEMON_SHRINE}, 1.33, 3.0} ); c.spawnAtDorm = true;), CONSTRUCT(ImmigrantInfo, @@ -193,7 +193,7 @@ static CollectiveConfig getKeeperConfig(bool fastImmigration) { c.frequency = 0.1; c.traits = LIST(MinionTrait::FIGHTER, MinionTrait::NO_RETURNING); c.attractions = LIST( - {{AttractionId::SQUARE, SquareId::TRAINING_ROOM}, 4.0, 12.0} + {{AttractionId::FURNITURE, FurnitureType::TRAINING_DUMMY}, 1.33, 4.0} ); c.salary = 0;), /*CONSTRUCT(ImmigrantInfo, diff --git a/player.cpp b/player.cpp index 64af836c8..d7db53c6f 100644 --- a/player.cpp +++ b/player.cpp @@ -176,7 +176,7 @@ void Player::pickUpItemAction(int numStack, bool multi) { if (getUsableSquareApplyType()) { --numStack; if (numStack == -1) { - getCreature()->applySquare().perform(getCreature()); + getCreature()->applySquare(getCreature()->getPosition()).perform(getCreature()); return; } } diff --git a/player_control.cpp b/player_control.cpp index 05b8c1809..bd07cb2b6 100644 --- a/player_control.cpp +++ b/player_control.cpp @@ -65,6 +65,9 @@ #include "attack_trigger.h" #include "view_object.h" #include "body.h" +#include "furniture.h" +#include "furniture_type.h" +#include "furniture_factory.h" template void PlayerControl::serialize(Archive& ar, const unsigned int version) { @@ -92,6 +95,15 @@ struct PlayerControl::BuildInfo { optional maxNumber; } squareInfo; + struct FurnitureInfo { + FurnitureType type; + CostInfo cost; + string name; + bool buildImmediatly; + bool noCredit; + optional maxNumber; + } furnitureInfo; + struct TrapInfo { TrapType type; string name; @@ -101,6 +113,7 @@ struct PlayerControl::BuildInfo { enum BuildType { DIG, SQUARE, + FURNITURE, IMP, TRAP, DESTROY, @@ -121,6 +134,10 @@ struct PlayerControl::BuildInfo { bool hotkeyOpens = false) : squareInfo(info), buildType(SQUARE), requirements(req), help(h), hotkey(key), groupName(group), hotkeyOpensGroup(hotkeyOpens) {} + BuildInfo(FurnitureInfo info, vector req = {}, const string& h = "", char key = 0, string group = "", + bool hotkeyOpens = false) : furnitureInfo(info), buildType(FURNITURE), requirements(req), help(h), hotkey(key), + groupName(group), hotkeyOpensGroup(hotkeyOpens) {} + BuildInfo(TrapInfo info, vector = {}, const string& h = "", char hotkey = 0, string group = ""); BuildInfo(const Creature*, CostInfo, const string& groupName, const string& h = "", char hotkey = 0); @@ -150,11 +167,9 @@ vector PlayerControl::getBuildInfo(TribeId tribe) { BuildInfo(BuildInfo::DIG, "", 'd'), BuildInfo({SquareId::MOUNTAIN, {ResourceId::STONE, 50}, "Fill up tunnel"}, {}, "Fill up one tile at a time. Cutting off an area is not allowed."), - BuildInfo({SquareId::STOCKPILE, {ResourceId::GOLD, 0}, "Everything", true}, {}, - "All possible items in your dungeon can be stored here.", 's', "Storage", true), - BuildInfo({SquareId::STOCKPILE_EQUIP, {ResourceId::GOLD, 0}, "Equipment", true}, {}, + BuildInfo({FurnitureType::STOCKPILE_EQUIP, {ResourceId::GOLD, 0}, "Equipment", true}, {}, "All equipment for your minions can be stored here.", 0, "Storage"), - BuildInfo({SquareId::STOCKPILE_RES, {ResourceId::GOLD, 0}, "Resources", true}, {}, + BuildInfo({FurnitureType::STOCKPILE_RES, {ResourceId::GOLD, 0}, "Resources", true}, {}, "Only wood, iron and granite can be stored here.", 0, "Storage"), BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::WOOD_FLOOR1, "wooden floor"}}, {ResourceId::WOOD, 10}, "Wooden"}, {}, "Wooden floor.", 0, "Floors"), @@ -168,36 +183,36 @@ vector PlayerControl::getBuildInfo(TribeId tribe) { {ResourceId::GOLD, 10}, "Carpet"}, {}, "Carpet.", 0, "Floors"), BuildInfo({SquareType{SquareId::CUSTOM_FLOOR, CustomFloorInfo{ViewId::CARPET_FLOOR2, "carpet floor"}}, {ResourceId::GOLD, 10}, "Carpet"}, {}, "Carpet.", 0, "Floors"), - BuildInfo({SquareId::LIBRARY, {ResourceId::WOOD, 20}, "Library"}, {}, + BuildInfo({FurnitureType::BOOK_SHELF, {ResourceId::WOOD, 20}, "Library"}, {}, "Mana is regenerated here.", 'y'), - BuildInfo({SquareId::THRONE, {ResourceId::GOLD, 800}, "Throne", false, false, 0, 1}, + BuildInfo({FurnitureType::THRONE, {ResourceId::GOLD, 800}, "Throne", false, false, 1}, {{RequirementId::VILLAGE_CONQUERED}}, "Increases population limit by " + toString(ModelBuilder::getThronePopulationIncrease())), - BuildInfo({SquareId::TREASURE_CHEST, {ResourceId::WOOD, 5}, "Treasure room"}, {}, + BuildInfo({FurnitureType::TREASURE_CHEST, {ResourceId::WOOD, 5}, "Treasure room"}, {}, "Stores gold."), BuildInfo({Collective::getHatcheryType(tribe), {ResourceId::WOOD, 20}, "Pigsty"}, {{RequirementId::TECHNOLOGY, TechId::PIGSTY}}, "Increases minion population limit by up to " + toString(ModelBuilder::getPigstyPopulationIncrease()) + ".", 'p'), - BuildInfo({SquareId::DORM, {ResourceId::WOOD, 10}, "Dormitory"}, {}, - "Humanoid minions place their beds here.", 'm'), - BuildInfo({SquareId::TRAINING_ROOM, {ResourceId::IRON, 20}, "Training room"}, {}, + BuildInfo({FurnitureType::BED, {ResourceId::WOOD, 10}, "Bed"}, {}, + "Humanoid minions sleep here.", 'm'), + BuildInfo({FurnitureType::TRAINING_DUMMY, {ResourceId::IRON, 20}, "Training room"}, {}, "Used to level up your minions.", 't'), - BuildInfo({SquareId::WORKSHOP, {ResourceId::WOOD, 20}, "Workshop"}, + BuildInfo({FurnitureType::WORKSHOP, {ResourceId::WOOD, 20}, "Workshop"}, {{RequirementId::TECHNOLOGY, TechId::CRAFTING}}, "Produces leather equipment, traps, first-aid kits and other.", 'w', workshop), - BuildInfo({SquareId::FORGE, {ResourceId::IRON, 15}, "Forge"}, + BuildInfo({FurnitureType::FORGE, {ResourceId::IRON, 15}, "Forge"}, {{RequirementId::TECHNOLOGY, TechId::IRON_WORKING}}, "Produces iron weapons and armor.", 'f', workshop), - BuildInfo({SquareId::LABORATORY, {ResourceId::STONE, 15}, "Laboratory"}, + BuildInfo({FurnitureType::LABORATORY, {ResourceId::STONE, 15}, "Laboratory"}, {{RequirementId::TECHNOLOGY, TechId::ALCHEMY}}, "Produces magical potions.", 'r', workshop), - BuildInfo({SquareId::JEWELER, {ResourceId::WOOD, 20}, "Jeweler"}, + BuildInfo({FurnitureType::JEWELER, {ResourceId::WOOD, 20}, "Jeweler"}, {{RequirementId::TECHNOLOGY, TechId::JEWELLERY}}, "Produces magical rings and amulets.", 'j', workshop), - BuildInfo({SquareId::RITUAL_ROOM, {ResourceId::MANA, 15}, "Ritual room"}, {}, + BuildInfo({FurnitureType::DEMON_SHRINE, {ResourceId::MANA, 15}, "Ritual room"}, {}, "Summons various demons to your dungeon."), - BuildInfo({SquareId::BEAST_LAIR, {ResourceId::WOOD, 12}, "Beast lair"}, {}, - "Beasts have their cages here."), - BuildInfo({SquareId::CEMETERY, {ResourceId::STONE, 20}, "Graveyard"}, {}, - "Corpses are dragged here. Sometimes an undead creature makes their home here.", 'v'), + BuildInfo({FurnitureType::BEAST_CAGE, {ResourceId::WOOD, 12}, "Beast lair"}, {}, + "Beasts sleep here."), + BuildInfo({FurnitureType::GRAVE, {ResourceId::STONE, 20}, "Graveyard"}, {}, + "Spot for hauling dead bodies and for undead creatures to sleep in.", 'g'), BuildInfo({SquareId::PRISON, {ResourceId::IRON, 20}, "Prison"}, {}, "Captured enemies are kept here.", 0), BuildInfo({SquareId::TORTURE_TABLE, {ResourceId::IRON, 20}, "Torture room"}, {}, @@ -223,7 +238,7 @@ vector PlayerControl::getBuildInfo(TribeId tribe) { toString(ModelBuilder::getStatuePopulationIncrease()) + ".", 0, "Installations"), BuildInfo({SquareId::WHIPPING_POST, {ResourceId::WOOD, 30}, "Whipping post"}, {}, "A place to whip your minions if they need a morale boost.", 0, "Installations"), - BuildInfo({SquareId::IMPALED_HEAD, {ResourceId::PRISONER_HEAD, 1}, "Prisoner head", false, true}, {}, + BuildInfo({FurnitureType::IMPALED_HEAD, {ResourceId::PRISONER_HEAD, 1}, "Prisoner head", false, true}, {}, "Impaled head of an executed prisoner. Aggravates enemies.", 0, "Installations"), BuildInfo({TrapType::TERROR, "Terror trap", ViewId::TERROR_TRAP}, {{RequirementId::TECHNOLOGY, TechId::TRAPS}}, "Causes the trespasser to panic.", 0, "Traps"), @@ -700,13 +715,14 @@ int PlayerControl::getMinLibrarySize() const { } void PlayerControl::handleLibrary(View* view) { - if (getCollective()->getSquares(SquareId::LIBRARY).empty()) { + int libraryCount = getCollective()->getConstructions().getFurnitureCount(FurnitureType::BOOK_SHELF); + if (libraryCount == 0) { view->presentText("", "You need to build a library to start research."); return; } vector options; bool allInactive = false; - if (getCollective()->getSquares(SquareId::LIBRARY).size() <= getMinLibrarySize()) { + if (libraryCount <= getMinLibrarySize()) { allInactive = true; options.emplace_back("You need a larger library to continue research.", ListElem::TITLE); } @@ -736,7 +752,7 @@ void PlayerControl::handleLibrary(View* view) { typedef CollectiveInfo::Button Button; static optional> getCostObj(CostInfo cost) { - auto& resourceInfo = CollectiveConfig::getResourceInfo().at(cost.id); + auto& resourceInfo = CollectiveConfig::getResourceInfo(cost.id); if (cost.value > 0 && !resourceInfo.dontDisplay) return make_pair(resourceInfo.viewId, cost.value); else @@ -778,6 +794,13 @@ static ViewId getSquareViewId(SquareType type) { return ids.at(type); } +static ViewId getFurnitureViewId(FurnitureType type) { + static EnumMap> ids; + if (!ids[type]) + ids[type] = FurnitureFactory::get(type)->getViewObject().id(); + return *ids[type]; +} + vector