diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..a3fd6648b --- /dev/null +++ b/Makefile @@ -0,0 +1,93 @@ + + +CC = new-g++ +LD = new-g++ +CFLAGS = -Wall -std=c++0x -Wno-sign-compare -Wno-unused-variable -Wl,-rpath=. + +CMD:=$(shell rm -f zagadka) + +ifdef DATA_DIR + CFLAGS += -DDATA_DIR=\"$(DATA_DIR)\" +endif + +ifdef OPT +GFLAG = -O3 -DRELEASE +else +GFLAG = -g +endif + +ifndef OPTFLAGS + OPTFLAGS = -static-libstdc++ ${GFLAG} +endif + +ifdef OPT +OBJDIR = obj-opt +else +OBJDIR = obj +endif + +NAME = zagadka + +ROOT = ./ +TOROOT = ./../ +IPATH = -I. -I./extern + +CFLAGS += $(IPATH) + +SRCS = time_queue.cpp level.cpp model.cpp square.cpp util.cpp monster.cpp square_factory.cpp view.cpp creature.cpp message_buffer.cpp item_factory.cpp item.cpp inventory.cpp debug.cpp player.cpp window_view.cpp field_of_view.cpp view_object.cpp creature_factory.cpp quest.cpp shortest_path.cpp effect.cpp equipment.cpp level_maker.cpp monster_ai.cpp attack.cpp attack.cpp tribe.cpp name_generator.cpp event.cpp location.cpp skill.cpp fire.cpp ranged_weapon.cpp action.cpp map_layout.cpp trigger.cpp map_memory.cpp view_index.cpp pantheon.cpp enemy_check.cpp collective.cpp collective_action.cpp task.cpp markov_chain.cpp controller.cpp village_control.cpp poison_gas.cpp minion_equipment.cpp + +#LIBS = -L/usr/lib/x86_64-linux-gnu `pkg-config --libs allegro-5.0 allegro_ttf-5.0 allegro_primitives-5.0 allegro_image-5.0` +LIBS = -L/usr/lib/x86_64-linux-gnu -lsfml-graphics -lsfml-window -lsfml-system ${LDFLAGS} + +ifdef debug + CFLAGS += -g + OBJDIR := ${addsuffix -d,$(OBJDIR)} + NAME := ${addsuffix -d,$(NAME)} +else + CFLAGS += $(OPTFLAGS) +endif + + +OBJS = $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) +DEPS = $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.d)) + +############################################################################## + + +all: $(OBJDIR) $(NAME) + ctags --c++-kinds=+p --fields=+iaS --extra=+q *.h + +$(OBJDIR): + mkdir $(OBJDIR) + +stdafx.h.gch: stdafx.h $(wildcard *.h) + $(CC) -MMD $(CFLAGS) -c $< -o $@ + +ifndef OPT +PCH = stdafx.h.gch +endif + +$(OBJDIR)/%.o: %.cpp ${PCH} + $(CC) -MMD $(CFLAGS) -c $< -o $@ + +$(NAME): $(OBJS) $(OBJDIR)/main.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +test: $(OBJS) $(OBJDIR)/test.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +monkey: $(OBJS) $(OBJDIR)/monkey_test.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +clean: + $(RM) $(OBJDIR)/*.o + $(RM) $(OBJDIR)/*.d + $(RM) log13* + $(RM) log.out + $(RM) $(OBJDIR)/test + $(RM) $(OBJDIR)-opt/*.o + $(RM) $(OBJDIR)-opt/*.d + $(RM) $(NAME) + $(RM) stdafx.h.gch + +-include $(DEPS) diff --git a/Makefile-win b/Makefile-win new file mode 100644 index 000000000..698325a5d --- /dev/null +++ b/Makefile-win @@ -0,0 +1,71 @@ + + +CC = g++ +LD = g++ +CFLAGS = -Wall -std=c++0x -Wno-sign-compare -Wno-unused-variable + +ifdef DATA_DIR + CFLAGS += -DDATA_DIR=\"$(DATA_DIR)\" +endif + +ifndef OPTFLAGS + OPTFLAGS = -Winvalid-pch -static-libstdc++ -static -DSFML_STATIC -DWINDOWS -DRELEASE -O3 +endif + +OBJDIR = obj +NAME = zagadka + +ROOT = ./ +TOROOT = ./../ +IPATH = -I. -I./extern + +CFLAGS += $(IPATH) + +SRCS = time_queue.cpp level.cpp model.cpp square.cpp util.cpp monster.cpp square_factory.cpp view.cpp creature.cpp message_buffer.cpp item_factory.cpp item.cpp inventory.cpp debug.cpp player.cpp window_view.cpp field_of_view.cpp view_object.cpp creature_factory.cpp quest.cpp shortest_path.cpp effect.cpp equipment.cpp level_maker.cpp monster_ai.cpp attack.cpp attack.cpp tribe.cpp name_generator.cpp event.cpp location.cpp skill.cpp fire.cpp ranged_weapon.cpp action.cpp map_layout.cpp trigger.cpp map_memory.cpp view_index.cpp pantheon.cpp enemy_check.cpp collective.cpp collective_action.cpp task.cpp markov_chain.cpp controller.cpp village_control.cpp poison_gas.cpp minion_equipment.cpp + +LIBS = -lsfml-graphics-s -lsfml-window-s -lsfml-system-s -lkernel32 -luser32 -lgdi32 -lcomdlg32 -lole32 -ldinput -lddraw -ldxguid -lwinmm -ldsound -lpsapi -lgdiplus -lshlwapi -luuid -lfreetype-2.4.8-static-md -lopengl32 -lglu32 + + + +ifdef debug + CFLAGS += -g + OBJDIR := ${addsuffix -d,$(OBJDIR)} + NAME := ${addsuffix -d,$(NAME)} +else + CFLAGS += $(OPTFLAGS) +endif + + +OBJS = $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) +DEPS = $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.d)) + +############################################################################## + +all: $(OBJDIR) $(NAME) + ctags --c++-kinds=+p --fields=+iaS --extra=+q *.h + +$(OBJDIR): + mkdir $(OBJDIR) + +stdafx.h.gch: stdafx.h $(wildcard *.h) + $(CC) -MMD $(CFLAGS) -c $< -o $@ + +$(OBJDIR)/%.o: %.cpp stdafx.h.gch + $(CC) -MMD $(CFLAGS) -c $< -o $@ + +$(NAME): $(OBJS) $(OBJDIR)/main.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +test: $(OBJS) $(OBJDIR)/test.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +monkey: $(OBJS) $(OBJDIR)/monkey_test.o + $(LD) $(CFLAGS) -o $@ $^ $(LIBS) + +clean: + $(RM) $(OBJDIR)/*.o + $(RM) $(OBJDIR)/*.d + $(RM) $(NAME) + $(RM) stdafx.h.gch + +-include $(DEPS) diff --git a/Symbola.ttf b/Symbola.ttf new file mode 100644 index 000000000..a49cc68b2 Binary files /dev/null and b/Symbola.ttf differ diff --git a/action.cpp b/action.cpp new file mode 100644 index 000000000..24d0057ee --- /dev/null +++ b/action.cpp @@ -0,0 +1,43 @@ +#include "stdafx.h" + +vector dirActions {ActionId::MOVE, ActionId::TRAVEL, ActionId::FIRE, ActionId::THROW_DIR}; + +Action::Action() { +} + +Action::Action(ActionId i) : id(i) { + CHECK(!contains(dirActions, id)) << "Id " << (int)id << " needs direction"; +} + +ActionId Action::getId() { + return id; +} + +Action::Action(ActionId i, Vec2 dir) : id(i), direction(dir) { + CHECK(contains(dirActions, id)) << "Id " << (int)id << " doesn't have direction"; +} + +Vec2 Action::getDirection() { + CHECK(contains(dirActions, id)) << "Id " << (int)id << " doesn't have direction"; + return direction; +} + +std::ostream& operator << (std::ostream& o, Action a) { + o << (int) a.getId(); + if (contains(dirActions, a.getId())) + o << " " << a.getDirection(); + return o; +} + +std::istream& operator >> (std::istream& in, Action& a) { + int id; + in >> id; + if (contains(dirActions, (ActionId) id)) { + Vec2 v; + in >> v; + a = Action((ActionId) id, v); + } else + a = Action((ActionId) id); + return in; +} + diff --git a/action.h b/action.h new file mode 100644 index 000000000..92c742045 --- /dev/null +++ b/action.h @@ -0,0 +1,44 @@ +#ifndef _ACTION_H +#define _ACTION_H + +#include + +enum class ActionId { + MOVE, + TRAVEL, + FIRE, + PICK_UP, + EXT_PICK_UP, + DROP, + EXT_DROP, + SHOW_INVENTORY, + APPLY_ITEM, + EQUIPMENT, + THROW, + THROW_DIR, + SHOW_HISTORY, + HIDE, + PAY_DEBT, + CHAT, + WAIT, + UNPOSSESS, + IDLE +}; + +class Action { + public: + Action(); + Action(ActionId); + Action(ActionId, Vec2 direction); + ActionId getId(); + Vec2 getDirection(); + + private: + ActionId id; + Vec2 direction; +}; + +std::ostream& operator << (std::ostream&, Action); +std::istream& operator >> (std::istream&, Action&); + +#endif diff --git a/artifacts.txt b/artifacts.txt new file mode 100644 index 000000000..cafd620bf --- /dev/null +++ b/artifacts.txt @@ -0,0 +1,233 @@ +Anglos +Angris +Angu Blade +Anguirit +Angul-blade +Aran Jing +Aranagicke +Aranajandir +Arandimor +Arandir +Bang Death +Bang Star +Bangerpear +Banglamdril +Bangu Bang +Bear Shard +Bear Star +Beargh +Bearonfirel +Bearsil +Beltach +Bladbolt +Bladhund +Bladhunt +Bladhus +Blightsteel +Bloodband +Bloodbang +Bloodbolt +Bloodlusher +Bloothuse +Bran Jinger +Branagmor +Brandimor +Brandimord +Branrúth +Bullblade +Bullcrusher +Bullcrust +Bullfist +Bullnir +Bully Blade +Burnblade +Burnbladhus +Burninguire +CalachBang +Caladbana +Calamdrit +Calare Bite +Calarefire +Calaze Star +Caudimordal +Caudimtooth +Changabang +Char Shard +Char Spirit +Char Stach +Char Star +Chargh +Clar Spiril +Clarendal +Clarendax +Clargh +Claronfist +Clarsil +Corpal Band +Deathunter +Drambolg +Durefist +Durewinë +Ebony Blace +Ebony Death +Fire Blade +Firefist +Firendir +Firewind +Firewinder +Firewinë +Flamboko +Frakebitch +Frakebite +Frost Blade +Glamboko +Glampirist +Graysword +Grimordax +Grimtopper +Gron's Bane +Gronfire +Grony Blade +Grony Death +Hellblade +Hellblazor +Hellcrusher +Hellcrust +Hellfist +Hellseye +Helly Blade +Ironfire +Irony Blade +Irony Death +Jianrúth +Jing Death +Jing Spiril +Lugh Razor +Lughtstooth +Meshersteel +Morakir +Moral Razor +Morgh Rede +Morgulsword +Mournir +Nehillblade +Nehirlwinë +Razorblash +Razorbolt +Redempir +Ring Death +Ring Shard +Ring Spirit +Ring Star +Ringersteel +Ringlaw +Ringrist +Serpal Band +Serpal Bang +Serpal Bite +Shar Spiril +Shar Stach +Shar Star +Shargh +Sharsil +Silent Bang +Silent Bite +Silent Blaw +Silencer +Skul-blach +Skul-blade +Skul-blare +Skul-blaw +Skullseye +Skullsword +Snagmor +Snagmord +Snagmordax +Snakebite +Snakebitch +Snakirite +Soul-blach +Soul-blade +Speedhunter +Spear Spel +Spear Spir +Speathwinë +Spellblach +Spellcrush +Spellfire +Spelth +Spelthwinë +Spirel +Spirellfire +Spirite +Starendal +Starendax +Stargh +Starond +Starondir +Starsil +Stormblade +Stordal +Stordax +Storlent +Stormlasil +Sure Bitch +Sure Bite +Sure Blade +Sure Brakir +Sure Bul +Surefist +Surewind +Surewinë +Thundempire +Thunderbolg +Thunte +Thunteel +Thunteind +Thunteinë +Umbral Bane +Umbralade +Umbralibur +Umbrindle +Umbringer +Umbrinn +Umbrinë +Undeadbolg +Undeadhunt +Undempir +Underblade +Underbolt +Uzun Brakk +Vampirel +Vampirite +Vampirlwing +Vargh Rede +Vargul-blaw +Vargulseye +Vargulswand +Vargulswane +Vargulswang +Vasilibur +Vasiligh +Vasilir +Vorpendal +Vorpendax +Vorpent +Werefire +Werefist +Werendinë +Werendúril +Werendúth +Werewind +Whire Brand +Whire Shard +Whirel +Whirelth +Whirit +Whiritch +Whirlwing +Whirlwinë +WitchBanaga +WitchBand +WitchBandir +WitchBang diff --git a/attack.cpp b/attack.cpp new file mode 100644 index 000000000..7da026f72 --- /dev/null +++ b/attack.cpp @@ -0,0 +1,29 @@ +#include "stdafx.h" + +using namespace std; + +Attack::Attack(const Creature* a, AttackLevel l, AttackType t, int h, int s, bool b) : attacker(a), level(l), type(t), toHit(h), strength(s), back(b) {} + +const Creature* Attack::getAttacker() const { + return attacker; +} + +int Attack::getStrength() const { + return strength; +} + +int Attack::getToHit() const { + return toHit; +} + +AttackType Attack::getType() const { + return type; +} + +AttackLevel Attack::getLevel() const { + return level; +} + +bool Attack::inTheBack() const { + return back; +} diff --git a/attack.h b/attack.h new file mode 100644 index 000000000..99eca10c2 --- /dev/null +++ b/attack.h @@ -0,0 +1,28 @@ +#ifndef _ATTACK_H +#define _ATTACK_H + +#include "enums.h" + +class Creature; + +class Attack { + public: + Attack(const Creature* attacker, AttackLevel level, AttackType type, int toHit, int strength, bool back = false); + + const Creature* getAttacker() const; + int getStrength() const; + int getToHit() const; + AttackType getType() const; + AttackLevel getLevel() const; + bool inTheBack() const; + + private: + const Creature* attacker; + AttackLevel level; + AttackType type; + int toHit; + int strength; + bool back; +}; + +#endif diff --git a/aztec_names.txt b/aztec_names.txt new file mode 100644 index 000000000..93c8de3b7 --- /dev/null +++ b/aztec_names.txt @@ -0,0 +1,382 @@ +Cama +Camaxoch +Camaxochtl +Camaxtl +Citl +Citlac +Citlaca +Citlacel +Citlal +Citli +Citlinteca +Citlintia +Citlintica +Coatl +Coatlaca +Coatlal +Coatlalli +Coatli +Coaxtl +Coaxtlal +Coaxtlalli +Coaxtli +Coyotia +Coyotica +Coyoticac +Coyotical +Coyoticatl +Cual +Cualxoch +Cualxochtl +Cuatl +Cuatlacatl +Cuatlacel +Cuatlal +Cuatlalli +Cuatli +Eztl +Eztlac +Eztlacalli +Eztlacatl +Eztlacel +Eztlacelel +Eztlal +Eztlalli +Ich +Ichitl +Ichitlalli +Ichitli +Ichtac +Ichtacel +Ichtacoatl +Ichtl +Ichtlacel +Ichtlalli +Ichtli +Icnoch +Icnochitl +Icnochitli +Icnochtac +Icnochtaca +Icnochtl +Icnochtli +Icnoyotia +Icnoyotica +Ihual +Ihualli +Ihualxoch +Ihuatl +Ihuatlac +Ihuatlal +Ihuatlalli +Ihuatli +Ihuica +Ihuicac +Ihuicaca +Ihuicacac +Ihuicacatl +Ihuicacel +Ihuical +Ihuicalli +Itotica +Itoticac +Itoticaca +Itoticacel +Itotical +Itoticalli +Itoticatl +Itoticatli +Itotl +Itotlacal +Itotlal +Itotlalli +Itotli +Itotlintia +Ixtl +Ixtlac +Ixtlaca +Ixtlacel +Ixtlacelel +Ixtlal +Ixtlalli +Mazal +Mazalli +Mazalxoch +Mezca +Mezcac +Mezcaca +Mezcacacel +Mezcacalli +Mezcacatl +Mezcacatli +Mezcacel +Mezcacelel +Mezcacoatl +Mezcal +Mezcalli +Mezcalxoch +Mezcatl +Mezcatli +Meztl +Meztlacatl +Meztlal +Meztlalli +Mezuma +Mezumaxoch +Mezumaxtl +Mezumaxtli +Mil +Mili +Milinteca +Milintia +Monteca +Montecac +Montecacel +Montecal +Montecalli +Montecatl +Montecatli +Montezca +Montezcac +Montezcal +Montezcatl +Monteztl +Monteztli +Montia +Montica +Monticac +Monticaca +Monticacel +Montical +Monticalli +Monticatl +Monticatli +Nah +Nahual +Nahualli +Nahualxoch +Nahuica +Nahuicac +Nahuicaca +Nahuicacel +Nahuical +Nahuicalli +Nahuicatl +Nahuicatli +Neca +Necac +Necaca +Necacac +Necacal +Necacatl +Necacatli +Necacel +Necacelel +Necacoatl +Necacoaxtl +Necal +Necalxoch +Necatl +Necatli +Nenetzal +Nenetzalli +Nenetzatl +Nenetzatli +Nenetzi +Nenoch +Nenochitl +Nenochitli +Nenochtac +Nenochtaca +Nenochtl +Nenochtli +Nenoyotia +Nenoyotica +Nenoyotl +Nenoyotli +Ohtac +Ohtaca +Ohtacac +Ohtacacel +Ohtacalli +Ohtacatl +Ohtacatli +Ohtacel +Ohtacelel +Ohtacoatl +Ohtacoatli +Ohtacoaxtl +Ohtl +Ohtlac +Ohtlacoatl +Ohtlal +Ohtlalli +Ohtlalxoch +Patl +Patlac +Patlacel +Patlalli +Quetl +Quetlac +Quetlacel +Quetlalli +Quetli +Quetzatl +Quetzatlac +Quetzatli +Quetzi +Tenetl +Tenetlac +Tenetlalli +Tenetli +Tenetzal +Tenetzalli +Tenetzatl +Tenetzatli +Tenetzi +Tenoyotia +Tenoyotica +Tenoyotl +Tenoyotlal +Tenoyotli +Tezca +Tezcac +Tezcaca +Tezcacatl +Tezcacatli +Tezcacel +Tezcal +Tezcalli +Tezcalxoch +Tezcatl +Tezcatlal +Tezcatli +Teztl +Teztlaca +Teztlacel +Teztlal +Teztlalli +Teztli +Tezuma +Tezumaxoch +Tezumaxtl +Tezumaxtli +Tlac +Tlaca +Tlacalli +Tlacatl +Tlacatli +Tlacel +Tlacoatl +Tlacoatli +Tlacoaxoch +Tlacoaxtl +Tlacoaxtli +Tlal +Tlalxoch +Tlalxochtl +Toch +Tochitl +Tochitlac +Tochitli +Tochtac +Tochtaca +Tochtacatl +Tochtacel +Tochtl +Tochtlalli +Tolotia +Tolotica +Toloticaca +Toloticatl +Tolotl +Tolotlalli +Tolotli +Tolteca +Toltecac +Toltecacac +Toltecacel +Toltecal +Toltecalli +Toltezca +Toltezcac +Toltezcatl +Tolteztl +Tolteztli +Toltezuma +Xiuhcoaxtl +Xoch +Xochtac +Xochtaca +Xochtacac +Xochtacal +Xochtacatl +Xochtacel +Xochtl +Xochtlacel +Xochtlal +Xochtlalli +Xochtli +Yaotia +Yaotica +Yaoticac +Yaoticacel +Yaoticalli +Yaoticatl +Yaoticatli +Yaretl +Yaretlal +Yaretli +Yaretzal +Yaretzalli +Yaretzatl +Yaretzatli +Yolotia +Yolotica +Yoloticac +Yoloticatl +Yolotl +Yolotlac +Yolotlal +Yolotlalli +Yolteca +Yoltecac +Yoltecacal +Yoltecacel +Yoltecal +Yoltecalli +Yoltecatl +Yoltecatli +Yoltezca +Yoltezcal +Yoltezcatl +Yolteztl +Yolteztli +Yoltezuma +Zaniya +Zaniyaniya +Zaniyanya +Zaniyanyah +Zanya +Zanyah +Zanyahual +Zanyahuatl +Zanyahuica +Zanyaniya +Zanyaniyah +Zanyanya +Zanyanyah +Zya +Zyah +Zyahual +Zyahualli +Zyahuatl +Zyahuatlal +Zyahuatli +Zyahuica +Zyahuicac +Zyahuical +Zyahuicatl +Zyaniya +Zyaniyah +Zyaniyanya diff --git a/collective.cpp b/collective.cpp new file mode 100644 index 000000000..43bcdb126 --- /dev/null +++ b/collective.cpp @@ -0,0 +1,810 @@ +#include "stdafx.h" + +enum Warning { NO_CHESTS, MORE_CHESTS, NO_BEDS, MORE_BEDS, NO_HATCHERY, NO_TRAINING }; +static const int numWarnings = 6; +static bool warning[numWarnings] = {0}; +static string warningText[] { + "You need to build a treasure room.", + "You need a larger treasure room.", + "You need a lair for your minions.", + "You need a larger lair for your minions.", + "Your minions are hungry!", + "You need training posts for your minions"}; + +vector Collective::buildInfo { + BuildInfo({SquareType::FLOOR, 0, "Dig"}), + BuildInfo({SquareType::TREASURE_CHEST, 0, "Treasure room"}), + BuildInfo({SquareType::TRIBE_DOOR, 20, "Door"}), + BuildInfo({SquareType::BRIDGE, 5, "Bridge"}), + BuildInfo({SquareType::BED, 50, "Lair"}), + BuildInfo({SquareType::HATCHERY, 10, "Hatchery"}), + BuildInfo({SquareType::TRAINING_DUMMY, 50, "Training room"}), + BuildInfo({SquareType::WORKSHOP, 50, "Workshop"}), + BuildInfo({SquareType::GRAVE, 50, "Graveyard"}), + BuildInfo({TrapType::BOULDER, "Boulder trap", ViewId::BOULDER}), + BuildInfo({TrapType::POISON_GAS, "Gas trap", ViewId::GAS_TRAP}), + BuildInfo(BuildInfo::IMP), + BuildInfo(BuildInfo::GUARD_POST), +}; + +Collective::Collective(CreatureFactory factory, CreatureFactory undead) + : minionFactory(factory), undeadFactory(undead), credit(50) { + EventListener::addListener(this); + // init the map so the values can be safely read with .at() + for (BuildInfo info : buildInfo) + if (info.buildType == BuildInfo::SQUARE) + mySquares[info.squareInfo.type].clear(); + else if (info.buildType == BuildInfo::TRAP) + trapMap[info.trapInfo.type].clear(); +} + + +const int basicImpCost = 20; +int startImpNum = -1; +const int minionLimit = 16; + + +void Collective::render(View* view) { + if (possessed && (!possessed->isPlayer() || possessed->isDead())) { + if (contains(team, possessed)) + removeElement(team, possessed); + if (possessed->isDead() && !team.empty()) { + possess(team.front(), view); + } else { + view->setTimeMilli(possessed->getTime() * 300); + view->clearMessages(); + ViewObject::setHallu(false); + possessed = nullptr; + team.clear(); + gatheringTeam = false; + teamLevelChanges.clear(); + view->resetCenter(); + } + } + if (!possessed) { + view->refreshView(this); + } else + view->stopClock(); +} + +bool Collective::isTurnBased() { + return possessed != nullptr; +} + +vector> Collective::getTrapItems(TrapType type, set squares) const { + vector> ret; + if (squares.empty()) + squares = mySquares.at(SquareType::WORKSHOP); + for (Vec2 pos : squares) { + vector v = level->getSquare(pos)->getItems([type, this](Item* it) { + return it->getTrapType() == type && !markedItems.count(it); }); + for (Item* it : v) + ret.emplace_back(it, pos); + } + return ret; +} + + +void Collective::refreshGameInfo(View::GameInfo& gameInfo) const { + gameInfo.infoType = View::GameInfo::InfoType::BAND; + View::GameInfo::BandInfo& info = gameInfo.bandInfo; + info.number = creatures.size(); + info.name = "KeeperRL"; + info.buttons.clear(); + int gold = numGold(); + for (BuildInfo button : buildInfo) + switch (button.buildType) { + case BuildInfo::SQUARE: { + BuildInfo::SquareInfo& elem = button.squareInfo; + info.buttons.emplace_back( + elem.name + (elem.cost > 0 ? (" $ " + convertToString(elem.cost)) : "") + + (elem.cost > 0 ? " [" + convertToString(mySquares.at(elem.type).size()) + "]" : ""), + SquareFactory::get(elem.type)->getViewObject(), + elem.cost <= gold); + } + break; + case BuildInfo::TRAP: { + BuildInfo::TrapInfo& elem = button.trapInfo; + int numTraps = getTrapItems(elem.type).size(); + info.buttons.emplace_back(elem.name + " (" + convertToString(numTraps) + " ready)", + ViewObject(elem.viewId, ViewLayer::LARGE_ITEM, ""), numTraps > 0); + } + break; + case BuildInfo::IMP: + info.buttons.emplace_back("Imp $ " + convertToString(getImpCost()), + ViewObject(ViewId::IMP, ViewLayer::CREATURE, ""), getImpCost() <= gold); + break; + case BuildInfo::GUARD_POST: + info.buttons.emplace_back("Guard post", + ViewObject(ViewId::GUARD_POST, ViewLayer::CREATURE, ""), true); + + } + info.activeButton = currentButton; + info.tasks = minionTaskStrings; + info.monsterHeader = "Monsters: " + convertToString(minions.size()) + " / " + convertToString(minionLimit); + info.creatures.clear(); + for (Creature* c : minions) + info.creatures.push_back(c); + info.enemies.clear(); + for (Vec2 v : myTiles) + if (Creature* c = level->getSquare(v)->getCreature()) + if (c->getTribe() != Tribe::player) + info.enemies.push_back(c); + info.numGold = numGold(); + info.warning = ""; + for (int i : Range(numWarnings)) + if (warning[i]) { + info.warning = warningText[i]; + break; + } + info.time = heart->getTime(); + info.gatheringTeam = gatheringTeam; + info.team.clear(); + for (Creature* c : team) + info.team.push_back(c); +} + +const MapMemory& Collective::getMemory(const Level* l) const { + return memory[l]; +} + +static ViewObject getTrapObject(TrapType type) { + switch (type) { + case TrapType::BOULDER: return ViewObject(ViewId::UNARMED_BOULDER_TRAP, ViewLayer::LARGE_ITEM, "Unarmed trap"); + case TrapType::POISON_GAS: return ViewObject(ViewId::UNARMED_GAS_TRAP, ViewLayer::LARGE_ITEM, "Unarmed trap"); + } + return ViewObject(ViewId::UNARMED_GAS_TRAP, ViewLayer::LARGE_ITEM, "Unarmed trap"); +} + +ViewIndex Collective::getViewIndex(Vec2 pos) const { + ViewIndex index = level->getSquare(pos)->getViewIndex(this); + if (marked.count(pos)) + index.setHighlight(HighlightType::BUILD); + if (!index.hasObject(ViewLayer::LARGE_ITEM)) { + if (traps.count(pos)) + index.insert(getTrapObject(traps.at(pos).type)); + if (guardPosts.count(pos)) + index.insert(ViewObject(ViewId::GUARD_POST, ViewLayer::LARGE_ITEM, "Guard post")); + } + return index; +} + +bool Collective::staticPosition() const { + return false; +} + +Vec2 Collective::getPosition() const { + return heart->getPosition(); +} + +enum Selection { SELECT, DESELECT, NONE } selection = NONE; + + + +void Collective::addTask(PTask task, Creature* c) { + taken[task.get()] = c; + taskMap[c] = task.get(); + addTask(std::move(task)); +} + +void Collective::addTask(PTask task) { + tasks.push_back(std::move(task)); +} + +void Collective::removeTask(Task* task) { + if (marked.count(task->getPosition())) + marked.erase(task->getPosition()); + for (int i : All(tasks)) + if (tasks[i].get() == task) { + removeIndex(tasks, i); + break; + } + if (taken.count(task)) { + taskMap.erase(taken.at(task)); + taken.erase(task); + } +} + +void Collective::markSquare(Vec2 pos, BuildInfo::SquareInfo info) { + tasks.push_back(Task::construction(this, pos, info.type)); + marked[pos] = tasks.back().get(); + if (info.cost) + completionCost[tasks.back().get()] = info.cost; +} + +void Collective::unmarkSquare(Vec2 pos) { + Task* t = marked.at(pos); + if (completionCost.count(t)) { + returnGold(completionCost.at(t)); + completionCost.erase(t); + } + removeTask(t); + marked.erase(pos); +} + +int Collective::numGold() const { + int ret = credit; + for (Vec2 pos : mySquares.at(SquareType::TREASURE_CHEST)) + ret += level->getSquare(pos)->getItems(Item::typePredicate(ItemType::GOLD)).size(); + return ret; +} + +void Collective::takeGold(int number) { + int num = number; + if (num == 0) + return; + CHECK(num > 0); + if (credit) { + if (credit >= num) { + credit -= num; + return; + } else { + num -= credit; + credit = 0; + } + } + for (Vec2 pos : randomPermutation(mySquares[SquareType::TREASURE_CHEST])) { + vector goldHere = level->getSquare(pos)->getItems(Item::typePredicate(ItemType::GOLD)); + for (Item* it : goldHere) { + level->getSquare(pos)->removeItem(it); + if (--num == 0) + return; + } + } + Debug(FATAL) << "Didn't have enough gold"; +} + +void Collective::returnGold(int num) { + if (num == 0) + return; + CHECK(num > 0); + if (mySquares[SquareType::TREASURE_CHEST].empty()) { + credit += num; + } else + level->getSquare(chooseRandom(mySquares[SquareType::TREASURE_CHEST]))-> + dropItems(ItemFactory::fromId(ItemId::GOLD_PIECE, num)); +} + +int Collective::getImpCost() const { + if (imps.size() < startImpNum) + return 0; + return basicImpCost * pow(2, double(imps.size() - startImpNum) / 5); +} + +void Collective::possess(const Creature* cr, View* view) { + CHECK(contains(creatures, cr)); + CHECK(!cr->isDead()); + Creature* c = const_cast(cr); + if (c->isSleeping()) + c->wakeUp(); + freeFromGuardPost(c); + c->pushController(new Player(c, view, false, &memory)); + possessed = c; + c->getLevel()->setPlayer(c); +} + +bool Collective::canBuildDoor(Vec2 pos) const { + Rectangle innerRect = level->getBounds().minusMargin(1); + auto wallFun = [=](Vec2 pos) { + return level->getSquare(pos)->canConstruct(SquareType::FLOOR) || + !pos.inRectangle(innerRect); }; + return pos.inRectangle(innerRect) && + ((wallFun(pos - Vec2(0, 1)) && wallFun(pos - Vec2(0, -1))) || + (wallFun(pos - Vec2(1, 0)) && wallFun(pos - Vec2(-1, 0)))); +} + +bool Collective::canPlacePost(Vec2 pos) const { + return !guardPosts.count(pos) && !traps.count(pos) && + level->getSquare(pos)->canEnterEmpty(Creature::getDefault()) && memory[level].hasViewIndex(pos); +} + +void Collective::freeFromGuardPost(const Creature* c) { + for (auto& elem : guardPosts) + if (elem.second.attender == c) + elem.second.attender = nullptr; +} + +void Collective::processInput(View* view) { + CollectiveAction action = view->getClick(); + switch (action.getType()) { + case CollectiveAction::GATHER_TEAM: + if (gatheringTeam && !team.empty()) { + possess(team[0], view); + gatheringTeam = false; + for (Creature* c : team) { + freeFromGuardPost(c); + if (c->isSleeping()) + c->wakeUp(); + } + } else + gatheringTeam = true; + break; + case CollectiveAction::CANCEL_TEAM: gatheringTeam = false; team.clear(); break; + case CollectiveAction::ROOM_BUTTON: currentButton = action.getNum(); break; + case CollectiveAction::CREATURE_BUTTON: + if (!gatheringTeam) + possess(action.getCreature(), view); + else { + if (contains(team, action.getCreature())) + removeElement(team, action.getCreature()); + else + team.push_back(const_cast(action.getCreature())); + } + break; + case CollectiveAction::CREATURE_DESCRIPTION: messageBuffer.addMessage(MessageBuffer::important( + action.getCreature()->getDescription())); break; + case CollectiveAction::GO_TO: { + Vec2 pos = action.getPosition(); + if (!pos.inRectangle(level->getBounds())) + return; + switch (buildInfo[currentButton].buildType) { + case BuildInfo::IMP: + if (numGold() >= getImpCost()) { + PCreature imp = CreatureFactory::fromId(CreatureId::IMP, Tribe::player, + MonsterAIFactory::collective(this)); + for (Vec2 v : pos.neighbors8(true)) + if (v.inRectangle(level->getBounds()) && level->getSquare(v)->canEnter(imp.get())) { + takeGold(getImpCost()); + addCreature(imp.get()); + level->addCreature(v, std::move(imp)); + break; + } + } + break; + case BuildInfo::TRAP: { + TrapType trapType = buildInfo[currentButton].trapInfo.type; + if (getTrapItems(trapType).size() > 0 && canPlacePost(pos)){ + traps[pos] = {trapType, false, false}; + trapMap[trapType].push_back(pos); + } + } + break; + case BuildInfo::GUARD_POST: + if (guardPosts.count(pos) && selection != SELECT) { + guardPosts.erase(pos); + selection = DESELECT; + } + else if (canPlacePost(pos) && guardPosts.size() < minions.size() && selection != DESELECT) { + guardPosts[pos] = {nullptr}; + selection = SELECT; + } + break; + case BuildInfo::SQUARE: { + BuildInfo::SquareInfo info = buildInfo[currentButton].squareInfo; + bool diggingSquare = !memory[level].hasViewIndex(pos) || + (level->getSquare(pos)->canConstruct(info.type)); + if (diggingSquare || selection != NONE) { + if (!marked.count(pos) && selection != DESELECT && diggingSquare && + numGold() >= info.cost && + (info.type != SquareType::TRIBE_DOOR || canBuildDoor(pos))) { + markSquare(pos, info); + selection = SELECT; + takeGold(info.cost); + } else + if (marked.count(pos) && selection != SELECT) { + unmarkSquare(pos); + selection = DESELECT; + } + } + } + break; + } + } + break; + case CollectiveAction::BUTTON_RELEASE: selection = NONE; break; + + default: break; + } +} + +void Collective::onConstructed(Vec2 pos, SquareType type) { + CHECK(!mySquares[type].count(pos)); + mySquares[type].insert(pos); + if (contains({SquareType::FLOOR, SquareType::BRIDGE}, type)) + locked.clear(); + if (marked.count(pos)) + marked.erase(pos); +} + +void Collective::onPickedUp(Vec2 pos, vector items) { + CHECK(!items.empty()); + for (Item* it : items) + markedItems.erase(it); +} + +void Collective::onBrought(Vec2 pos, vector items) { +} + +void Collective::onAppliedItem(Vec2 pos, Item* item) { + CHECK(item->getTrapType()); + traps[pos].marked = false; + traps[pos].armed = true; +} + +void Collective::onAppliedItemCancel(Vec2 pos) { + traps.at(pos).marked = false; +} + +Vec2 Collective::getHeartPos() const { + return heart->getPosition(); +} + +ItemPredicate Collective::unMarkedItems(ItemType type) const { + return [this, type](Item* it) { + return it->getType() == type && !markedItems.count(it); }; +} + +void Collective::update(Creature* c) { + if (!contains(creatures, c) || c->getLevel() != level) + return; + for (Vec2 pos : level->getVisibleTiles(c)) { + myTiles.insert(pos); + ViewIndex index = level->getSquare(pos)->getViewIndex(c); + memory[level].clearSquare(pos); + for (ViewLayer l : { ViewLayer::ITEM, ViewLayer::FLOOR, ViewLayer::LARGE_ITEM}) + if (index.hasObject(l)) + memory[level].addObject(pos, index.getObject(l)); + } +} + +bool Collective::isDownstairsVisible() const { + Vec2 pos = getOnlyElement(level->getLandingSquares(StairDirection::DOWN, StairKey::DWARF)); + return memory[level].hasViewIndex(pos); +} + +void Collective::tick() { + if (isDownstairsVisible() && (minions.size() < mySquares[SquareType::BED].size() || minions.empty()) && + Random.roll(40) && minions.size() < minionLimit) { + PCreature c = minionFactory.random(MonsterAIFactory::collective(this)); + addCreature(c.get()); + level->landCreature(StairDirection::DOWN, StairKey::DWARF, std::move(c)); + } + warning[NO_BEDS] = mySquares[SquareType::BED].size() == 0 && !minions.empty(); + warning[MORE_BEDS] = mySquares[SquareType::BED].size() < minions.size() - vampires.size(); + warning[NO_TRAINING] = mySquares[SquareType::TRAINING_DUMMY].empty() && !minions.empty(); + warning[NO_HATCHERY] = mySquares[SquareType::HATCHERY].empty() && !minions.empty(); + map>> trapItems { + {TrapType::BOULDER, getTrapItems(TrapType::BOULDER, myTiles)}, + {TrapType::POISON_GAS, getTrapItems(TrapType::POISON_GAS, myTiles)}}; + for (auto elem : traps) { + vector>& items = trapItems.at(elem.second.type); + if (!items.empty()) { + if (!elem.second.armed && !elem.second.marked) { + addTask(Task::applyItem(this, items.back().second, items.back().first, elem.first)); + markedItems.insert({items.back().first}); + items.pop_back(); + traps[elem.first].marked = true; + } + } + } + for (Vec2 pos : myTiles) { + vector gold = level->getSquare(pos)->getItems(unMarkedItems(ItemType::GOLD)); + if (gold.size() > 0 && !mySquares[SquareType::TREASURE_CHEST].count(pos)) { + if (!mySquares[SquareType::TREASURE_CHEST].empty()) { + warning[NO_CHESTS] = false; + Optional target; + for (Vec2 chest : mySquares[SquareType::TREASURE_CHEST]) + if ((!target || (chest - pos).length8() < (*target - pos).length8()) && + level->getSquare(chest)->getItems(Item::typePredicate(ItemType::GOLD)).size() <= 30) + target = chest; + if (!target) + warning[MORE_CHESTS] = true; + else { + warning[MORE_CHESTS] = false; + addTask(Task::bringItem(this, pos, gold, *target)); + markedItems.insert(gold.begin(), gold.end()); + } + } else { + warning[NO_CHESTS] = true; + } + } + vector corpses = level->getSquare(pos)->getItems(unMarkedItems(ItemType::CORPSE)); + if (corpses.size() > 0) { + if (!mySquares[SquareType::GRAVE].count(pos) && + !mySquares[SquareType::GRAVE].empty()) { + Vec2 target = chooseRandom(mySquares[SquareType::GRAVE]); + addTask(Task::bringItem(this, pos, {corpses[0]}, target)); + markedItems.insert({corpses[0]}); + } + if (mySquares[SquareType::GRAVE].count(pos) && Random.roll(200) && + vampires.size() < mySquares[SquareType::GRAVE].size() && minions.size() < minionLimit) { + PCreature vampire = undeadFactory.random(MonsterAIFactory::collective(this)); + for (Vec2 v : pos.neighbors8(true)) + if (level->getSquare(v)->canEnter(vampire.get())) { + level->getSquare(pos)->removeItems(corpses); + vampires.push_back(vampire.get()); + addCreature(vampire.get()); + level->addCreature(v, std::move(vampire)); + break; + } + } + } + vector equipment = level->getSquare(pos)->getItems([this](Item* it) { + return minionEquipment.isItemUseful(it) && !markedItems.count(it); }); + if (!equipment.empty() && !mySquares[SquareType::WORKSHOP].empty() && + !mySquares[SquareType::WORKSHOP].count(pos)) { + Vec2 target = chooseRandom(mySquares[SquareType::WORKSHOP]); + addTask(Task::bringItem(this, pos, equipment, target)); + markedItems.insert(equipment.begin(), equipment.end()); + } + if (marked.count(pos) && marked.at(pos)->isImpossible(level) && !taken.count(marked.at(pos))) + removeTask(marked.at(pos)); + } +} + +bool Collective::canSee(const Creature* c) const { + return canSee(c->getPosition()); +} + +bool Collective::canSee(Vec2 position) const { + return memory[level].hasViewIndex(position) + || contains(level->getLandingSquares(StairDirection::DOWN, StairKey::DWARF), position) + || contains(level->getLandingSquares(StairDirection::UP, StairKey::DWARF), position); + for (Creature* member : creatures) + if (member->canSee(position)) + return true; + return false; +} + +void Collective::setLevel(Level* l) { + for (Vec2 v : l->getBounds()) + if (/*contains({SquareApplyType::ASCEND, SquareApplyType::DESCEND}, + l->getSquare(v)->getApplyType(Creature::getDefault())) ||*/ + l->getSquare(v)->getName() == "gold ore") + memory[l].addObject(v, l->getSquare(v)->getViewObject()); + level = l; +} + +vector Collective::getUnknownAttacker() const { + return {}; +} + +void Collective::onChangeLevelEvent(const Creature* c, const Level* from, Vec2 pos, const Level* to, Vec2 toPos) { + if (c == possessed) { + teamLevelChanges[from] = pos; + if (!levelChangeHistory.count(to)) + levelChangeHistory[to] = toPos; + } +} + +MoveInfo Collective::getMinionMove(Creature* c) { + if (possessed && contains(team, c)) { + Optional v; + if (possessed->getLevel() != c->getLevel()) { + if (teamLevelChanges.count(c->getLevel())) { + v = teamLevelChanges.at(c->getLevel()); + if (v == c->getPosition()) + return {1.0, [=] { + c->applySquare(); + }}; + } + } else + v = possessed->getPosition(); + if (v) { + if (auto move = c->getMoveTowards(*v)) + return {1.0, [=] { + c->move(*move); + }}; + else + return NoMove; + } + } + if (c->getLevel() != level) { + if (!levelChangeHistory.count(c->getLevel())) + return NoMove; + Vec2 target = levelChangeHistory.at(c->getLevel()); + if (c->getPosition() == target) + return {1.0, [=] { + c->applySquare(); + }}; + else if (auto move = c->getMoveTowards(target)) + return {1.0, [=] { + c->move(*move); + }}; + else + return NoMove; + } + for (auto& elem : guardPosts) { + bool isTraining = contains({MinionTask::TRAIN, MinionTask::TRAIN_IDLE}, minionTasks.at(c).getState()); + if (elem.second.attender == c) { + if (isTraining) { + minionTasks.at(c).update(); + if (c->getPosition().dist8(elem.first) > 1) { + if (auto move = c->getMoveTowards(elem.first)) + return {1.0, [=] { + c->move(*move); + }}; + } else + return NoMove; + } else + elem.second.attender = nullptr; + } + } + for (auto& elem : guardPosts) { + bool isTraining = contains({MinionTask::TRAIN, MinionTask::TRAIN_IDLE}, minionTasks.at(c).getState()); + if (elem.second.attender == nullptr && isTraining) { + elem.second.attender = c; + if (taskMap.count(c)) + removeTask(taskMap.at(c)); + } + } + + if (taskMap.count(c)) { + Task* task = taskMap.at(c); + if (task->isDone()) { + removeTask(task); + } else + return task->getMove(c); + } + for (Vec2 v : mySquares[SquareType::WORKSHOP]) + for (Item* it : level->getSquare(v)->getItems([this, c] (const Item* it) { + return minionEquipment.needsItem(c, it); })) { + if (c->canEquip(it)) { + addTask(Task::equipItem(this, v, it), c); + } + else + addTask(Task::pickItem(this, v, {it}), c); + return taskMap.at(c)->getMove(c); + } + minionTasks.at(c).update(); + if (c->getHealth() < 1) + minionTasks.at(c).setState(MinionTask::SLEEP); + switch (minionTasks.at(c).getState()) { + case MinionTask::SLEEP: { + set& whatBeds = (contains(vampires, c) ? mySquares[SquareType::GRAVE] : mySquares[SquareType::BED]); + if (whatBeds.empty()) + return NoMove; + addTask(Task::applySquare(this, whatBeds), c); + minionTaskStrings[c] = "sleeping"; + break; } + case MinionTask::TRAIN: + if (mySquares[SquareType::TRAINING_DUMMY].empty()) + return NoMove; + addTask(Task::applySquare(this, mySquares[SquareType::TRAINING_DUMMY]), c); + minionTaskStrings[c] = "training"; + break; + case MinionTask::TRAIN_IDLE: + return NoMove; + case MinionTask::WORKSHOP: + if (mySquares[SquareType::WORKSHOP].empty()) + return NoMove; + addTask(Task::applySquare(this, mySquares[SquareType::WORKSHOP]), c); + minionTaskStrings[c] = "crafting"; + break; + case MinionTask::WORKSHOP_IDLE: + return NoMove; + case MinionTask::EAT: + if (mySquares[SquareType::HATCHERY].empty()) + return NoMove; + minionTaskStrings[c] = "eating"; + addTask(Task::eat(this, mySquares[SquareType::HATCHERY]), c); + break; + } + return taskMap.at(c)->getMove(c); +} + +MoveInfo Collective::getMove(Creature* c) { + if (!contains(imps, c)) { + CHECK(contains(minions, c)); + return getMinionMove(c); + } + if (c->getLevel() != level) + return NoMove; + if (startImpNum == -1) + startImpNum = imps.size(); + if (taskMap.count(c)) { + Task* task = taskMap.at(c); + if (task->isDone()) { + removeTask(task); + } else + return task->getMove(c); + } + Task* closest = nullptr; + for (PTask& task : tasks) { + double dist = (task->getPosition() - c->getPosition()).length8(); + if ((!taken.count(task.get()) || (task->canTransfer() + && (task->getPosition() - taken.at(task.get())->getPosition()).length8() > dist)) + && (!closest || + dist < (closest->getPosition() - c->getPosition()).length8()) && !locked.count(make_pair(c, task.get()))) { + bool valid = task->getMove(c).isValid(); + if (valid) + closest = task.get(); + else + locked.insert(make_pair(c, task.get())); + } + } + if (closest) { + if (taken.count(closest)) { + taskMap.erase(taken.at(closest)); + taken.erase(closest); + } + taskMap[c] = closest; + taken[closest] = c; + return closest->getMove(c); + } else + return NoMove; +} + +MarkovChain Collective::getTasksForMinion(Creature* c) { + MinionTask t1, t2; + if (c->getName() == "gnome") { + t1 = MinionTask::WORKSHOP; + t2 = MinionTask::WORKSHOP_IDLE; + } else { + t1 = MinionTask::TRAIN; + t2 = MinionTask::TRAIN_IDLE; + } + return MarkovChain(MinionTask::SLEEP, { + {MinionTask::SLEEP, {{ MinionTask::EAT, 0.5}, { t1, 0.5}}}, + {MinionTask::EAT, {{ t1, 0.4}, { MinionTask::SLEEP, 0.2}}}, + {t1, {{ MinionTask::EAT, 0.005}, { MinionTask::SLEEP, 0.005}, {t2, 0.99}}}, + {t2, {{ t1, 1}}}}); +} + +void Collective::addCreature(Creature* c) { + if (c->getName() == "dungeon heart") { + CHECK(heart == nullptr) << "Too many dungeon hearts"; + heart = c; + creatures.push_back(c); + return; + } + creatures.push_back(c); + if (!c->canConstruct(SquareType::FLOOR)) { + minions.push_back(c); + minionTasks.insert(make_pair(c, getTasksForMinion(c))); + } else + imps.push_back(c); +} + +void Collective::onSquareReplacedEvent(const Level* l, Vec2 pos) { + if (l == level) { + bool found = false; + for (auto& elem : mySquares) + if (elem.second.count(pos)) { + elem.second.erase(pos); + found = true; + } + CHECK(found); + } +} + +void Collective::onTriggerEvent(const Level* l, Vec2 pos) { + if (traps.count(pos) && l == level) + traps.at(pos).armed = false; +} + +void Collective::onKillEvent(const Creature* victim, const Creature* killer) { + if (victim == heart) { + messageBuffer.addMessage(MessageBuffer::important("Your dungeon heart was destroyed. " + "You've been playing KeeperRL alpha.")); + exit(0); + } + if (contains(creatures, victim)) { + Creature* c = const_cast(victim); + removeElement(creatures, c); + for (auto& elem : guardPosts) + if (elem.second.attender == c) + elem.second.attender = nullptr; + if (contains(team, c)) + removeElement(team, c); + if (taskMap.count(c)) { + if (!taskMap.at(c)->canTransfer()) { + taskMap.at(c)->cancel(); + removeTask(taskMap.at(c)); + } else { + taken.erase(taskMap.at(c)); + taskMap.erase(c); + } + } + if (contains(imps, c)) + removeElement(imps, c); + if (contains(minions, c)) + removeElement(minions, c); + if (contains(vampires, c)) + removeElement(vampires, c); + } +} + +const Level* Collective::getLevel() const { + return level; +} diff --git a/collective.h b/collective.h new file mode 100644 index 000000000..1e04a1a7b --- /dev/null +++ b/collective.h @@ -0,0 +1,134 @@ +#ifndef _COLLECTIVE_H +#define _COLLECTIVE_H + +#include "map_memory.h" +#include "view.h" +#include "monster_ai.h" +#include "creature_view.h" +#include "creature_factory.h" +#include "markov_chain.h" +#include "minion_equipment.h" + + +class Collective : public CreatureView, public EventListener { + public: + Collective(CreatureFactory minionFactory, CreatureFactory undeadFactory); + virtual const MapMemory& getMemory(const Level* l) const override; + virtual ViewIndex getViewIndex(Vec2 pos) const override; + virtual void refreshGameInfo(View::GameInfo&) const override; + virtual Vec2 getPosition() const override; + virtual bool canSee(const Creature*) const override; + virtual bool canSee(Vec2 position) const override; + virtual vector getUnknownAttacker() const override; + + virtual bool staticPosition() const override; + + virtual void onKillEvent(const Creature* victim, const Creature* killer) override; + virtual void onTriggerEvent(const Level*, Vec2 pos) override; + virtual void onSquareReplacedEvent(const Level*, Vec2 pos) override; + virtual void onChangeLevelEvent(const Creature*, const Level* from, Vec2 pos, const Level* to, Vec2 toPos) override; + + void processInput(View* view); + void tick(); + void update(Creature*); + MoveInfo getMove(Creature* c); + MoveInfo getMinionMove(Creature* c); + void addCreature(Creature* c); + void setLevel(Level* l); + + virtual const Level* getLevel() const; + + void onConstructed(Vec2 pos, SquareType); + void onBrought(Vec2 pos, vector items); + void onAppliedItem(Vec2 pos, Item* item); + void onAppliedItemCancel(Vec2 pos); + void onPickedUp(Vec2 pos, vector items); + + Vec2 getHeartPos() const; + void render(View*); + void possess(const Creature*, View*); + + bool isTurnBased(); + + private: + struct BuildInfo { + struct SquareInfo { + SquareType type; + int cost; + string name; + } squareInfo; + + struct TrapInfo { + TrapType type; + string name; + ViewId viewId; + } trapInfo; + + enum BuildType { SQUARE, IMP, TRAP, GUARD_POST} buildType; + + BuildInfo(SquareInfo info) : squareInfo(info), buildType(SQUARE) {} + BuildInfo(TrapInfo info) : trapInfo(info), buildType(TRAP) {} + BuildInfo(BuildType type) : buildType(type) { + CHECK(contains({IMP, GUARD_POST}, type)); + } + + }; + static vector buildInfo; + bool isDownstairsVisible() const; + void markSquare(Vec2 pos, BuildInfo::SquareInfo); + void unmarkSquare(Vec2 pos); + void removeTask(Task*); + void addTask(PTask, Creature*); + void addTask(PTask); + int numGold() const; + void takeGold(int); + void returnGold(int); + int getImpCost() const; + bool canBuildDoor(Vec2 pos) const; + bool canPlacePost(Vec2 pos) const; + void freeFromGuardPost(const Creature*); + vector> getTrapItems(TrapType, set = {}) const; + ItemPredicate unMarkedItems(ItemType) const; + MarkovChain getTasksForMinion(Creature* c); + CreatureFactory minionFactory; + CreatureFactory undeadFactory; + vector creatures; + vector minions; + vector imps; + vector vampires; + vector tasks; + set markedItems; + map marked; + map taken; + map taskMap; + map completionCost; + struct TrapInfo { + TrapType type; + bool armed; + bool marked; + }; + map traps; + map> trapMap; + map> minionTasks; + map minionTaskStrings; + set> locked; + map> mySquares; + set myTiles; + Level* level; + Creature* heart = nullptr; + mutable map memory; + int currentButton = 0; + bool gatheringTeam = false; + vector team; + map teamLevelChanges; + map levelChangeHistory; + int credit; + Creature* possessed = nullptr; + MinionEquipment minionEquipment; + struct GuardPostInfo { + const Creature* attender; + }; + map guardPosts; +}; + +#endif diff --git a/collective_action.cpp b/collective_action.cpp new file mode 100644 index 000000000..2b480bdc9 --- /dev/null +++ b/collective_action.cpp @@ -0,0 +1,41 @@ +#include "stdafx.h" + +vector vectorTypes { CollectiveAction::GO_TO }; +vector intTypes { CollectiveAction::ROOM_BUTTON }; +vector creatureTypes { CollectiveAction::CREATURE_BUTTON, + CollectiveAction::CREATURE_DESCRIPTION }; + +CollectiveAction::CollectiveAction(Type t, Vec2 p) : type(t), pos(p) { + CHECK(contains(vectorTypes, t)); +} + +CollectiveAction::CollectiveAction(Type t, int n) : type(t), num(n) { + CHECK(contains(intTypes, t)); +} + +CollectiveAction::CollectiveAction(Type t, const Creature* c) : type(t), creature(c) { + CHECK(contains(creatureTypes, t)); +} + +CollectiveAction::CollectiveAction(Type t) : type(t) { + CHECK(!contains(intTypes, t) && !contains(vectorTypes, t)); +} + +CollectiveAction::Type CollectiveAction::getType() { + return type; +} + +Vec2 CollectiveAction::getPosition() { + CHECK(contains(vectorTypes, type)); + return pos; +} + +int CollectiveAction::getNum() { + CHECK(contains(intTypes, type)); + return num; +} + +const Creature* CollectiveAction::getCreature() { + CHECK(contains(creatureTypes, type)); + return creature; +} diff --git a/collective_action.h b/collective_action.h new file mode 100644 index 000000000..623529a7f --- /dev/null +++ b/collective_action.h @@ -0,0 +1,28 @@ +#ifndef _COLLECTIVE_ACTION_H +#define _COLLECTIVE_ACTION_H + +#include "util.h" + +class CollectiveAction { + public: + enum Type { GO_TO, IDLE, BUTTON_RELEASE, ROOM_BUTTON, IMP_BUTTON, CREATURE_BUTTON, + CREATURE_DESCRIPTION, GATHER_TEAM, CANCEL_TEAM }; + + CollectiveAction(Type, Vec2 pos); + CollectiveAction(Type, int); + CollectiveAction(Type, const Creature*); + CollectiveAction(Type); + + Type getType(); + Vec2 getPosition(); + int getNum(); + const Creature* getCreature(); + + private: + Type type; + Vec2 pos; + int num; + const Creature* creature; +}; + +#endif diff --git a/controller.cpp b/controller.cpp new file mode 100644 index 000000000..264060ab1 --- /dev/null +++ b/controller.cpp @@ -0,0 +1,8 @@ +#include "stdafx.h" + +ControllerFactory::ControllerFactory(function f) : fun(f) {} + +PController ControllerFactory::get(Creature* c) { + return PController(fun(c)); +} + diff --git a/controller.h b/controller.h new file mode 100644 index 000000000..0c6eb3f4c --- /dev/null +++ b/controller.h @@ -0,0 +1,40 @@ +#ifndef _CONTROLLER_H +#define _CONTROLLER_H + +class Creature; + +class Controller { + public: + virtual void grantIdentify(int numItems) {}; + + virtual bool isPlayer() const = 0; + + virtual void you(MsgType type, const string& param) const = 0; + virtual void you(const string& param) const = 0; + virtual void privateMessage(const string& message) const {} + + virtual void onKilled(const Creature* attacker) {} + virtual void onItemsAppeared(vector items) {} + virtual const MapMemory& getMemory(const Level* l = nullptr) const = 0; + + virtual void makeMove() = 0; + + virtual bool wantsItems(const Creature* from, vector items) const { return false; } + virtual void takeItems(const Creature* from, vector items) {} + virtual int getDebt(const Creature* debtor) const { return 0; } + + virtual void onBump(Creature*) = 0; + + virtual ~Controller() {} +}; + +class ControllerFactory { + public: + ControllerFactory(function); + PController get(Creature*); + + private: + function fun; +}; + +#endif diff --git a/coolvetica rg.ttf b/coolvetica rg.ttf new file mode 100644 index 000000000..c8f4f14a3 Binary files /dev/null and b/coolvetica rg.ttf differ diff --git a/creature.cpp b/creature.cpp new file mode 100644 index 000000000..56da350ad --- /dev/null +++ b/creature.cpp @@ -0,0 +1,1673 @@ +#include "stdafx.h" + +using namespace std; + +Creature* Creature::getDefault() { + static PCreature defaultCreature = CreatureFactory::fromId(CreatureId::GNOME, Tribe::monster, + MonsterAIFactory::idle()); + return defaultCreature.get(); +} + +Creature::Creature(ViewObject o, Tribe* t, const CreatureAttributes& attr, ControllerFactory f) : CreatureAttributes(attr), viewObject(o), time(0), tribe(t), dead(false), lastTick(0), controller(f.get(this)) { + static int cnt = 1; + uniqueId = ++cnt; + for (Skill* skill : skills) + skill->onTeach(this); +} + +ViewIndex Creature::getViewIndex(Vec2 pos) const { + return level->getSquare(pos)->getViewIndex(this); +} + +void Creature::pushController(Controller* ctrl) { + viewObject.setPlayer(true); + controllerStack.push(std::move(controller)); + controller.reset(ctrl); +} + +void Creature::popController() { + viewObject.setPlayer(false); + CHECK(canPopController()); + controller = std::move(controllerStack.top()); + controllerStack.pop(); +} + +bool Creature::canPopController() { + return !controllerStack.empty(); +} + +bool Creature::isDead() const { + return dead; +} + +void Creature::spendTime(double t) { + time += 100.0 * t / (double) getAttr(AttrType::SPEED); + hidden = false; +} + +bool Creature::canMove(Vec2 direction) const { + if (holding) { + privateMessage("You can't break free!"); + return false; + } + return (direction.length8() == 1 && level->canMoveCreature(this, direction)) || canSwapPosition(direction); +} + +void Creature::move(Vec2 direction) { + stationary = false; + Debug() << getTheName() << " moving " << direction; + CHECK(canMove(direction)); + if (level->canMoveCreature(this, direction)) + level->moveCreature(this, direction); + else + swapPosition(direction); + if (collapsed) { + you(MsgType::CRAWL, getConstSquare()->getName()); + spendTime(3); + } else + spendTime(1); +} + +int Creature::getDebt(const Creature* debtor) const { + return controller->getDebt(debtor); +} + +bool Creature::wantsItems(const Creature* from, vector items) const { + return controller->wantsItems(from, items); +} + +void Creature::takeItems(const Creature* from, vector items) { + return controller->takeItems(from, std::move(items)); +} + +void Creature::you(MsgType type, const string& param) const { + controller->you(type, param); +} + +void Creature::you(const string& param) const { + controller->you(param); +} + +void Creature::privateMessage(const string& message) const { + controller->privateMessage(message); +} + +void Creature::onItemsAppeared(vector items) { + controller->onItemsAppeared(items); +} + +void Creature::grantIdentify(int numItems) { + controller->grantIdentify(numItems); +} + +Controller* Creature::getController() { + return controller.get(); +} + +const MapMemory& Creature::getMemory(const Level* l) const { + return controller->getMemory(l); +} + +bool Creature::canSwapPosition(Vec2 direction) const { + const Creature* c = getConstSquare(direction)->getCreature(); + if (!c) + return false; + if (c->sleeping) { + privateMessage(c->getTheName() + " is sleeping."); + return false; + } + return (!swapPositionCooldown || isPlayer()) && !c->stationary && + direction.length8() == 1 && !c->isPlayer() && !c->isEnemy(this) && + getConstSquare(direction)->canEnterEmpty(this) && getConstSquare()->canEnterEmpty(c); +} + +void Creature::swapPosition(Vec2 direction) { + CHECK(canSwapPosition(direction)); + swapPositionCooldown = 4; + getConstSquare(direction)->getCreature()->privateMessage("Excuse me!"); + privateMessage("Excuse me!"); + level->swapCreatures(this, getSquare(direction)->getCreature()); +} + +void Creature::makeMove() { + CHECK(!isDead()); + if (holding && holding->isDead()) + holding = nullptr; + if (sleeping) { + spendTime(1); + return; + } + updateVisibleEnemies(); + if (swapPositionCooldown) + --swapPositionCooldown; + MEASURE(controller->makeMove(), "creature move time"); + CHECK(!inEquipChain) << "Someone forgot to finishEquipChain()"; + if (!hidden) + viewObject.setHidden(false); + unknownAttacker.clear(); + if (!getSquare()->isCovered()) + shineLight(); +} + +Square* Creature::getSquare() { + return level->getSquare(position); +} + +Square* Creature::getSquare(Vec2 direction) { + return level->getSquare(position + direction); +} + +const Square* Creature::getConstSquare() const { + return getLevel()->getSquare(position); +} + +const Square* Creature::getConstSquare(Vec2 direction) const { + return getLevel()->getSquare(position + direction); +} + +void Creature::wait() { + Debug() << getTheName() << " waiting"; + bool keepHiding = hidden; + spendTime(1); + hidden = keepHiding; +} + +int Creature::getUniqueId() const { + return uniqueId; +} + +const Equipment& Creature::getEquipment() const { + return equipment; +} + +vector Creature::steal(const vector items) { + return equipment.removeItems(items); +} + +Item* Creature::getAmmo() const { + for (Item* item : equipment.getItems()) + if (item->getType() == ItemType::AMMO) + return item; + return nullptr; +} + +const Level* Creature::getLevel() const { + return level; +} + +Level* Creature::getLevel() { + return level; +} + +Vec2 Creature::getPosition() const { + return position; +} + +void Creature::globalMessage(const string& playerCanSee, const string& cant) const { + if (const Creature* player = level->getPlayer()) { + if (player->canSee(this)) + player->privateMessage(playerCanSee); + else + player->privateMessage(cant); + } +} + +const vector& Creature::getVisibleEnemies() const { + return visibleEnemies; +} + +void Creature::updateVisibleEnemies() { + visibleEnemies.clear(); + for (const Creature* c : level->getAllCreatures()) + if (isEnemy(c) && canSee(c)) + visibleEnemies.push_back(c); +} + +vector Creature::getVisibleCreatures() const { + vector res; + for (Creature* c : level->getAllCreatures()) + if (canSee(c)) + res.push_back(c); + for (const Creature* c : getUnknownAttacker()) + if (!contains(res, c)) + res.push_back(c); + return res; +} + +void Creature::addSkill(Skill* skill) { + skills.insert(skill); + skill->onTeach(this); + privateMessage(skill->getHelpText()); +} + +bool Creature::hasSkill(Skill* skill) const { + return skills.count(skill); +} + +bool Creature::hasSkillToUseWeapon(const Item* it) const { + return !it->isWieldedTwoHanded() || hasSkill(Skill::twoHandedWeapon); +} + +vector Creature::getPickUpOptions() const { + if (!isHumanoid()) + return vector(); + else + return level->getSquare(getPosition())->getItems(); +} + +bool Creature::canPickUp(const vector& items) const { + if (!isHumanoid()) + return false; + double weight = getInventoryWeight(); + for (Item* it : items) + weight += it->getWeight(); + if (weight > 2 * getAttr(AttrType::INV_LIMIT)) { + privateMessage("You are carrying too much to pick this up."); + return false; + } + return true; +} + +void Creature::pickUp(const vector& items) { + CHECK(canPickUp(items)); + Debug() << getTheName() << " pickup "; + for (auto item : items) { + equipment.addItem(level->getSquare(getPosition())->removeItem(item)); + } + if (getInventoryWeight() > getAttr(AttrType::INV_LIMIT)) + privateMessage("You are overloaded."); + EventListener::addPickupEvent(this, items); + spendTime(1); +} + +void Creature::drop(const vector& items) { + CHECK(isHumanoid()); + Debug() << getTheName() << " drop"; + for (auto item : items) { + level->getSquare(getPosition())->dropItem(equipment.removeItem(item)); + } + EventListener::addDropEvent(this, items); + spendTime(1); +} + +void Creature::drop(vector items) { + Debug() << getTheName() << " drop"; + getSquare()->dropItems(std::move(items)); +} + +void Creature::startEquipChain() { + inEquipChain = true; +} + +void Creature::finishEquipChain() { + inEquipChain = false; + if (numEquipActions > 0) + spendTime(1); + numEquipActions = 0; +} + +bool Creature::canEquip(const Item* item) const { + if (!isHumanoid()) + return false; + if (numGoodArms() == 0) { + privateMessage("You don't have hands!"); + return false; + } + if (!hasSkill(Skill::twoHandedWeapon) && item->isWieldedTwoHanded()) { + privateMessage("You don't have the skill to use two-handed weapons."); + return false; + } + if (!hasSkill(Skill::archery) && item->getType() == ItemType::RANGED_WEAPON) { + privateMessage("You don't have the skill to shoot a bow."); + return false; + } + if (numGoodArms() == 1 && item->isWieldedTwoHanded()) { + privateMessage("You need two hands to wield " + item->getAName() + "!"); + return false; + } + return item->canEquip() && equipment.getItem(item->getEquipmentSlot()) == nullptr; +} + +bool Creature::canUnequip(const Item* item) const { + if (!isHumanoid()) + return false; + if (numGoodArms() == 0) { + privateMessage("You don't have hands!"); + return false; + } else + return true; +} + +void Creature::equip(Item* item) { + CHECK(canEquip(item)); + Debug() << getTheName() << " equip " << item->getName(); + EquipmentSlot slot = item->getEquipmentSlot(); + equipment.equip(item, slot); + item->onEquip(this); + if (!inEquipChain) + spendTime(1); + else + ++numEquipActions; +} + +void Creature::unequip(Item* item) { + CHECK(canUnequip(item)); + Debug() << getTheName() << " unequip"; + EquipmentSlot slot = item->getEquipmentSlot(); + CHECK(equipment.getItem(slot) == item) << "Item not equiped."; + equipment.unequip(slot); + item->onUnequip(this); + if (!inEquipChain) + spendTime(1); + else + ++numEquipActions; +} + +bool Creature::canHeal(Vec2 direction) const { + Creature* other = level->getSquare(position + direction)->getCreature(); + return healer && other && other->getHealth() < 1; +} + +void Creature::heal(Vec2 direction) { + CHECK(canHeal(direction)); + Creature* other = level->getSquare(position + direction)->getCreature(); + other->you(MsgType::ARE, "healed by " + getTheName()); + other->heal(); + spendTime(1); +} + +bool Creature::canBumpInto(Vec2 direction) const { + return level->getSquare(getPosition() + direction)->getCreature(); +} + +void Creature::bumpInto(Vec2 direction) { + CHECK(canBumpInto(direction)); + level->getSquare(getPosition() + direction)->getCreature()->controller->onBump(this); + spendTime(1); +} + +void Creature::applySquare() { + Debug() << getTheName() << " applying " << getSquare()->getName();; + getSquare()->onApply(this); + spendTime(1); +} + +bool Creature::canHide() const { + return skills.count(Skill::ambush) && getConstSquare()->canHide(); +} + +void Creature::hide() { + knownHiding.clear(); + viewObject.setHidden(true); + for (const Creature* c : getLevel()->getAllCreatures()) + if (c->canSee(this) && c->isEnemy(this)) { + knownHiding.insert(c); + if (!isBlind()) + you(MsgType::CAN_SEE_HIDING, c->getTheName()); + } + spendTime(1); + hidden = true; +} + +bool Creature::canChatTo(Vec2 direction) const { + return getConstSquare(direction)->getCreature(); +} + +void Creature::chatTo(Vec2 direction) { + CHECK(canChatTo(direction)); + Creature* c = getSquare(direction)->getCreature(); + c->onChat(this); + spendTime(1); +} + +void Creature::onChat(Creature* from) { + if (isEnemy(from) && chatReactionHostile) { + if (chatReactionHostile->front() == '\"') + from->privateMessage(*chatReactionHostile); + else + from->privateMessage(getTheName() + " " + *chatReactionHostile); + } + if (!isEnemy(from) && chatReactionFriendly) { + if (chatReactionFriendly->front() == '\"') + from->privateMessage(*chatReactionFriendly); + else + from->privateMessage(getTheName() + " " + *chatReactionFriendly); + } +} + +void Creature::stealFrom(Vec2 direction, const vector& items) { + Creature* c = NOTNULL(getSquare(direction)->getCreature()); + equipment.addItems(c->steal(items)); +} + +bool Creature::isHidden() const { + return hidden; +} + +bool Creature::knowsHiding(const Creature* c) const { + return knownHiding.count(c) == 1; +} + +void Creature::panic(double time) { + if (sleeping) + return; + enraged.unset(); + if (!panicking) + you(MsgType::PANIC, ""); + panicking.set(getTime() + time); +} + +void Creature::hallucinate(double time) { + if (!isBlind()) + privateMessage("The world explodes into colors!"); + hallucinating.set(getTime() + time); +} + +bool Creature::isHallucinating() const { + return hallucinating; +} + +void Creature::blind(double time) { + if (permanentlyBlind) + return; + if (!blinded) + you(MsgType::ARE, "blind!"); + viewObject.setBlind(true); + blinded.set(getTime() + time); +} + +bool Creature::isBlind() const { + return blinded || permanentlyBlind; +} + +void Creature::makeInvisible(double time) { + if (!isBlind()) + you(MsgType::TURN_INVISIBLE, ""); + viewObject.setInvisible(true); + invisible.set(getTime() + time); +} + +bool Creature::isInvisible() const { + return invisible; +} + +void Creature::rage(double time) { + if (sleeping) + return; + panicking.unset(); + if (!enraged) + you(MsgType::RAGE, ""); + enraged.set(getTime() + time); +} + +void Creature::giveStrBonus(double time) { + if (!strBonus) + you(MsgType::FEEL, "stronger"); + strBonus.set(getTime() + time); +} + +void Creature::giveDexBonus(double time) { + if (!dexBonus) + you(MsgType::FEEL, "more agile"); + dexBonus.set(getTime() + time); +} + +bool Creature::isPanicking() const { + return panicking; +} + +int Creature::getAttrVal(AttrType type) const { + switch (type) { + case AttrType::SPEED: return *speed + expLevel * 4; + case AttrType::DEXTERITY: return *dexterity + expLevel / 2; + case AttrType::STRENGTH: return *strength + (expLevel - 1) / 2; + default: return 0; + } +} + +int attrBonus = 3; + +int dexPenNoArm = 2; +int dexPenNoLeg = 10; +int dexPenNoWing = 5; + +int strPenNoArm = 1; +int strPenNoLeg = 3; +int strPenNoWing = 2; + +int Creature::getAttr(AttrType type) const { + int def = getAttrVal(type); + for (Item* item : equipment.getItems()) + if (equipment.isEquiped(item)) + def += item->getModifier(type); + switch (type) { + case AttrType::STRENGTH: + def *= 0.666 + health / 3; + if (sleeping) + def *= 0.66; + if (strBonus) + def += attrBonus; + def -= injuredArms * strPenNoArm + injuredLegs * strPenNoLeg + injuredWings * strPenNoWing; + break; + case AttrType::DEXTERITY: + def *= 0.666 + health / 3; + if (sleeping) + def = 0; + if (dexBonus) + def += attrBonus; + def -= injuredArms * dexPenNoArm + injuredLegs * dexPenNoLeg + injuredWings * dexPenNoWing; + break; + case AttrType::THROWN_DAMAGE: + case AttrType::DAMAGE: + def += getAttr(AttrType::STRENGTH); + if (!getWeapon()) + def += barehandedDamage; + if (panicking) + def -= attrBonus; + if (enraged) + def += attrBonus; + break; + case AttrType::DEFENSE: + def += getAttr(AttrType::STRENGTH); + if (panicking) + def += attrBonus; + if (enraged) + def -= attrBonus; + break; + case AttrType::THROWN_TO_HIT: + case AttrType::TO_HIT: + def += getAttr(AttrType::DEXTERITY); + break; + case AttrType::SPEED: { + double totWeight = getInventoryWeight(); + if (totWeight > getAttr(AttrType::STRENGTH)) + def -= 20.0 * totWeight / def; + if (slowed) + def /= 2; + if (speeding) + def *= 2; + break;} + case AttrType::INV_LIMIT: + return getAttr(AttrType::STRENGTH) * 2 * carryingMultiplier; + // default: + // break; + } + return max(0, def); +} + +double Creature::getInventoryWeight() const { + double ret = 0; + for (Item* item : getEquipment().getItems()) + ret += item->getWeight(); + return ret; +} + +Tribe* Creature::getTribe() const { + return tribe; +} + +bool Creature::isFriend(const Creature* c) const { + return !isEnemy(c); +} + +pair Creature::getStanding(const Creature* c) const { + double bestWeight = 0; + double standing = getTribe()->getStanding(c); + if (contains(privateEnemies, c)) { + standing = -1; + bestWeight = 1; + } + for (EnemyCheck* enemyCheck : enemyChecks) + if (enemyCheck->hasStanding(c) && enemyCheck->getWeight() > bestWeight) { + standing = enemyCheck->getStanding(c); + bestWeight = enemyCheck->getWeight(); + } + return make_pair(standing, bestWeight); +} + +void Creature::addEnemyCheck(EnemyCheck* c) { + enemyChecks.push_back(c); +} + +void Creature::removeEnemyCheck(EnemyCheck* c) { + removeElement(enemyChecks, c); +} + +bool Creature::isEnemy(const Creature* c) const { + pair myStanding = getStanding(c); + pair hisStanding = c->getStanding(this); + double standing = 0; + if (myStanding.second > hisStanding.second) + standing = myStanding.first; + if (myStanding.second < hisStanding.second) + standing = hisStanding.first; + if (myStanding.second == hisStanding.second) + standing = min(myStanding.first, hisStanding.first); + return c != this && standing < 0; +} + +vector Creature::getGold(int num) const { + vector ret; + for (Item* item : equipment.getItems([](Item* it) { return it->getType() == ItemType::GOLD; })) { + ret.push_back(item); + if (ret.size() == num) + return ret; + } + return ret; +} + +void Creature::setPosition(Vec2 pos) { + position = pos; +} + +void Creature::setLevel(Level* l) { + level = l; +} + +void Creature::slowDown(double duration) { + you(MsgType::ARE, "moving more slowly"); + speeding.unset(); + slowed.set(getTime() + duration); +} + +void Creature::speedUp(double duration) { + you(MsgType::ARE, "moving faster"); + slowed.unset(); + speeding.set(getTime() + duration); +} + +double Creature::getTime() const { + return time; +} + +void Creature::setTime(double t) { + time = t; +} + +void Creature::tick(double realTime) { + for (Item* item : equipment.getItems()) { + item->tick(time, level, position); + if (item->isDiscarded()) + equipment.removeItem(item); + } + if (slowed.isFinished(realTime)) + you(MsgType::ARE, "moving faster again"); + if (sleeping.isFinished(realTime)) + you(MsgType::WAKE_UP, ""); + if (speeding.isFinished(realTime)) + you(MsgType::ARE, "moving more slowly again"); + if (strBonus.isFinished(realTime)) + you(MsgType::ARE, "weaker again"); + if (dexBonus.isFinished(realTime)) + you(MsgType::FEEL, "less agile again"); + if (panicking.isFinished(realTime) || enraged.isFinished(realTime) || hallucinating.isFinished(realTime)) { + if (!hallucinating) + privateMessage("Your mind is clear again"); + else + privateMessage("Your brain is hurting a bit less."); + } + if (blinded.isFinished(realTime)) { + you("can see again"); + viewObject.setBlind(false); + } + if (invisible.isFinished(realTime)) { + you(MsgType::TURN_VISIBLE, ""); + viewObject.setInvisible(false); + } + double delta = realTime - lastTick; + lastTick = realTime; + updateViewObject(); + if (undead && numGoodArms() + numGoodLegs() + numGoodHeads() <= 2) { + you(MsgType::FALL_APART, ""); + die(lastAttacker); + return; + } + if (health < 0.5) + health -= delta / 40; + if (health <= 0) { + you(MsgType::DIE_OF_BLEEDING, ""); + die(lastAttacker); + } + +} + +BodyPart Creature::armOrWing() const { + if (arms == 0) + return BodyPart::WING; + if (wings == 0) + return BodyPart::ARM; + return chooseRandom({ BodyPart::WING, BodyPart::ARM }, {1, 1}); +} + +BodyPart Creature::getBodyPart(AttackLevel attack) const { + if (flyer) + return chooseRandom({BodyPart::TORSO, BodyPart::HEAD, BodyPart::LEG, BodyPart::WING, BodyPart::ARM}, {1, 1, 1, 2, 1}); + switch (attack) { + case AttackLevel::HIGH: + return BodyPart::HEAD; + case AttackLevel::MIDDLE: + if (size == CreatureSize::SMALL || size == CreatureSize::MEDIUM || collapsed) + return BodyPart::HEAD; + else + return chooseRandom({BodyPart::TORSO, armOrWing()}, {1, 1}); + case AttackLevel::LOW: + if (size == CreatureSize::SMALL || collapsed) + return chooseRandom({BodyPart::TORSO, armOrWing(), BodyPart::HEAD, BodyPart::LEG}, {1, 1, 1, 1}); + if (size == CreatureSize::MEDIUM) + return chooseRandom({BodyPart::TORSO, armOrWing(), BodyPart::LEG}, {1, 1, 3}); + else + return BodyPart::LEG; + } + return BodyPart::ARM; +} + +string getAttackParam(AttackType type) { + switch (type) { + case AttackType::CUT: return "cut"; + case AttackType::STAB: return "stab"; + case AttackType::CRUSH: return "crush"; + case AttackType::PUNCH: return "punch"; + case AttackType::BITE: return "bite"; + case AttackType::HIT: return "hit"; + case AttackType::SHOOT: return "shot"; + } + return ""; +} + +void Creature::injureLeg(bool drop) { + if (legs == 0) + return; + if (drop) { + --legs; + ++lostLegs; + if (injuredLegs > legs) + --injuredLegs; + } + else if (injuredLegs < legs) + ++injuredLegs; + if (!collapsed) + you(MsgType::COLLAPSE, ""); + collapsed = true; + if (drop) { + getSquare()->dropItem(ItemFactory::corpse(*name + " leg", "bone", *weight / 8, + isFood ? ItemType::FOOD : ItemType::CORPSE)); + } +} + +void Creature::injureArm(bool dropArm) { + if (dropArm) { + --arms; + ++lostArms; + if (injuredArms > arms) + --injuredArms; + } + else if (injuredArms < arms) + ++injuredArms; + string itemName = isPlayer() ? ("your arm") : (*name + " arm"); + if (getWeapon()) { + you(MsgType::DROP_WEAPON, getWeapon()->getName()); + level->getSquare(getPosition())->dropItem(equipment.removeItem(getWeapon())); + } + if (dropArm) + getSquare()->dropItem(ItemFactory::corpse(*name + " arm", "bone", *weight / 12, + isFood ? ItemType::FOOD : ItemType::CORPSE)); +} + +void Creature::injureWing(bool drop) { + if (drop) { + --wings; + ++lostWings; + if (injuredWings > wings) + --injuredWings; + } + else if (injuredWings < wings) + ++injuredWings; + if (flyer) { + you(MsgType::FALL, getSquare()->getName()); + flyer = false; + } + if ((legs < 2 || injuredLegs > 0) && !collapsed) { + collapsed = true; + } + string itemName = isPlayer() ? ("your wing") : (*name + " wing"); + if (drop) + getSquare()->dropItem(ItemFactory::corpse(*name + " wing", "bone", *weight / 12, + isFood ? ItemType::FOOD : ItemType::CORPSE)); +} + +void Creature::injureHead(bool drop) { + if (drop) { + --heads; + if (injuredHeads > heads) + --injuredHeads; + } + else if (injuredHeads < heads) + ++injuredHeads; + if (drop) + getSquare()->dropItem(ItemFactory::corpse(*name +" head", *name + " skull", *weight / 12, + isFood ? ItemType::FOOD : ItemType::CORPSE)); +} + +void Creature::attack(const Creature* c1) { + Creature* c = const_cast(c1); + int toHitVariance = 9; + int attackVariance = 6; + CHECK((c->getPosition() - getPosition()).length8() == 1) + << "Bad attack direction " << c->getPosition() - getPosition(); + CHECK(canAttack(c)); + Debug() << getTheName() << " attacking " << c->getName(); + int toHit = Random.getRandom(-toHitVariance, toHitVariance) + getAttr(AttrType::TO_HIT); + int damage = Random.getRandom(-attackVariance, attackVariance) + getAttr(AttrType::DAMAGE); + bool backstab = false; + string enemyName = getLevel()->playerCanSee(c) ? c->getTheName() : "something"; + if (c->isPlayer()) + enemyName = ""; + if (!c->canSee(this) && canSee(c)) { + if (getWeapon() && getWeapon()->getAttackType() == AttackType::STAB) { + damage += 15; + backstab = true; + } + you(MsgType::ATTACK_SURPRISE, enemyName); + } + Attack attack(this, getRandomAttackLevel(), getAttackType(), toHit, damage, backstab); + if (!c->dodgeAttack(attack)) { + if (getWeapon()) { + you(getWeapon()->getAttackType() == AttackType::STAB ? + MsgType::THRUST_WEAPON : MsgType::SWING_WEAPON, getWeapon()->getName()); + if (!canSee(c)) + privateMessage("You hit something."); + } + else { + if (isHumanoid()) { + you(attack.getLevel() == AttackLevel::LOW ? MsgType::KICK : MsgType::PUNCH, enemyName); + } + else + you(MsgType::BITE, enemyName); + } + c->takeDamage(attack); + } + else + you(MsgType::MISS_ATTACK, enemyName); + spendTime(1); +} + +bool Creature::dodgeAttack(Attack attack) { + Debug() << getTheName() << " dodging " << attack.getAttacker()->getName() << " to hit " << attack.getToHit() << " dodge " << getAttr(AttrType::TO_HIT); + if (const Creature* c = attack.getAttacker()) { + if (!canSee(c)) + unknownAttacker.push_back(c); + EventListener::addAttackEvent(this, c); + if (!contains(privateEnemies, c)) + privateEnemies.push_back(c); + } + return canSee(attack.getAttacker()) && attack.getToHit() <= getAttr(AttrType::TO_HIT); +} + +bool Creature::takeDamage(Attack attack) { + if (sleeping) + wakeUp(); + int defense = getAttr(AttrType::DEFENSE); + Debug() << getTheName() << " attacked by " << attack.getAttacker()->getName() << " damage " << attack.getStrength() << " defense " << defense; + if (attack.getStrength() > defense) { + lastAttacker = attack.getAttacker(); + double dam = (defense == 0) ? 1 : double(attack.getStrength() - defense) / defense; + dam *= damageMultiplier; + if (!undead) + bleed(dam); + if (!noBody) { + BodyPart part = attack.inTheBack() ? BodyPart::BACK : getBodyPart(attack.getLevel()); + switch (part) { + case BodyPart::BACK: + youHit(part, attack.getType()); + break; + case BodyPart::WING: + if (dam >= 0.3 && wings > injuredWings) { + youHit(BodyPart::WING, attack.getType()); + injureWing(attack.getType() == AttackType::CUT || attack.getType() == AttackType::BITE); + if (health <= 0) + health = 0.01; + return false; + } + case BodyPart::ARM: + if (dam >= 0.5 && arms > injuredArms) { + youHit(BodyPart::ARM, attack.getType()); + injureArm(attack.getType() == AttackType::CUT || attack.getType() == AttackType::BITE); + if (health <= 0) + health = 0.01; + return false; + } + case BodyPart::LEG: + if (dam >= 0.8 && legs > injuredLegs) { + youHit(BodyPart::LEG, attack.getType()); + injureLeg(attack.getType() == AttackType::CUT || attack.getType() == AttackType::BITE); + if (health <= 0) + health = 0.01; + return false; + } + case BodyPart::HEAD: + if (dam >= 0.8 && heads > injuredHeads) { + youHit(BodyPart::HEAD, attack.getType()); + injureHead(attack.getType() == AttackType::CUT || attack.getType() == AttackType::BITE); + if (!undead) { + you(MsgType::DIE, ""); + die(attack.getAttacker()); + } + return true; + } + case BodyPart::TORSO: + if (dam >= 1.5) { + youHit(BodyPart::TORSO, attack.getType()); + if (!undead) + you(MsgType::DIE, ""); + die(attack.getAttacker()); + return true; + } + break; + } + } + if (health <= 0) { + you(MsgType::ARE, "critically wounded"); + you(MsgType::DIE, ""); + die(attack.getAttacker()); + return true; + } else + if (health < 0.5) + you(MsgType::ARE, "critically wounded"); + else + you(MsgType::ARE, "wounded"); + } else + you(MsgType::GET_HIT_NODAMAGE, getAttackParam(attack.getType())); + const Creature* c = attack.getAttacker(); + return false; +} + +void Creature::updateViewObject() { + if (const Creature* c = getLevel()->getPlayer()) + if (isEnemy(c)) + viewObject.setHostile(true); + viewObject.setBleeding(1 - health); +} + +double Creature::getHealth() const { + return health; +} + +double Creature::getWeight() const { + return *weight; +} + +string sizeStr(CreatureSize s) { + switch (s) { + case CreatureSize::SMALL: return "small"; + case CreatureSize::MEDIUM: return "medium"; + case CreatureSize::LARGE: return "large"; + case CreatureSize::HUGE: return "huge"; + } + return 0; +} + + +string limbsStr(int arms, int legs, int wings) { + vector ret; + if (arms) + ret.push_back("arms"); + if (legs) + ret.push_back("legs"); + if (wings) + ret.push_back("wings"); + if (ret.size() > 0) + return " with " + combine(ret); + else + return ""; +} + +string attrStr(bool strong, bool agile, bool fast) { + vector good; + vector bad; + if (strong) + good.push_back("strong"); + else + bad.push_back("weak"); + if (agile) + good.push_back("agile"); + else + bad.push_back("clumsy"); + if (fast) + good.push_back("fast"); + else + bad.push_back("slow"); + string p1 = combine(good); + string p2 = combine(bad); + if (p1.size() > 0 && p2.size() > 0) + p1.append(", but "); + p1.append(p2); + return p1; +} + +bool Creature::isSpecialMonster() const { + return specialMonster; +} + +string Creature::getDescription() const { + string weapon; + /*if (Item* item = getEquipment().getItem(EquipmentSlot::WEAPON)) + weapon = " It's wielding " + item->getAName() + "."; + else + if (Item* item = getEquipment().getItem(EquipmentSlot::RANGED_WEAPON)) + weapon = " It's wielding " + item->getAName() + ".";*/ + return getTheName() + " is a " + sizeStr(*size) + (isHumanoid() ? " humanoid creature" : " beast") + + (!isHumanoid() ? limbsStr(arms, legs, wings) : (wings ? " with wings" : "")) + ". " + + "It is " + attrStr(*strength > 16, *dexterity > 16, *speed > 100) + "." + weapon; +} + +void Creature::setSpeed(double value) { + speed = value; +} + +double Creature::getSpeed() const { + return *speed; +} +void Creature::heal(double amount, bool replaceLimbs) { + Debug() << getTheName() << " heal"; + if (health < 1) { + health = min(1., health + amount); + if (health >= 0.5) { + if (injuredArms > 0) { + you(MsgType::YOUR, string(injuredArms > 1 ? "arms are" : "arm is") + " in better shape"); + injuredArms = 0; + } + if (lostArms > 0 && replaceLimbs) { + you(MsgType::YOUR, string(lostArms > 1 ? "arms grow back!" : "arm grows back!")); + arms += lostArms; + lostArms = 0; + } + if (injuredWings > 0) { + you(MsgType::YOUR, string(injuredArms > 1 ? "wings are" : "wing is") + " in better shape"); + injuredWings = 0; + } + if (lostWings > 0 && replaceLimbs) { + you(MsgType::YOUR, string(lostArms > 1 ? "wings grow back!" : "wing grows back!")); + wings += lostWings; + lostWings = 0; + flyer = true; + } + if (injuredLegs > 0) { + you(MsgType::YOUR, string(injuredLegs > 1 ? "legs are" : "leg is") + " in better shape"); + injuredLegs = 0; + if (legs == 2 && collapsed) { + collapsed = false; + you(MsgType::STAND_UP, ""); + } + } + if (lostLegs > 0 && replaceLimbs) { + you(MsgType::YOUR, string(lostLegs > 1 ? "legs grow back!" : "leg grows back!")); + legs += lostLegs; + lostLegs = 0; + } + } + if (health == 1) { + you(MsgType::BLEEDING_STOPS, ""); + health = 1; + lastAttacker = nullptr; + } + updateViewObject(); + } +} + +void Creature::bleed(double severity) { + updateViewObject(); + health -= severity; + updateViewObject(); + Debug() << getTheName() << " health " << health; +} + +void Creature::setOnFire(double amount) { + if (!fireResistant) { + you(MsgType::ARE, "burnt by the fire"); + bleed(6. * amount / double(getAttr(AttrType::STRENGTH))); + } +} + +void Creature::poisonWithGas(double amount) { + if (breathing && !undead) { + you(MsgType::ARE, "poisoned by the gas"); + bleed(amount / double(getAttr(AttrType::STRENGTH))); + } +} + +void Creature::shineLight() { + if (undead) { + if (Random.roll(10)) { + you(MsgType::YOUR, "body crumbles to dust"); + die(nullptr); + } else + you(MsgType::ARE, "burnt by the sun"); + } +} + +void Creature::setHeld(const Creature* c) { + holding = c; +} + +bool Creature::isHeld() const { + return holding != nullptr; +} + +void Creature::sleep(int time) { + if (!noSleep) + sleeping.set(getTime() + time); +} + +bool Creature::isSleeping() const { + return sleeping; +} + +void Creature::wakeUp() { + you(MsgType::WAKE_UP, ""); + sleeping.unset(); +} + +void Creature::take(vector items) { + for (PItem& elem : items) + take(std::move(elem)); +} + +void Creature::take(PItem item) { + /* item->identify(); + Debug() << (specialMonster ? "special monster " : "") + getTheName() << " takes " << item->getNameAndModifiers();*/ + if (item->isWieldedTwoHanded()) + addSkill(Skill::twoHandedWeapon); + if (item->getType() == ItemType::RANGED_WEAPON) + addSkill(Skill::archery); + Item* ref = item.get(); + equipment.addItem(std::move(item)); + if (canEquip(ref)) + equip(ref); +} + +void Creature::dropCorpse() { + getSquare()->dropItem(ItemFactory::corpse(*name + " corpse", *name + " skeleton", *weight, + isFood ? ItemType::FOOD : ItemType::CORPSE)); +} + +void Creature::die(const Creature* attacker, bool dropInventory) { + Debug() << getTheName() << " dies."; + controller->onKilled(attacker); + if (dropInventory) + for (PItem& item : equipment.removeAllItems()) { + level->getSquare(position)->dropItem(std::move(item)); + } + dead = true; + if (dropInventory && !noBody) + dropCorpse(); + level->killCreature(this); + EventListener::addKillEvent(this, attacker); +} + +bool Creature::canFlyAway() const { + return canFly() && !getConstSquare()->isCovered(); +} + +void Creature::flyAway() { + Debug() << getTheName() << " fly away"; + CHECK(canFlyAway()); + globalMessage(getTheName() + " flies away."); + dead = true; + level->killCreature(this); +} + +void Creature::give(const Creature* whom, vector items) { + CHECK(whom->wantsItems(this, items)); + getLevel()->getSquare(whom->getPosition())->getCreature()->takeItems(this, equipment.removeItems(items)); +} + +bool Creature::canFire(Vec2 direction) const { + CHECK(direction.length8() == 1); + if (!getEquipment().getItem(EquipmentSlot::RANGED_WEAPON)) + return false; + if (!hasSkill(Skill::archery)) { + privateMessage("You don't have the skill to shoot a bow."); + return false; + } + if (numGoodArms() < 2) { + privateMessage("You need two hands to shoot a bow."); + return false; + } + if (!getAmmo()) { + privateMessage("Out of ammunition"); + return false; + } + return true; +} + +void Creature::fire(Vec2 direction) { + CHECK(canFire(direction)); + PItem ammo = equipment.removeItem(NOTNULL(getAmmo())); + RangedWeapon* weapon = NOTNULL(dynamic_cast(getEquipment().getItem(EquipmentSlot::RANGED_WEAPON))); + weapon->fire(this, level, std::move(ammo), direction); + spendTime(1); +} + +void Creature::squash(Vec2 direction) { + if (canDestroy(direction)) + destroy(direction); + Creature* c = getSquare(direction)->getCreature(); + if (c) { + c->you(MsgType::KILLED_BY, getTheName()); + c->die(); + } +} + +void Creature::construct(Vec2 direction, SquareType type) { + getSquare(direction)->construct(type); + spendTime(1); +} + +bool Creature::canConstruct(Vec2 direction, SquareType type) const { + return getConstSquare(direction)->canConstruct(type) && canConstruct(type); +} + +bool Creature::canConstruct(SquareType type) const { + return hasSkill(Skill::construction); +} + +void Creature::eat(Item* item) { + getSquare()->removeItem(item); + spendTime(3); +} + +bool Creature::canDestroy(Vec2 direction) const { + return getConstSquare(direction)->canDestroy(); +} + +void Creature::destroy(Vec2 direction) { + getSquare(direction)->destroy(getAttr(AttrType::STRENGTH)); + spendTime(1); +} + +bool Creature::canAttack(const Creature* c) const { + Vec2 direction = c->getPosition() - getPosition(); + return direction.length8() == 1; +} + +AttackLevel Creature::getRandomAttackLevel() const { + if (isHumanoid() && injuredArms == arms) + return AttackLevel::LOW; + switch (*size) { + case CreatureSize::SMALL: return AttackLevel::LOW; + case CreatureSize::MEDIUM: return chooseRandom({AttackLevel::LOW, AttackLevel::MIDDLE}, {1,1}); + case CreatureSize::LARGE: return chooseRandom({AttackLevel::LOW, AttackLevel::MIDDLE, AttackLevel::HIGH},{1,2,2}); + case CreatureSize::HUGE: return chooseRandom({AttackLevel::MIDDLE, AttackLevel::HIGH}, {1,3}); + } + return AttackLevel::LOW; +} + +Item* Creature::getWeapon() const { + return equipment.getItem(EquipmentSlot::WEAPON); +} + +AttackType Creature::getAttackType() const { + if (getWeapon()) + return getWeapon()->getAttackType(); + else + return isHumanoid() ? AttackType::PUNCH : AttackType::BITE; +} + +void Creature::applyItem(Item* item) { + Debug() << getTheName() << " applying " << item->getAName(); + CHECK(canApplyItem(item)); + double time = item->getApplyTime(); + item->apply(this, level); + if (item->isDiscarded()) { + equipment.removeItem(item); + } + spendTime(time); +} + +bool Creature::canApplyItem(Item* item) const { + if (!isHumanoid()) + return false; + if (!numGoodArms()) { + privateMessage("You don't have hands!"); + return false; + } else + return true; +} + +bool Creature::canThrowItem(Item* item) { + if (injuredArms == arms || !isHumanoid()) { + privateMessage("You can't throw anything!"); + return false; + } else if (item->getWeight() > 20) { + privateMessage(item->getTheName() + " is too heavy!"); + return false; + } + return true; +} + +void Creature::throwItem(Item* item, Vec2 direction) { + Debug() << getTheName() << " throwing " << item->getAName(); + CHECK(canThrowItem(item)); + int dist = 0; + int toHitVariance = 10; + int attackVariance = 7; + int str = getAttr(AttrType::STRENGTH); + if (item->getWeight() <= 0.5) + dist = 10 * str / 15; + else if (item->getWeight() <= 5) + dist = 5 * str / 15; + else if (item->getWeight() <= 20) + dist = 2 * str / 15; + else + Debug(FATAL) << "Item too heavy."; + int toHit = Random.getRandom(-toHitVariance, toHitVariance) + + getAttr(AttrType::THROWN_TO_HIT) + item->getModifier(AttrType::THROWN_TO_HIT); + int damage = Random.getRandom(-attackVariance, attackVariance) + + getAttr(AttrType::THROWN_DAMAGE) + item->getModifier(AttrType::THROWN_DAMAGE); + if (hasSkill(Skill::knifeThrowing) && item->getAttackType() == AttackType::STAB) { + damage += 7; + toHit += 4; + } + Attack attack(this, getRandomAttackLevel(), item->getAttackType(), toHit, damage); + level->throwItem(equipment.removeItem(item), attack, dist, getPosition(), direction); + spendTime(1); +} + +const ViewObject& Creature::getViewObject() const { + return viewObject; +} + +void Creature::setViewObject(const ViewObject& obj) { + viewObject = obj; +} + +bool Creature::canSee(const Creature* c) const { + return !isBlind() && !c->isInvisible() && + (!c->isHidden() || c->knowsHiding(this)) && + getLevel()->canSee(position, c->getPosition()); +} + +bool Creature::canSee(Vec2 pos) const { + return !isBlind() && + getLevel()->canSee(position, pos); +} + +bool Creature::isPlayer() const { + return controller->isPlayer(); +} + +string Creature::getTheName() const { + if (islower((*name)[0])) + return "the " + *name; + return *name; +} + +string Creature::getAName() const { + if (islower((*name)[0])) + return "a " + *name; + return *name; +} + +Optional Creature::getFirstName() const { + return firstName; +} + +string Creature::getName() const { + return *name; +} + +bool Creature::isHumanoid() const { + return *humanoid; +} + +bool Creature::isAnimal() const { + return animal; +} + +bool Creature::isStationary() const { + return stationary; +} + +void Creature::setStationary() { + stationary = true; +} + +bool Creature::isInvincible() const { + return invincible; +} + +bool Creature::isUndead() const { + return undead; +} + +bool Creature::canSwim() const { + return contains(skills, Skill::swimming); +} + +bool Creature::canFly() const { + return flyer; +} + +bool Creature::canWalk() const { + return walker; +} + +int Creature::numArms() const { + return arms; +} + +int Creature::numLegs() const { + return legs; +} + +int Creature::numWings() const { + return wings; +} + +bool Creature::lostLimbs() const { + return lostWings > 0 || lostArms > 0 || lostLegs > 0; +} + +int Creature::numGoodArms() const { + return arms - injuredArms; +} + +int Creature::numGoodLegs() const { + return legs - injuredLegs; +} + +int Creature::numGoodHeads() const { + return heads - injuredHeads; +} + +double Creature::getCourage() const { + return courage; +} + +void Creature::increaseExpLevel() { + if (expLevel < maxLevel) { + ++expLevel; + viewObject.setSizeIncrease(0.3); + if (skillGain.count(expLevel)) + addSkill(skillGain.at(expLevel)); + } +} + +int Creature::getExpLevel() const { + return expLevel; +} + +Optional Creature::getMoveTowards(Vec2 pos, bool away) { + Debug() << "" << getPosition() << (away ? "Moving away from" : " Moving toward ") << pos; + bool newPath = false; + if (!shortestPath || shortestPath->getTarget() != pos || shortestPath->isReversed() != away) { + newPath = true; + if (!away) + shortestPath = ShortestPath(getLevel(), this, pos, getPosition()); + else + shortestPath = ShortestPath(getLevel(), this, pos, getPosition(), -1.5); + } + CHECK(shortestPath); + if (shortestPath->isReachable(getPosition())) { + Vec2 pos2 = shortestPath->getNextMove(getPosition()); + if (canMove(pos2 - getPosition())) { + return pos2 - getPosition(); + } + } + if (newPath) + return Nothing(); + Debug() << "Reconstructing shortest path."; + if (!away) + shortestPath = ShortestPath(getLevel(), this, pos, getPosition()); + else + shortestPath = ShortestPath(getLevel(), this, pos, getPosition(), -1.5); + if (shortestPath->isReachable(getPosition())) { + Vec2 pos2 = shortestPath->getNextMove(getPosition()); + if (canMove(pos2 - getPosition())) { + return pos2 - getPosition(); + } else + return Nothing(); + } else { + Debug() << "Cannot move toward " << pos; + return Nothing(); + } +} + +Optional Creature::getMoveAway(Vec2 pos, bool pathfinding) { + if ((pos - getPosition()).length8() <= 5 && pathfinding) { + Optional move = getMoveTowards(pos, true); + if (move) + return move; + } + pair dirs = (getPosition() - pos).approxL1(); + vector moves; + if (canMove(dirs.first)) + moves.push_back(dirs.first); + if (canMove(dirs.second)) + moves.push_back(dirs.second); + if (moves.size() > 0) + return moves[Random.getRandom(moves.size())]; + return Nothing(); +} + +bool Creature::atTarget() const { + return shortestPath && getPosition() == shortestPath->getTarget(); +} + +void Creature::youHit(BodyPart part, AttackType type) const { + switch (part) { + case BodyPart::BACK: + switch (type) { + case AttackType::SHOOT: you(MsgType::ARE, "shot in the spine!"); break; + case AttackType::BITE: you(MsgType::YOUR, "head is bitten off!"); break; + case AttackType::CUT: you(MsgType::YOUR, "head is choped off!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "skull is shattered!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "neck is broken!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the back of the head!"); break; + case AttackType::STAB: you(MsgType::ARE, "stabbed in the " + + chooseRandom({"back", "neck"})); break; + } + break; + case BodyPart::HEAD: + switch (type) { + case AttackType::SHOOT: you(MsgType::YOUR, "shot in the " + + chooseRandom({"eye", "neck", "forehead"}) + "!"); break; + case AttackType::BITE: you(MsgType::YOUR, "head is bitten off!"); break; + case AttackType::CUT: you(MsgType::YOUR, "head is choped off!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "skull is shattered!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "neck is broken!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the head!"); break; + case AttackType::STAB: you(MsgType::ARE, "stabbed in the eye!"); break; + } + break; + case BodyPart::TORSO: + switch (type) { + case AttackType::SHOOT: you(MsgType::YOUR, "shot in the heart!"); break; + case AttackType::BITE: you(MsgType::YOUR, "internal organs are ripped out!"); break; + case AttackType::CUT: you(MsgType::ARE, "cut in half!"); break; + case AttackType::STAB: you(MsgType::ARE, "stabbed in the " + + chooseRandom({"stomach", "heart"}, {1, 1}) + "!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "ribs and internal organs are crushed!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the chest!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "stomach receives a deadly blow!"); break; + } + break; + case BodyPart::ARM: + switch (type) { + case AttackType::SHOOT: you(MsgType::YOUR, "shot in the arm!"); break; + case AttackType::BITE: you(MsgType::YOUR, "arm is bitten off!"); break; + case AttackType::CUT: you(MsgType::YOUR, "arm is choped off!"); break; + case AttackType::STAB: you(MsgType::ARE, "stabbed in the arm!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "arm is smashed!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the arm!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "arm is broken!"); break; + } + break; + case BodyPart::WING: + switch (type) { + case AttackType::SHOOT: you(MsgType::YOUR, "shot in the wing!"); break; + case AttackType::BITE: you(MsgType::YOUR, "wing is bitten off!"); break; + case AttackType::CUT: you(MsgType::YOUR, "wing is choped off!"); break; + case AttackType::STAB: you(MsgType::ARE, "stabbed in the wing!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "wing is smashed!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the wing!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "wing is broken!"); break; + } + break; + case BodyPart::LEG: + switch (type) { + case AttackType::SHOOT: you(MsgType::YOUR, "shot in the leg!"); break; + case AttackType::BITE: you(MsgType::YOUR, "leg is bitten off!"); break; + case AttackType::CUT: you(MsgType::YOUR, "leg is cut off!"); break; + case AttackType::STAB: you(MsgType::YOUR, "stabbed in the leg!"); break; + case AttackType::CRUSH: you(MsgType::YOUR, "knee is crushed!"); break; + case AttackType::HIT: you(MsgType::ARE, "hit in the leg!"); break; + case AttackType::PUNCH: you(MsgType::YOUR, "leg is broken!"); break; + } + break; + } +} + +vector Creature::getUnknownAttacker() const { + return unknownAttacker; +} + +void Creature::refreshGameInfo(View::GameInfo& gameInfo) const { + gameInfo.infoType = View::GameInfo::InfoType::PLAYER; + View::GameInfo::PlayerInfo& info = gameInfo.playerInfo; + info.speed = getAttr(AttrType::SPEED); + if (firstName) + info.playerName = *firstName; + info.title = *name; + info.adjectives.clear(); + if (isBlind()) + info.adjectives.push_back("blind"); + if (isInvisible()) + info.adjectives.push_back("invisible"); + if (numArms() == 1) + info.adjectives.push_back("one-armed"); + if (numArms() == 0) + info.adjectives.push_back("armless"); + if (numLegs() == 1) + info.adjectives.push_back("one-legged"); + if (numLegs() == 0) + info.adjectives.push_back("legless"); + if (isHallucinating()) + info.adjectives.push_back("tripped"); + Item* weapon = getEquipment().getItem(EquipmentSlot::WEAPON); + info.weaponName = weapon ? weapon->getAName() : ""; + const Location* location = getLevel()->getLocation(getPosition()); + info.levelName = location && location->hasName() + ? capitalFirst(location->getName()) : getLevel()->getName(); + info.defense = getAttr(AttrType::DEFENSE); + info.bleeding = getHealth() < 1; + info.attack = getAttr(AttrType::DAMAGE); + info.strength = getAttr(AttrType::STRENGTH); + info.dexterity = getAttr(AttrType::DEXTERITY); + info.time = getTime(); + info.numGold = getGold(100000000).size(); + info.elfStanding = Tribe::elven->getStanding(this); + info.dwarfStanding = Tribe::dwarven->getStanding(this); + info.goblinStanding = Tribe::goblin->getStanding(this); + } + diff --git a/creature.h b/creature.h new file mode 100644 index 000000000..8014402cb --- /dev/null +++ b/creature.h @@ -0,0 +1,279 @@ +#ifndef _CREATURE_H +#define _CREATURE_H + +#include "creature_attributes.h" +#include "view_object.h" +#include "equipment.h" +#include "enums.h" +#include "attack.h" +#include "shortest_path.h" +#include "tribe.h" +#include "timer_var.h" +#include "skill.h" +#include "map_memory.h" +#include "creature_view.h" +#include "controller.h" + +class Level; +class Tribe; + +class Creature : private CreatureAttributes, public CreatureView { + public: + typedef CreatureAttributes CreatureAttributes; + Creature(ViewObject o, Tribe* tribe, const CreatureAttributes& attr, ControllerFactory); + virtual ~Creature() {} + + static Creature* getDefault(); + + const ViewObject& getViewObject() const; + virtual ViewIndex getViewIndex(Vec2 pos) const override; + void makeMove(); + double getTime() const; + void setTime(double t); + void setLevel(Level* l); + virtual const Level* getLevel() const; + Level* getLevel(); + const Square* getConstSquare() const; + const Square* getConstSquare(Vec2 direction) const; + Square* getSquare(); + Square* getSquare(Vec2 direction); + void setPosition(Vec2 pos); + virtual Vec2 getPosition() const override; + bool dodgeAttack(Attack); + bool takeDamage(Attack); + void heal(double amount = 1, bool replaceLimgs = false); + double getHealth() const; + double getWeight() const; + void sleep(int time); + bool isSleeping() const; + void wakeUp(); + void take(PItem item); + void take(vector item); + const Equipment& getEquipment() const; + vector steal(const vector items); + int getUniqueId() const; + virtual bool canSee(const Creature*) const override; + virtual bool canSee(Vec2 pos) const override; + void slowDown(double duration); + void speedUp(double duration); + + void tick(double realTime); + + string getTheName() const; + string getAName() const; + string getName() const; + Optional getFirstName() const; + int getAttr(AttrType) const; + + Tribe* getTribe() const; + bool isFriend(const Creature*) const; + bool isEnemy(const Creature*) const; + void addEnemyCheck(EnemyCheck*); + void removeEnemyCheck(EnemyCheck*); + int getDebt(const Creature* debtor) const; + vector getGold(int num) const; + + bool wantsItems(const Creature* from, vector items) const; + void takeItems(const Creature* from, vector items); + + void youHit(BodyPart part, AttackType type) const; + + virtual void dropCorpse(); + + void globalMessage(const string& playerCanSee, const string& cant = "") const; + const vector& getVisibleEnemies() const; + vector getVisibleCreatures() const; + + bool isDead() const; + bool isHumanoid() const; + bool isAnimal() const; + bool isStationary() const; + void setStationary(); + bool isInvincible() const; + bool isUndead() const; + bool canSwim() const; + bool canFly() const; + bool canWalk() const; + int numArms() const; + int numLegs() const; + int numWings() const; + bool lostLimbs() const; + int numGoodArms() const; + int numGoodLegs() const; + int numGoodHeads() const; + double getCourage() const; + + void increaseExpLevel(); + int getExpLevel() const; + + string getDescription() const; + bool isSpecialMonster() const; + + void addSkill(Skill* skill); + bool hasSkill(Skill*) const; + bool hasSkillToUseWeapon(const Item*) const; + + bool canMove(Vec2 direction) const; + void move(Vec2 direction); + bool canSwapPosition(Vec2 direction) const; + void swapPosition(Vec2 direction); + void wait(); + vector getPickUpOptions() const; + bool canPickUp(const vector& item) const; + void pickUp(const vector& item); + void drop(const vector& item); + void drop(vector item); + bool canAttack(const Creature*) const; + void attack(const Creature*); + bool canBumpInto(Vec2 direction) const; + void bumpInto(Vec2 direction); + void applyItem(Item* item); + bool canApplyItem(Item* item) const; + void startEquipChain(); + void finishEquipChain(); + void equip(Item* item); + void unequip(Item* item); + bool canEquip(const Item* item) const; + bool canUnequip(const Item* item) const; + bool canThrowItem(Item*); + void throwItem(Item*, Vec2 direction); + bool canHeal(Vec2 direction) const; + void heal(Vec2 direction); + void applySquare(); + bool canHide() const; + void hide(); + bool isHidden() const; + bool knowsHiding(const Creature*) const; + void panic(double time); + void rage(double time); + void hallucinate(double time); + bool isHallucinating() const; + void blind(double time); + bool isBlind() const; + void makeInvisible(double time); + bool isInvisible() const; + void giveStrBonus(double time); + void giveDexBonus(double time); + bool canFlyAway() const; + void flyAway(); + bool canChatTo(Vec2 direction) const; + void chatTo(Vec2 direction); + void stealFrom(Vec2 direction, const vector&); + void give(const Creature* whom, vector items); + bool canFire(Vec2 direction) const; + void fire(Vec2 direction); + void squash(Vec2 direction); + void construct(Vec2 direction, SquareType); + bool canConstruct(Vec2 direction, SquareType) const; + bool canConstruct(SquareType) const; + void eat(Item*); + bool canDestroy(Vec2 direction) const; + void destroy(Vec2 direction); + + virtual void onChat(Creature*); + + bool isPanicking() const; + Item* getWeapon() const; + + Optional getMoveTowards(Vec2 pos, bool away = false); + Optional getMoveAway(Vec2 pos, bool pathfinding = true); + bool atTarget() const; + void die(const Creature* attacker = nullptr, bool dropInventory = true); + void bleed(double severity); + void setOnFire(double amount); + void poisonWithGas(double amount); + void shineLight(); + void setHeld(const Creature* holding); + bool isHeld() const; + + virtual vector getUnknownAttacker() const override; + virtual void refreshGameInfo(View::GameInfo&) const override; + + void you(MsgType type, const string& param) const; + void you(const string& param) const; + void privateMessage(const string& message) const; + bool isPlayer() const; + void onItemsAppeared(vector items); + const MapMemory& getMemory(const Level* l = nullptr) const; + void grantIdentify(int numItems); + + Controller* getController(); + void pushController(Controller*); + void popController(); + bool canPopController(); + void setSpeed(double); + double getSpeed() const; + + protected: + void setViewObject(const ViewObject&); + + private: + double getInventoryWeight() const; + Item* getAmmo() const; + void updateViewObject(); + int getStrengthAttackBonus() const; + int getAttrVal(AttrType type) const; + int getToHit() const; + BodyPart getBodyPart(AttackLevel attack) const; + void injureLeg(bool drop); + void injureArm(bool drop); + void injureWing(bool drop); + void injureHead(bool drop); + AttackLevel getRandomAttackLevel() const; + AttackType getAttackType() const; + void spendTime(double time); + BodyPart armOrWing() const; + void updateVisibleEnemies(); + pair getStanding(const Creature* c) const; + + ViewObject viewObject; + Level* level = nullptr; + Vec2 position; + double time; + Equipment equipment; + int uniqueId; + Optional shortestPath; + unordered_set knownHiding; + Tribe* tribe; + unordered_map standingOverride; + vector enemyChecks; + double health = 1; + bool dead; + double lastTick; + bool collapsed = false; + int injuredArms = 0; + int injuredLegs = 0; + int injuredWings = 0; + int injuredHeads = 0; + int lostArms = 0; + int lostLegs = 0; + int lostWings = 0; + int lostHeads = 0; + bool hidden = false; + bool inEquipChain = false; + int numEquipActions = 0; + Optional homePos; + const Creature* leader = nullptr; + const Creature* lastAttacker = nullptr; + int swapPositionCooldown = 0; + TimerVar sleeping; + TimerVar panicking; + TimerVar enraged; + TimerVar slowed; + TimerVar speeding; + TimerVar strBonus; + TimerVar dexBonus; + TimerVar hallucinating; + TimerVar blinded; + TimerVar invisible; + int expLevel = 1; + vector unknownAttacker; + vector visibleEnemies; + vector privateEnemies; + const Creature* holding = nullptr; + PController controller; + stack controllerStack; +}; + + +#endif diff --git a/creature_attributes.h b/creature_attributes.h new file mode 100644 index 000000000..81342ecf5 --- /dev/null +++ b/creature_attributes.h @@ -0,0 +1,60 @@ +#ifndef _CREATURE_ATTRIBUTES_H +#define _CREATURE_ATTRIBUTES_H + +#include +#include + +#include "util.h" +#include "skill.h" + +// WTF is this defined +#undef HUGE + +enum class CreatureSize { SMALL, MEDIUM, LARGE, HUGE }; + +#define CATTR(X) CreatureAttributes([&](CreatureAttributes& c) { X }) + +class CreatureAttributes { + public: + CreatureAttributes(function fun) { + fun(*this); + } + + MustInitialize name; + MustInitialize speed; + MustInitialize size; + MustInitialize strength; + MustInitialize dexterity; + MustInitialize weight; + Optional chatReactionFriendly; + Optional chatReactionHostile; + Optional firstName; + bool specialMonster = false; + int barehandedDamage = 0; + int legs = 2; + int arms = 2; + int wings = 0; + int heads = 1; + bool noBody = false; + bool fireResistant = false; + bool breathing = true; + MustInitialize humanoid; + bool animal = false; + bool healer = false; + bool flyer = false; + bool undead = false; + bool walker = true; + bool isFood = false; + bool stationary = false; + bool noSleep = false; + double courage = 1; + int maxLevel = 10; + double carryingMultiplier = 1; + bool permanentlyBlind = false; + bool invincible = false; + double damageMultiplier = 1; + unordered_set skills; + map skillGain {{4, Skill::twoHandedWeapon}, {6, Skill::knifeThrowing}, {10, Skill::archery}}; +}; + +#endif diff --git a/creature_factory.cpp b/creature_factory.cpp new file mode 100644 index 000000000..8ab6832be --- /dev/null +++ b/creature_factory.cpp @@ -0,0 +1,1439 @@ +#include "stdafx.h" + +using namespace std; + +static map > creatureMap; +static map > inventoryMap; +static vector keys; +static map > levelRange; + +class BoulderController : public Monster { + public: + BoulderController(Creature* c, Vec2 dir) : Monster(c, MonsterAIFactory::idle()), direction(dir) {} + + BoulderController(Creature* c, Tribe* _myTribe) : Monster(c, MonsterAIFactory::idle()), + stopped(true), myTribe(_myTribe) {} + + virtual void makeMove() override { + if (myTribe != nullptr && stopped) { + for (Vec2 v : Vec2::directions8(true)) { + int radius = 6; + bool found = false; + for (int i = 1; i <= radius; ++i) { + if (!(creature->getPosition() + (v * i)).inRectangle(creature->getLevel()->getBounds())) + break; + if (Creature* other = creature->getSquare(v * i)->getCreature()) + if (other->getTribe() != myTribe) { + direction = v; + stopped = false; + found = true; + EventListener::addTriggerEvent(creature->getLevel(), creature->getPosition()); + break; + } + if (!creature->getSquare(v * i)->canEnter(creature)) + break; + } + if (found) + break; + } + } + if (stopped) { + creature->wait(); + return; + } + if (creature->getConstSquare(direction)->getStrength() < 300) + creature->squash(direction); + if (creature->canMove(direction)) + creature->move(direction); + else { + creature->getLevel()->globalMessage(creature->getPosition() + direction, + creature->getTheName() + " crashes on the " + creature->getConstSquare(direction)->getName(), + "You hear a crash"); + creature->die(); + } + if (creature->isDead()) + return; + double speed = creature->getSpeed(); + double deceleration = 0.1; + speed -= deceleration * 100 * 100 / speed; + if (speed < 30) { + if (myTribe) { + creature->die(); + return; + } + speed = 100; + stopped = true; + creature->setStationary(); + myTribe = nullptr; + } + creature->setSpeed(speed); + } + + virtual void you(MsgType type, const string& param) const override { + string msg, msgNoSee; + switch (type) { + case MsgType::BURN: msg = creature->getTheName() + " burns in the " + param; break; + case MsgType::DROWN: msg = creature->getTheName() + " falls into the " + param; + msgNoSee = "You hear a loud splash"; break; + case MsgType::KILLED_BY: msg = creature->getTheName() + " is destroyed by " + param; break; + case MsgType::HIT_THROWN_ITEM: msg = param + " hits " + creature->getTheName(); break; + case MsgType::CRASH_THROWN_ITEM: msg = param + " crashes on " + creature->getTheName(); break; + case MsgType::ENTER_PORTAL: msg = creature->getTheName() + " disappears in the portal."; break; + default: break; + } + if (!msg.empty()) + creature->globalMessage(msg, msgNoSee); + } + + virtual void onBump(Creature* c) override { + Vec2 dir = creature->getPosition() - c->getPosition(); + string it = c->canSee(creature) ? creature->getTheName() : "it"; + string something = c->canSee(creature) ? creature->getTheName() : "something"; + if (!creature->canMove(dir)) { + c->privateMessage(it + " won't move in this direction"); + return; + } + c->privateMessage("You push " + something); + if (stopped || dir == direction) { + direction = dir; + creature->setSpeed(100 * c->getAttr(AttrType::STRENGTH) / 30); + stopped = false; + } + } + + private: + Vec2 direction; + bool stopped = false; + Tribe* myTribe = nullptr; +}; + +class Boulder : public Creature { + public: + Boulder(const ViewObject& obj, const CreatureAttributes& attr, Vec2 dir) : + Creature(obj, Tribe::killEveryone, attr, ControllerFactory([dir](Creature* c) { + return new BoulderController(c, dir); })) {} + + Boulder(const ViewObject& obj, const CreatureAttributes& attr, Tribe* myTribe) : + Creature(obj, myTribe, attr, ControllerFactory([myTribe](Creature* c) { + return new BoulderController(c, myTribe); })) {} + + virtual void dropCorpse() override { + drop(ItemFactory::fromId(ItemId::ROCK, Random.getRandom(100, 200))); + } +}; + +PCreature CreatureFactory::getRollingBoulder(Vec2 direction) { + return PCreature(new Boulder( + ViewObject(ViewId::BOULDER, ViewLayer::CREATURE, "Boulder"), CATTR( + c.dexterity = 1; + c.strength = 1000; + c.weight = 1000; + c.humanoid = false; + c.size = CreatureSize::LARGE; + c.speed = 200; + c.permanentlyBlind = true; + c.stationary = true; + c.noSleep = true; + c.invincible = true; + c.breathing = false; + c.name = "boulder";), direction)); +} + +PCreature CreatureFactory::getGuardingBoulder(Tribe* tribe) { + return PCreature(new Boulder( + ViewObject(ViewId::BOULDER, ViewLayer::CREATURE, "Boulder"), CATTR( + c.dexterity = 1; + c.strength = 1000; + c.weight = 1000; + c.humanoid = false; + c.size = CreatureSize::LARGE; + c.speed = 200; + c.permanentlyBlind = true; + c.noSleep = true; + c.stationary = true; + c.invincible = true; + c.breathing = false; + c.name = "boulder";), tribe)); +} + +class KrakenController : public Monster { + public: + KrakenController(Creature* c) : Monster(c, MonsterAIFactory::monster()) { + numSpawns = chooseRandom({1, 2}, {4, 1}); + } + + void makeReady() { + ready = true; + } + + void unReady() { + ready = false; + } + + virtual void onKilled(const Creature* attacker) override { + if (attacker) + attacker->privateMessage("You cut the kraken's tentacle"); + for (Creature* c : spawns) + if (!c->isDead()) + c->die(nullptr); + } + + virtual void you(MsgType type, const string& param) const override { + string msg, msgNoSee; + switch (type) { + case MsgType::KILLED_BY: msg = "The kraken's tentacle is cut by " + param; break; + case MsgType::DIE: + case MsgType::DIE_OF_BLEEDING: return; + default: Monster::you(type, param); break; + } + if (!msg.empty()) + creature->globalMessage(msg, msgNoSee); + } + + virtual void makeMove() override { + int radius = 10; + if (waitNow) { + creature->wait(); + waitNow = false; + return; + } + for (Creature* c : spawns) + if (c->isDead()) { + ++numSpawns; + removeElement(spawns, c); + break; + } + if (held && ((held->getPosition() - creature->getPosition()).length8() != 1 || held->isDead())) + held = nullptr; + if (held) { + held->you(MsgType::HAPPENS_TO, creature->getTheName() + " pulls"); + if (father) { + held->setHeld(father->creature); + father->held = held; + creature->die(nullptr, false); + creature->getLevel()->moveCreature(held, creature->getPosition() - held->getPosition()); + } else { + held->you(MsgType::ARE, "eaten by " + creature->getTheName()); + held->die(); + } + } + if (numSpawns > 0) + for (Vec2 v: Rectangle(Vec2(-radius, -radius), Vec2(radius + 1, radius + 1))) + if (creature->getLevel()->inBounds(creature->getPosition() + v)) + if (Creature * c = creature->getSquare(v)->getCreature()) + if (creature->canSee(c) && creature->isEnemy(c) && !creature->isStationary()) { + if (v.length8() == 1) { + if (ready) { + c->you(MsgType::HAPPENS_TO, creature->getTheName() + " swings itself around"); + c->setHeld(creature); + held = c; + unReady(); + } else + makeReady(); + break; + } + PCreature spawn = CreatureFactory::fromId(CreatureId::KRAKEN, creature->getTribe()); + pair dirs = v.approxL1(); + vector moves; + if (creature->getSquare(dirs.first)->canEnter(spawn.get())) + moves.push_back(dirs.first); + if (creature->getSquare(dirs.second)->canEnter(spawn.get())) + moves.push_back(dirs.second); + if (!moves.empty()) { + if (!ready) { + makeReady(); + } else { + Vec2 move = chooseRandom(moves); + spawns.push_back(spawn.get()); + dynamic_cast(spawn->getController())->father = this; + creature->getLevel()->addCreature(creature->getPosition() + move, std::move(spawn)); + --numSpawns; + unReady(); + } + } else + unReady(); + break; + } + creature->wait(); + } + private: + int numSpawns; + bool waitNow = true; + bool ready = false; + Creature* held = nullptr; + vector spawns; + KrakenController* father = nullptr; +}; + +/*class Shapechanger : public Monster { + public: + Shapechanger(const ViewObject& obj, Tribe* tribe, const CreatureAttributes& attr, vector _creatures) + : Monster(obj, tribe, attr, MonsterAIFactory::monster()), creatures(_creatures) {} + + virtual void makeMoveSpecial() override { + int radius = 3; + for (Vec2 v: Rectangle(Vec2(-radius, -radius), Vec2(radius + 1, radius + 1))) + if (getLevel()->inBounds(getPosition() + v)) + if (Creature* enemy = getSquare(v)->getCreature()) + if (canSee(enemy) && isEnemy(enemy) && enemy->isPlayer()) { + PCreature c = CreatureFactory::fromId(chooseRandom(creatures), getTribe()); + enemy->you(MsgType::ARE, "frozen in place by " + getTheName() + "!"); + enemy->setHeld(c.get()); + globalMessage(getTheName() + " turns into " + c->getAName()); + Vec2 pos = getPosition(); + die(nullptr, false); + getLevel()->addCreature(pos, std::move(c)); + return; + } + Monster::makeMoveSpecial(); + } + + private: + vector creatures; +};*/ + +class KamikazeController : public Monster { + public: + KamikazeController(Creature* c, MonsterAIFactory f) : Monster(c, f) {} + + virtual void makeMove() override { + for (Vec2 v : Vec2::directions8()) + if (creature->getLevel()->inBounds(creature->getPosition() + v)) + if (Creature* c = creature->getSquare(v)->getCreature()) + if (creature->isEnemy(c) && creature->canSee(c)) { + creature->globalMessage(creature->getTheName() + " explodes!"); + c->getSquare()->setOnFire(1); + creature->die(nullptr, false); + return; + } + Monster::makeMove(); + } +}; + +class ShopkeeperController : public Monster, public EventListener { + public: + + ShopkeeperController(Creature* c, Location* area) : Monster(c, MonsterAIFactory::stayInLocation(area)), shopArea(area) { + EventListener::addListener(this); + } + + virtual void makeMove() override { + if (creature->getLevel() != shopArea->getLevel()) { + Monster::makeMove(); + return; + } + if (firstMove) { + for (Vec2 v : shopArea->getBounds()) + for (Item* item : creature->getLevel()->getSquare(v)->getItems()) { + myItems.push_back(item); + item->setUnpaid(true); + } + firstMove = false; + } + vector creatures; + for (Vec2 v : shopArea->getBounds()) + if (const Creature* c = creature->getLevel()->getSquare(v)->getCreature()) { + creatures.push_back(c); + if (!prevCreatures.count(c) && !thieves.count(c) && !creature->isEnemy(c)) { + if (!debt.count(c)) + c->privateMessage("\"Welcome to " + creature->getName() + "'s shop!\""); + else { + c->privateMessage("\"Pay your debt or... !\""); + thiefCount.erase(c); + } + } + } + for (auto elem : debt) { + const Creature* c = elem.first; + if (!contains(creatures, c)) { + c->privateMessage("\"Come back, you owe me " + convertToString(elem.second) + " zorkmids!\""); + if (++thiefCount[c] == 4) { + c->privateMessage("\"Thief! Thief!\""); + creature->getTribe()->onItemsStolen(c); + thiefCount.erase(c); + debt.erase(c); + thieves.insert(c); + for (Item* item : c->getEquipment().getItems()) + if (contains(unpaidItems[c], item)) + item->setUnpaid(false); + break; + } + } + } + prevCreatures.clear(); + prevCreatures.insert(creatures.begin(), creatures.end()); + Monster::makeMove(); + } + + virtual bool wantsItems(const Creature* from, vector items) const override { + for (Item* it : items) + if (it->getType() != ItemType::GOLD) + return false; + return true; + } + + virtual void takeItems(const Creature* from, vector items) { + for (PItem& item : items) { + CHECK(wantsItems(from, {item.get()})); + --debt[from]; + creature->take(std::move(item)); + } + CHECK(debt[from] == 0) << "Bad debt " << debt[from]; + debt.erase(from); + for (Item* it : from->getEquipment().getItems()) + if (contains(unpaidItems[from], it)) { + it->setUnpaid(false); + removeElement(myItems, it); + } + unpaidItems.erase(from); + } + + virtual void onItemsAppeared(Vec2 position, const vector& items) override { + if (position.inRectangle(shopArea->getBounds())) { + myItems.insert(myItems.end(), items.begin(), items.end()); + for (Item* it : items) + it->setUnpaid(true); + } + } + + virtual void onPickupEvent(const Creature* c, const vector& items) override { + if (c->getPosition().inRectangle(shopArea->getBounds())) { + for (const Item* it1 : items) + if (Optional elem = findElement(myItems, it1)) { + Item* item = myItems[*elem]; + debt[c] += item->getPrice(); + unpaidItems[c].push_back(item); + } + } + } + + virtual void onDropEvent(const Creature* c, const vector& items) override { + if (c->getPosition().inRectangle(shopArea->getBounds())) { + for (const Item* it1 : items) + if (Optional elem = findElement(myItems, it1)) { + Item* item = myItems[*elem]; + if ((debt[c] -= item->getPrice()) == 0) + debt.erase(c); + removeElement(unpaidItems[c], item); + } + } + } + + virtual const Level* getListenerLevel() const override { + return creature->getLevel(); + } + + virtual void onKilled(const Creature* attacker) override { + EventListener::removeListener(this); + for (Item* item : myItems) + item->setUnpaid(false); + } + + virtual int getDebt(const Creature* debtor) const override { + if (debt.count(debtor)) { + return debt.at(debtor); + } + else { + return 0; + } + } + + private: + unordered_set prevCreatures; + unordered_map debt; + unordered_map thiefCount; + unordered_set thieves; + unordered_map> unpaidItems; + Location* shopArea; + vector myItems; + bool firstMove = true; +}; + +class VillageElder : public Creature { + public: + VillageElder(vector> _teachSkills, vector tribeQuest, + const ViewObject& object, Tribe* t, const CreatureAttributes& attr, ControllerFactory factory) : + Creature(object, t, attr, factory), teachSkills(_teachSkills), tribeQuestOrder(tribeQuest) { + getTribe()->addImportantMember(this); + } + + bool teach(Creature* who) { + for (auto elem : teachSkills) + if (!who->hasSkill(elem.first) && getTribe()->getStanding(who) >= elem.second) { + Skill* teachSkill = elem.first; + who->addSkill(teachSkill); + who->privateMessage("\"You are a friend of our tribe. I will share my knowledge with you.\""); + who->privateMessage(getName() + " teaches you the " + teachSkill->getName()); + if (teachSkill == Skill::archery) { + who->privateMessage(getName() + " hands you a bow and a quiver of arrows."); + who->take(std::move(ItemFactory::fromId(ItemId::BOW))); + who->take(ItemFactory::fromId(ItemId::ARROW, Random.getRandom(20, 36))); + } + return true; + } + return false; + } + + bool tribeQuest(Creature* who) { + for (Tribe* tribe : tribeQuestOrder) { + vector elders = tribe->getImportantMembers(); + CHECKEQ((int)elders.size(), 1); + if ((killCreature == elders[0] && killCreature->isDead()) || + (bringItem && who->getEquipment().hasItem(bringItem))) { + who->privateMessage("\"" + who->getName() + ", you have fulfilled your quest.\""); + teach(who); + killCreature = nullptr; + bringItem = nullptr; + return true; + } + if (elders[0]->isDead()) + continue; + Item* weapon = nullptr; + for (Item* it : elders[0]->getEquipment().getItems(Item::typePredicate(ItemType::WEAPON))) + if (weapon == nullptr || it->getModifier(AttrType::DAMAGE) > weapon->getModifier(AttrType::DAMAGE)) + weapon = it; + who->privateMessage("\"The " + tribe->getName() + " have long been a nuisance for us."); + who->privateMessage( + "Go kill their leader, " + elders[0]->getTheName()); + if (weapon) { + who->privateMessage("As a proof, bring me his weapon, " + weapon->getArtifactName() + ".\""); + bringItem = weapon; + } else + killCreature = elders[0]; + + return true; + } + return false; + } + + virtual void onChat(Creature* who) override { + if (isEnemy(who)) { + Creature::onChat(who); + return; + } + if (tribeQuest(who)) + return; + // if (teach(who)) + // return; + const vector locations = getLevel()->getAllLocations(); + if (locations.size() == 0) + Creature::onChat(who); + else + while (1) { + Location* l = locations[Random.getRandom(locations.size())]; + if (l->hasName() && l != getLevel()->getLocation(getPosition())) { + Vec2 dir = l->getBounds().middle() - getPosition(); + string dist = dir.lengthD() > 150 ? "far" : "close"; + string bearing = dir.getBearing(); + who->privateMessage("\"There is a " + l->getName() + " " + dist + " in the " + bearing + "\""); + who->privateMessage("\"" + l->getDescription() + "\""); + return; + } + } + } + + private: + vector> teachSkills; + vector tribeQuestOrder; + const Item* bringItem = nullptr; + const Creature* killCreature = nullptr; +}; + +PCreature CreatureFactory::addInventory(PCreature c, const vector& items) { + for (ItemId item : items) { + PItem it = ItemFactory::fromId(item); + Item* ref = it.get(); + c->take(std::move(it)); + } + return c; +} + +PCreature CreatureFactory::getShopkeeper(Location* shopArea, Tribe* tribe) { + PCreature ret(new Creature( + ViewObject(tribe == Tribe::dwarven ? ViewId::DWARVEN_SHOPKEEPER : ViewId::ELVEN_SHOPKEEPER, + ViewLayer::CREATURE, "Shopkeeper"), tribe, + CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 13; + c.barehandedDamage = 13; + c.humanoid = true; + c.weight = 100; + c.chatReactionFriendly = "complains about high import tax"; + c.chatReactionHostile = "\"Die!\""; + c.name = NameGenerator::firstNames.getNext();), + ControllerFactory([shopArea](Creature* c) { + return new ShopkeeperController(c, shopArea); }))); + return addInventory(std::move(ret), vector(Random.getRandom(100, 300), ItemId::GOLD_PIECE)); +} + + +PCreature CreatureFactory::random(MonsterAIFactory actorFactory) { + if (unique.size() > 0) { + CreatureId id = unique.back(); + unique.pop_back(); + return fromId(id, tribe, actorFactory); + } + return fromId(chooseRandom(creatures, weights), tribe, actorFactory); +} + +PCreature get(ViewId viewId, + CreatureAttributes attr, + Tribe* tribe, + ControllerFactory factory) { + return PCreature(new Creature(ViewObject(viewId, ViewLayer::CREATURE, *attr.name), tribe, attr, factory)); +} + +CreatureFactory::CreatureFactory(Tribe* t, const vector& c, const vector& w, + const vector& u) + : tribe(t), creatures(c), weights(w), unique(u) { +} + +CreatureFactory CreatureFactory::humanVillage() { + return CreatureFactory(Tribe::elven, { CreatureId::PESEANT, + CreatureId::CHILD, CreatureId::HORSE, CreatureId::COW }, + { 2, 1, 1, 1}, {}); +} + +CreatureFactory CreatureFactory::elvenVillage() { + return CreatureFactory(Tribe::elven, { CreatureId::ELF, CreatureId::ELF_CHILD, CreatureId::HORSE, CreatureId::COW }, + { 2, 2, 1, 1}, { CreatureId::ELF_LORD}); +} + +CreatureFactory CreatureFactory::forrest() { + return CreatureFactory(Tribe::wildlife, + { CreatureId::DEER, CreatureId::FOX, CreatureId::BOAR, CreatureId::LEPRECHAUN }, + { 2, 1, 1, 3}, {}); +} + +CreatureFactory CreatureFactory::crypt() { + return CreatureFactory(Tribe::monster, { CreatureId::ZOMBIE}, { 1}, {}); +} + +CreatureFactory CreatureFactory::collectiveUndead() { + return CreatureFactory(Tribe::player, { CreatureId::ZOMBIE, CreatureId::VAMPIRE, CreatureId::MUMMY}, + {1, 1, 1}, {}); +} + +CreatureFactory CreatureFactory::collectiveStart() { + return CreatureFactory(Tribe::player, { CreatureId::IMP}, { 1}, {CreatureId::DUNGEON_HEART} ); +} + +CreatureFactory CreatureFactory::collectiveMinions() { + return CreatureFactory(Tribe::player, { CreatureId::GNOME, CreatureId::BILE_DEMON, CreatureId::HELL_HOUND, + CreatureId::SPECIAL_MONSTER}, { 3, 4, 5, 1}, {CreatureId::SPECIAL_MONSTER_HUMANOID}); +} + +CreatureFactory CreatureFactory::collectiveEnemies() { + return CreatureFactory(Tribe::monster, { CreatureId::KNIGHT, CreatureId::ARCHER}, { 1, 1}, {}); +} + +CreatureFactory CreatureFactory::collectiveFinalAttack() { + return CreatureFactory(Tribe::monster, { CreatureId::KNIGHT, CreatureId::ARCHER}, { 1, 1}, {CreatureId::AVATAR}); +} + +CreatureFactory CreatureFactory::dwarfTown(int num) { + int maxLevel = 3; + CHECK(num <= maxLevel && num > 0); + map> frequencies { + { CreatureId::GNOME, { 0, 1, 1}}, + { CreatureId::JACKAL, { 0, 1, 1 }}, + { CreatureId::RAT, { 2, 1, 1}}, + { CreatureId::BAT, { 0, 1, 1 }}, + { CreatureId::DWARF, { 6, 1, 1 }}, + { CreatureId::GOBLIN, { 2, 1, 1 }}}; + vector> uniqueMonsters(maxLevel); + uniqueMonsters[0].push_back(CreatureId::DWARF_BARON); + vector ids; + vector freq; + for (auto elem : frequencies) { + ids.push_back(elem.first); + freq.push_back(elem.second[num - 1]); + } + return CreatureFactory(Tribe::monster, ids, freq, uniqueMonsters[num - 1]); +} + +CreatureFactory CreatureFactory::goblinTown(int num) { + int maxLevel = 3; + CHECK(num <= maxLevel && num > 0); + map> frequencies { + { CreatureId::GNOME, { 0, 1, 1}}, + { CreatureId::JACKAL, { 0, 1, 1 }}, + { CreatureId::RAT, { 1, 1, 1}}, + { CreatureId::BAT, { 0, 1, 1 }}, + { CreatureId::GOBLIN, { 2, 1, 1 }}}; + vector> uniqueMonsters(maxLevel); + uniqueMonsters[0].push_back(CreatureId::GREAT_GOBLIN); + vector ids; + vector freq; + for (auto elem : frequencies) { + ids.push_back(elem.first); + freq.push_back(elem.second[num - 1]); + } + return CreatureFactory(Tribe::monster, ids, freq, uniqueMonsters[num - 1]); +} + +CreatureFactory CreatureFactory::pyramid(int level) { + if (level == 2) + return CreatureFactory(Tribe::monster, { CreatureId::MUMMY }, {1}, { CreatureId::MUMMY_LORD }); + else + return CreatureFactory(Tribe::monster, { CreatureId::MUMMY }, {1}, { }); +} + +CreatureFactory CreatureFactory::level(int num) { + int maxLevel = 8; + CHECK(num <= maxLevel && num > 0); + map> frequencies { + { CreatureId::SPECIAL_MONSTER, { 5, 8, 10, 12, 15, 18, 20, 22}}, + { CreatureId::GNOME, { 400, 200, 100, 100, 100, 100, 100, 100, 100, 100}}, + { CreatureId::LEPRECHAUN, { 20, 20, 20, 20, 20, 20, 20, 20}}, + { CreatureId::GOBLIN, { 20, 20, 30, 50, 50, 100, 200, 400 }}, + { CreatureId::BILE_DEMON, { 0, 0, 100, 100, 200, 200, 200, 200, 200, 200 }}, + { CreatureId::JACKAL, { 400, 100, 100, 100, 100, 100, 100, 100, 100, 100 }}, + { CreatureId::RAT, { 200, 200, 200, 200, 200, 200, 200, 200, 200, 200 }}, + { CreatureId::BAT, { 100, 100, 200, 200, 200, 200, 200, 200, 200, 200 }}, + { CreatureId::ZOMBIE, { 0, 0, 0, 30, 50, 100, 100, 100, 100, 100 }}, + { CreatureId::VAMPIRE_BAT, { 0, 0, 0, 10, 30, 50, 100, 100, 100, 100 }}, + { CreatureId::NIGHTMARE, { 5, 5, 10, 10, 20, 30, 30, 40, 40, 40 }}, + { CreatureId::DWARF, { 400, 200, 100, 50, 50, 30, 20, 20 }}}; + vector> uniqueMonsters(maxLevel); + uniqueMonsters[Random.getRandom(5, maxLevel)].push_back(CreatureId::SPECIAL_MONSTER); + vector ids; + vector freq; + for (auto elem : frequencies) { + ids.push_back(elem.first); + freq.push_back(elem.second[num - 1]); + } + + return CreatureFactory(Tribe::monster, ids, freq, uniqueMonsters[num - 1]); +} + +CreatureFactory CreatureFactory::singleType(Tribe* tribe, CreatureId id) { + return CreatureFactory(tribe, { id}, {1}, {}); +} + +vector CreatureFactory::getFlock(int size, CreatureId id, Creature* leader) { + vector ret; + for (int i : Range(size)) { + PCreature c = fromId(id, leader->getTribe(), MonsterAIFactory::follower(leader, 5)); + ret.push_back(std::move(c)); + } + return ret; +} + +PCreature getSpecial(const string& name, Tribe* tribe, bool humanoid, ControllerFactory factory) { + RandomGen r; + r.init(hash()(name)); + PCreature c = get(humanoid ? ViewId::SPECIAL_HUMANOID : ViewId::SPECIAL_BEAST, CATTR( + c.speed = r.getRandom(70, 150); + c.size = chooseRandom({CreatureSize::SMALL, CreatureSize::MEDIUM, CreatureSize::LARGE}, {1, 1, 1}); + c.strength = r.getRandom(14, 21); + c.dexterity = r.getRandom(14, 21); + c.barehandedDamage = r.getRandom(10); + c.humanoid = humanoid; + c.weight = c.size == CreatureSize::LARGE ? r.getRandom(80,120) : + c.size == CreatureSize::MEDIUM ? r.getRandom(40, 60) : + r.getRandom(5, 20); + if (*c.humanoid) { + c.chatReactionFriendly = "\"I am the mighty " + name + "\""; + c.chatReactionHostile = "\"I am the mighty " + name + ". Die!\""; + } else { + c.chatReactionFriendly = c.chatReactionHostile = "The " + name + " snarls."; + } + c.name = name; + c.wings = r.roll(4) ? 2 : 0; + if (c.wings) + c.flyer = true; + if (*c.humanoid == false) { + c.arms = r.roll(2) ? 2 : 0; + c.legs = r.getRandom(3) * 2; + *c.strength += 5; + *c.dexterity += 5; + c.barehandedDamage += 5; + } + if (r.roll(3)) + c.skills.insert(Skill::swimming); + c.specialMonster = true; + ), tribe, factory); + if (c->isHumanoid()) { + if (Random.roll(400)) { + c->take(ItemFactory::fromId(ItemId::BOW)); + c->take(ItemFactory::fromId(ItemId::ARROW, Random.getRandom(20, 36))); + } else + c->take(ItemFactory::fromId(chooseRandom( + {ItemId::SWORD, ItemId::BATTLE_AXE, ItemId::WAR_HAMMER}))); + } else { + switch (Random.getRandom(3)) { + case 0: + c->take(ItemFactory::fromId( + chooseRandom({ItemId::WARNING_AMULET, ItemId::HEALING_AMULET, ItemId::DEFENSE_AMULET}))); + break; + case 1: + c->take(ItemFactory::fromId(ItemId::INVISIBLE_POTION, Random.getRandom(3, 6))); + break; + case 2: + c->take(ItemFactory::fromId( + chooseRandom({ItemId::STRENGTH_MUSHROOM, ItemId::DEXTERITY_MUSHROOM}), Random.getRandom(3, 6))); + break; + default: + Debug(FATAL) << "Unhandled case value"; + } + + } + Debug() << c->getDescription(); + return c; +} + +PCreature get(CreatureId id, Tribe* tribe, MonsterAIFactory actorFactory) { + ControllerFactory factory = Monster::getFactory(actorFactory); + switch (id) { + case CreatureId::GOBLIN: return get(ViewId::GOBLIN, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 16; + c.dexterity = 14; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 100; + c.skills.insert(Skill::twoHandedWeapon); + c.chatReactionFriendly = "curses all elves"; + c.chatReactionHostile = "\"Die!\""; + c.name = "goblin";), Tribe::goblin, factory); + case CreatureId::BANDIT: return get(ViewId::BANDIT, CATTR( + c.speed = 80; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 13; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 100; + c.chatReactionFriendly = "curses all law enforcement"; + c.chatReactionHostile = "\"Die!\""; + c.name = "bandit";), tribe, factory); + case CreatureId::KNIGHT: return get(ViewId::KNIGHT, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 19; + c.dexterity = 16; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 100; + c.chatReactionFriendly = "curses all dungeons"; + c.chatReactionHostile = "\"Die!\""; + c.name = "knight";), tribe, factory); + case CreatureId::AVATAR: return get(ViewId::AVATAR, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 23; + c.dexterity = 18; + c.barehandedDamage = 8; + c.humanoid = true; + c.weight = 120; + c.chatReactionFriendly = "curses all dungeons"; + c.chatReactionHostile = "\"Die!\""; + c.name = "Avatar";), tribe, factory); + case CreatureId::ARCHER: return get(ViewId::ARCHER, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 16; + c.dexterity = 20; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 100; + c.chatReactionFriendly = "curses all dungeons"; + c.chatReactionHostile = "\"Die!\""; + c.name = "archer";), tribe, factory); + case CreatureId::PESEANT: return get(ViewId::PESEANT, CATTR( + c.speed = 80; + c.size = CreatureSize::LARGE; + c.strength = 14; + c.dexterity = 12; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 100; + c.chatReactionFriendly = "curses all dungeons"; + c.chatReactionHostile = "\"Heeelp!\""; + c.name = "peseant";), tribe, factory); + case CreatureId::CHILD: return get(ViewId::CHILD, CATTR( + c.speed = 140; + c.size = CreatureSize::LARGE; + c.strength = 8; + c.dexterity = 16; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 40; + c.chatReactionFriendly = "plaaaaay!"; + c.chatReactionHostile = "\"Heeelp!\""; + c.name = "child";), tribe, factory); + case CreatureId::ZOMBIE: return get(ViewId::ZOMBIE, CATTR( + c.speed = 60; + c.size = CreatureSize::LARGE; + c.strength = 12; + c.dexterity = 13; + c.barehandedDamage = 13; + c.humanoid = true; + c.weight = 100; + c.undead = true; + c.name = "zombie";), tribe, factory); + case CreatureId::VAMPIRE_BAT: + case CreatureId::VAMPIRE: return get(ViewId::VAMPIRE, CATTR( + c.speed = 120; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 15; + c.barehandedDamage = 2; + c.humanoid = true; + c.weight = 100; + c.undead = true; + c.flyer = true; + c.chatReactionFriendly = "curses all humans"; + c.chatReactionHostile = "\"Die!\""; + c.name = "vampire";), tribe, factory); + /* case CreatureId::VAMPIRE_BAT: return PCreature(new Shapechanger( + ViewObject(ViewId::BAT, ViewLayer::CREATURE, "Bat"), + tribe, + CATTR( + c.speed = 150; + c.size = CreatureSize::SMALL; + c.strength = 3; + c.dexterity = 16; + c.barehandedDamage = 12; + c.humanoid = false; + c.legs = 0; + c.arms = 0; + c.wings = 2; + c.weight = 1; + c.flyer = true; + c.name = "bat";), { + CreatureId::VAMPIRE} + ));*/ + case CreatureId::MUMMY: return get(ViewId::MUMMY, CATTR( + c.speed = 60; + c.size = CreatureSize::LARGE; + c.strength = 12; + c.dexterity = 13; + c.barehandedDamage = 13; + c.humanoid = true; + c.weight = 100; + c.undead = true; + c.skills.insert(Skill::twoHandedWeapon); + c.name = "mummy";), tribe, factory); + case CreatureId::MUMMY_LORD: return get(ViewId::MUMMY_LORD, CATTR( + c.speed = 60; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 13; + c.barehandedDamage = 13; + c.humanoid = true; + c.weight = 120; + c.undead = true; + c.skills.insert(Skill::twoHandedWeapon); + c.chatReactionFriendly = "curses all gravediggers"; + c.chatReactionHostile = "\"Die!\""; + c.name = NameGenerator::aztecNames.getNext();), tribe, factory); + case CreatureId::GREAT_GOBLIN: return PCreature(new VillageElder( + {{Skill::mushrooms, 0.1}, {Skill::knifeThrowing, 0.6}}, + {Tribe::dwarven, Tribe::elven}, + ViewObject(ViewId::GREAT_GOBLIN, ViewLayer::CREATURE, "Great goblin"), Tribe::goblin, + CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 20; + c.dexterity = 13; + c.barehandedDamage = 5; + c.humanoid = true; + c.weight = 180; + c.skills.insert(Skill::twoHandedWeapon); + c.chatReactionFriendly = "curses all elves"; + c.chatReactionHostile = "\"Die!\""; + c.name = "great goblin";), factory)); + case CreatureId::GNOME: return get(ViewId::GNOME, CATTR( + c.speed = 80; + c.size = CreatureSize::MEDIUM; + c.strength = 12; + c.dexterity = 13; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 45; + c.chatReactionFriendly = "talks about digging"; + c.chatReactionHostile = "\"Die!\""; + c.name = "gnome";), tribe, factory); + case CreatureId::IMP: return get(ViewId::IMP, CATTR( + c.speed = 200; + c.size = CreatureSize::SMALL; + c.strength = 8; + c.dexterity = 15; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 30; + c.courage = 0.1; + c.carryingMultiplier = 10; + c.skills.insert(Skill::construction); + c.chatReactionFriendly = "talks about digging"; + c.chatReactionHostile = "\"Die!\""; + c.name = "imp";), tribe, factory); + case CreatureId::BILE_DEMON: return get(ViewId::BILE_DEMON, CATTR( + c.speed = 80; + c.size = CreatureSize::LARGE; + c.strength = 24; + c.dexterity = 14; + c.barehandedDamage = 10; + c.humanoid = false; + c.weight = 140; + c.courage = 1; + c.firstName = NameGenerator::demonNames.getNext(); + c.name = "bile demon";), tribe, factory); + case CreatureId::HELL_HOUND: return get(ViewId::HELL_HOUND, CATTR( + c.speed = 160; + c.size = CreatureSize::MEDIUM; + c.strength = 16; + c.dexterity = 17; + c.barehandedDamage = 5; + c.humanoid = false; + c.weight = 50; + c.courage = 1; + c.firstName = NameGenerator::dogNames.getNext(); + c.name = "hell hound";), tribe, factory); + case CreatureId::CHICKEN: return get(ViewId::CHICKEN, CATTR( + c.speed = 50; + c.size = CreatureSize::SMALL; + c.strength = 2; + c.dexterity = 2; + c.barehandedDamage = 5; + c.humanoid = false; + c.weight = 3; + c.courage = 1; + c.walker = false; + c.isFood = true; + c.name = "chicken";), tribe, factory); + case CreatureId::DUNGEON_HEART: return get(ViewId::DUNGEON_HEART, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 23; + c.dexterity = 14; + c.barehandedDamage = 5; + c.humanoid = false; + c.legs = c.arms = c.heads = 0; + c.weight = 100; + c.walker = true; + c.stationary = true; + c.breathing = false; + c.damageMultiplier = 0.1; + c.name = "dungeon heart";), tribe, Monster::getFactory(MonsterAIFactory::idle())); + case CreatureId::LEPRECHAUN: return get(ViewId::LEPRECHAUN, CATTR( + c.speed = 160; + c.size = CreatureSize::MEDIUM; + c.strength = 10; + c.dexterity = 16; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 35; + c.courage = 20; + c.skills.insert(Skill::stealing); + c.chatReactionFriendly = "discusses the weather"; + c.chatReactionHostile = "discusses the weather"; + c.name = "leprechaun";), tribe, factory); + case CreatureId::DWARF: return get(ViewId::DWARF, CATTR( + c.speed = 80; + c.size = CreatureSize::MEDIUM; + // c.firstName = NameGenerator::dwarfNames.getNext(); + c.strength = 18; + c.dexterity = 13; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 90; + c.skills.insert(Skill::twoHandedWeapon); + c.chatReactionFriendly = "curses all goblins"; + c.chatReactionHostile = "\"Die!\""; + c.name = "dwarf";), Tribe::dwarven, factory); + case CreatureId::DWARF_BARON: return PCreature(new VillageElder( + {{Skill::twoHandedWeapon, 0.1}, {Skill::twoHandedWeapon, 0.6}}, + {Tribe::goblin, Tribe::elven}, + ViewObject(ViewId::DWARF_BARON, ViewLayer::CREATURE, "Dwarf baron"), Tribe::dwarven, + CATTR( + c.speed = 80; + c.size = CreatureSize::MEDIUM; + // c.firstName = NameGenerator::dwarfNames.getNext(); + c.strength = 22; + c.dexterity = 13; + c.barehandedDamage = 5; + c.humanoid = true; + c.weight = 120; + c.skills.insert(Skill::twoHandedWeapon); + c.chatReactionFriendly = "curses all goblins"; + c.chatReactionHostile = "\"Die!\""; + c.name = "dwarf baron";), factory)); + case CreatureId::ELF: return get(ViewId::ELF, CATTR( + c.speed = 120; + c.size = CreatureSize::MEDIUM; + c.strength = 13; + c.dexterity = 15; + c.barehandedDamage = 3; + c.humanoid = true; + c.weight = 50; + c.skills.insert(Skill::archery); + c.chatReactionFriendly = "curses all dwarves"; + c.chatReactionHostile = "\"Die!\""; + c.name = "elf";), Tribe::elven, factory); + case CreatureId::ELF_CHILD: return get(ViewId::ELF_CHILD, CATTR( + c.speed = 150; + c.size = CreatureSize::MEDIUM; + c.strength = 7; + c.dexterity = 17; + c.barehandedDamage = 0; + c.humanoid = true; + c.weight = 25; + c.skills.insert(Skill::archery); + c.chatReactionFriendly = "curses all dwarves"; + c.chatReactionHostile = "\"Help!\""; + c.name = "elf child";), Tribe::elven, factory); + case CreatureId::ELF_LORD: return PCreature(new VillageElder( + {{Skill::ambush, 0.1}, {Skill::archery, 0.6}}, + {Tribe::dwarven, Tribe::goblin}, + ViewObject(ViewId::ELF_LORD, ViewLayer::CREATURE, "Elf lord"), Tribe::elven, CATTR( + c.speed = 140; + c.size = CreatureSize::MEDIUM; + c.strength = 13; + c.dexterity = 18; + c.barehandedDamage = 3; + c.humanoid = true; + c.healer = true; + c.weight = 50; + c.skills.insert(Skill::archery); + c.chatReactionFriendly = "curses all dwarves"; + c.chatReactionHostile = "\"Die!\""; + c.name = "elf lord";), factory)); + case CreatureId::HORSE: return get(ViewId::HORSE, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 10; + c.dexterity = 17; + c.humanoid = false; + c.weight = 500; + c.animal = true; + c.name = "horse";), tribe, factory); + case CreatureId::COW: return get(ViewId::COW, CATTR( + c.speed = 40; + c.size = CreatureSize::LARGE; + c.strength = 10; + c.dexterity = 12; + c.humanoid = false; + c.weight = 400; + c.animal = true; + c.name = "cow";), tribe, factory); + case CreatureId::SHEEP: return get(ViewId::SHEEP, CATTR( + c.speed = 80; + c.size = CreatureSize::MEDIUM; + c.strength = 5; + c.dexterity = 12; + c.humanoid = false; + c.weight = 40; + c.animal = true; + c.name = "sheep";), tribe, factory); + case CreatureId::PIG: return get(ViewId::PIG, CATTR( + c.speed = 60; + c.size = CreatureSize::MEDIUM; + c.strength = 12; + c.dexterity = 8; + c.humanoid = false; + c.weight = 150; + c.animal = true; + c.name = "pig";), tribe, factory); + case CreatureId::JACKAL: return get(ViewId::JACKAL, CATTR( + c.speed = 120; + c.size = CreatureSize::SMALL; + c.strength = 10; + c.dexterity = 15; + c.barehandedDamage = 2; + c.humanoid = false; + c.weight = 10; + c.animal = true; + c.name = "jackal";), tribe, factory); + case CreatureId::DEER: return get(ViewId::DEER, CATTR( + c.speed = 200; + c.size = CreatureSize::LARGE; + c.strength = 10; + c.dexterity = 17; + c.humanoid = false; + c.weight = 400; + c.animal = true; + c.name = "deer";), tribe, factory); + case CreatureId::BOAR: return get(ViewId::BOAR, CATTR( + c.speed = 180; + c.size = CreatureSize::MEDIUM; + c.strength = 18; + c.dexterity = 15; + c.humanoid = false; + c.weight = 200; + c.animal = true; + c.name = "boar";), tribe, factory); + case CreatureId::FOX: return get(ViewId::FOX, CATTR( + c.speed = 140; + c.size = CreatureSize::SMALL; + c.strength = 10; + c.dexterity = 15; + c.barehandedDamage = 2; + c.humanoid = false; + c.weight = 10; + c.animal = true; + c.name = "fox";), tribe, factory); + case CreatureId::CAVE_BEAR: return get(ViewId::BEAR, CATTR( + c.speed = 120; + c.size = CreatureSize::MEDIUM; + c.strength = 18; + c.dexterity = 15; + c.barehandedDamage = 5; + c.weight = 250; + c.humanoid = false; + c.animal = true; + c.name = "cave bear";), tribe, factory); + case CreatureId::RAT: return get(ViewId::RAT, CATTR( + c.speed = 180; + c.size = CreatureSize::SMALL; + c.strength = 2; + c.dexterity = 12; + c.barehandedDamage = 2; + c.humanoid = false; + c.weight = 1; + c.animal = true; + c.skills.insert(Skill::swimming); + c.name = "rat";), Tribe::pest, factory); + case CreatureId::SNAKE: return get(ViewId::SNAKE, CATTR( + c.speed = 100; + c.size = CreatureSize::SMALL; + c.strength = 2; + c.dexterity = 14; + c.barehandedDamage = 15; + c.humanoid = false; + c.weight = 2; + c.animal = true; + c.skills.insert(Skill::swimming); + c.name = "snake";), Tribe::pest, factory); + case CreatureId::VULTURE: return get(ViewId::VULTURE, CATTR( + c.speed = 80; + c.size = CreatureSize::SMALL; + c.strength = 2; + c.dexterity = 12; + c.barehandedDamage = 2; + c.humanoid = false; + c.weight = 5; + c.arms = 0; + c.legs = 0; + c.wings = 2; + c.animal = true; + c.flyer = true; + c.name = "vulture";), Tribe::pest, factory); + case CreatureId::WOLF: return get(ViewId::WOLF, CATTR( + c.speed = 160; + c.size = CreatureSize::MEDIUM; + c.strength = 10; + c.dexterity = 15; + c.barehandedDamage = 10; + c.humanoid = false; + c.animal = true; + c.weight = 35; + c.name = "wolf";), tribe, factory); + case CreatureId::FIRE_SPHERE: return get(ViewId::FIRE_SPHERE, CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 5; + c.dexterity = 15; + c.barehandedDamage = 10; + c.humanoid = false; + c.arms = 0; + c.legs = 0; + c.heads = 0; + c.noBody = true; + c.breathing = false; + c.fireResistant = true; + c.flyer = true; + c.weight = 10; + c.courage = 100; + c.name = "fire sphere";), tribe, ControllerFactory([=](Creature* c) { + return new KamikazeController(c, actorFactory); + })); + case CreatureId::KRAKEN: return get(ViewId::KRAKEN, CATTR( + c.speed = 40; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 15; + c.barehandedDamage = 10; + c.humanoid = false; + c.arms = 0; + c.legs = 0; + c.heads = 0; + c.skills.insert(Skill::swimming); + c.weight = 100; + c.name = "kraken";), tribe, ControllerFactory([=](Creature* c) { + return new KrakenController(c); + })); + case CreatureId::NIGHTMARE: /*return PCreature(new Shapechanger( + ViewObject(ViewId::NIGHTMARE, ViewLayer::CREATURE, "nightmare"), + Tribe::monster, + CATTR( + c.speed = 100; + c.size = CreatureSize::LARGE; + c.strength = 5; + c.dexterity = 25; + c.barehandedDamage = 10; + c.humanoid = false; + c.arms = 0; + c.legs = 0; + c.flyer = true; + c.weight = 100; + c.name = "nightmare";), { + CreatureId::CAVE_BEAR, + CreatureId::WOLF, + CreatureId::ZOMBIE, + CreatureId::VAMPIRE, + CreatureId::MUMMY, + CreatureId::FIRE_SPHERE, + CreatureId::SPECIAL_MONSTER, + }));*/ + case CreatureId::BAT: return get(ViewId::BAT, CATTR( + c.speed = 150; + c.size = CreatureSize::SMALL; + c.strength = 3; + c.dexterity = 16; + c.barehandedDamage = 12; + c.humanoid = false; + c.legs = 0; + c.arms = 0; + c.wings = 2; + c.weight = 1; + c.flyer = true; + c.animal = true; + c.name = "bat";), tribe, factory); + case CreatureId::DEATH: return get(ViewId::DEATH, CATTR( + c.speed = 120; + c.size = CreatureSize::LARGE; + c.strength = 100; + c.dexterity = 35; + c.barehandedDamage = 10; + c.chatReactionFriendly = c.chatReactionHostile = "\"IN ORDER TO HAVE A CHANGE OF FORTUNE AT THE LAST MINUTE YOU HAVE TO TAKE YOUR FORTUNE TO THE LAST MINUTE.\""; + c.humanoid = true; + c.weight = 30; + c.breathing = false; + c.name = "Death";), Tribe::killEveryone, factory); + case CreatureId::SPECIAL_MONSTER: return getSpecial(NameGenerator::creatureNames.getNext(), + tribe, Random.roll(2), factory); + case CreatureId::SPECIAL_MONSTER_HUMANOID: return getSpecial(NameGenerator::creatureNames.getNext(), + tribe, true, factory); + } + Debug(FATAL) << "unhandled case"; + return PCreature(nullptr); +} + +ItemId randomHealing() { + return chooseRandom({ItemId::HEALING_POTION, ItemId::FIRST_AID_KIT}); +} + +ItemId randomBackup() { + return chooseRandom({ItemId::TELE_SCROLL, randomHealing()}, {1, 4}); +} + +ItemId randomArmor() { + return chooseRandom({ItemId::LEATHER_ARMOR, ItemId::CHAIN_ARMOR}, {4, 1}); +} + +class ItemList { + public: + ItemList& maybe(double chance, ItemId id, int num = 1) { + if (Random.getDouble() <= chance) + add(id, num); + return *this; + } + + ItemList& maybe(double chance, const vector& ids) { + if (Random.getDouble() <= chance) + for (ItemId id : ids) + add(id); + return *this; + } + + ItemList& add(ItemId id, int num = 1) { + for (int i : Range(num)) + ret.push_back(id); + return *this; + } + + operator vector() { + return ret; + } + + private: + vector ret; +}; + +vector getInventory(CreatureId id) { + switch (id) { + case CreatureId::DEATH: return ItemList() + .add(ItemId::SCYTHE); + case CreatureId::LEPRECHAUN: return ItemList() + .add(ItemId::TELE_SCROLL, Random.getRandom(1, 4)); + case CreatureId::GNOME: return ItemList() + .add(chooseRandom({ItemId::KNIFE})) + .maybe(0.05, ItemList().add(ItemId::BOW).add(ItemId::ARROW, Random.getRandom(20, 36))); + case CreatureId::ARCHER: return ItemList() + .add(ItemId::BOW).add(ItemId::ARROW, Random.getRandom(20, 36)) + .add(ItemId::KNIFE) + .add(ItemId::LEATHER_ARMOR) + .add(randomHealing()) + .add(ItemId::GOLD_PIECE, Random.getRandom(20, 50)); + case CreatureId::KNIGHT: return ItemList() + .add(ItemId::SWORD) + .add(ItemId::CHAIN_ARMOR) + .add(randomHealing()) + .add(ItemId::GOLD_PIECE, Random.getRandom(30, 80)); + case CreatureId::AVATAR: return ItemList() + .add(ItemId::SPECIAL_BATTLE_AXE) + .add(ItemId::CHAIN_ARMOR) + .add(ItemId::IRON_HELM) + .add(ItemId::HEALING_POTION, Random.getRandom(3, 7)) + .add(ItemId::GOLD_PIECE, Random.getRandom(30, 80)); + case CreatureId::BANDIT: + case CreatureId::GOBLIN: return ItemList() + .add(chooseRandom({ItemId::SWORD}, {3})) + .add(ItemId::LEATHER_ARMOR) + .maybe(0.3, randomBackup()) + .maybe(0.2, ItemId::GOLD_PIECE, Random.getRandom(10, 30)); + case CreatureId::GREAT_GOBLIN: return ItemList() + .add(chooseRandom({ItemId::SPECIAL_BATTLE_AXE, ItemId::SPECIAL_WAR_HAMMER}, {1, 1})) + .add(ItemId::IRON_HELM).add(ItemId::CHAIN_ARMOR) + .add(randomBackup()).add(ItemId::KNIFE, Random.getRandom(2, 5)) + .add(ItemId::GOLD_PIECE, Random.getRandom(100, 200)); + case CreatureId::DWARF: return ItemList() + .add(chooseRandom({ItemId::BATTLE_AXE, ItemId::WAR_HAMMER}, {1, 1})) + .maybe(0.6, randomBackup()) + .add(ItemId::CHAIN_ARMOR) + .maybe(0.5, ItemId::IRON_HELM) + .maybe(0.2, ItemId::GOLD_PIECE, Random.getRandom(10, 30)); + case CreatureId::DWARF_BARON: return ItemList() + .add(chooseRandom({ItemId::SPECIAL_BATTLE_AXE, ItemId::SPECIAL_WAR_HAMMER}, {1, 1})) + .add(randomBackup()) + .add(ItemId::CHAIN_ARMOR) + .add(ItemId::IRON_HELM); + case CreatureId::ELF_LORD: return ItemList() + .add(ItemId::SPECIAL_ELVEN_SWORD) + .add(ItemId::LEATHER_ARMOR) + .add(ItemId::BOW) + .add(ItemId::ARROW, Random.getRandom(20, 36)) + .add(randomBackup()); + case CreatureId::ELF: return ItemList() + .add(ItemId::ELVEN_SWORD) + .add(ItemId::LEATHER_ARMOR) + .add(ItemId::BOW) + .add(ItemId::ARROW, Random.getRandom(20, 36)) + .add(randomBackup()); + case CreatureId::MUMMY_LORD: return ItemList() + .add(ItemId::GOLD_PIECE, Random.getRandom(100, 200)).add( + chooseRandom({ItemId::SPECIAL_BATTLE_AXE, ItemId::SPECIAL_WAR_HAMMER, ItemId::SPECIAL_SWORD}, {1, 1, 1})); + default: return {}; + } +} + +PCreature CreatureFactory::fromId(CreatureId id, Tribe* t, MonsterAIFactory factory) { + return addInventory(get(id, t, factory), getInventory(id)); +} + diff --git a/creature_factory.h b/creature_factory.h new file mode 100644 index 000000000..d3805d4db --- /dev/null +++ b/creature_factory.h @@ -0,0 +1,56 @@ +#ifndef _CREATURE_FACTORY +#define _CREATURE_FACTORY + +#include +#include +#include + +#include "util.h" +#include "creature_attributes.h" +#include "tribe.h" +#include "view_object.h" +#include "item_factory.h" + +class Creature; +class ItemFactory; + + +class CreatureFactory { + public: + static PCreature fromId(CreatureId, Tribe*, MonsterAIFactory = MonsterAIFactory::monster()); + static vector getFlock(int size, CreatureId, Creature* leader); + static CreatureFactory humanVillage(); + static CreatureFactory elvenVillage(); + static CreatureFactory forrest(); + static CreatureFactory crypt(); + static CreatureFactory dwarfTown(int num); + static CreatureFactory collectiveStart(); + static CreatureFactory collectiveMinions(); + static CreatureFactory collectiveUndead(); + static CreatureFactory collectiveEnemies(); + static CreatureFactory collectiveFinalAttack(); + static CreatureFactory goblinTown(int num); + static CreatureFactory level(int num); + static CreatureFactory bottomLevel(); + static CreatureFactory singleType(Tribe*, CreatureId); + static CreatureFactory pyramid(int level); + PCreature random(MonsterAIFactory = MonsterAIFactory::monster()); + + static PCreature getShopkeeper(Location* shopArea, Tribe*); + static PCreature getRollingBoulder(Vec2 direction); + static PCreature getGuardingBoulder(Tribe* tribe); + + static PCreature addInventory(PCreature c, const vector& items); + + static void init(); + + private: + CreatureFactory(Tribe* tribe, const vector& creatures, const vector& weights, + const vector& unique); + Tribe* tribe; + vector creatures; + vector weights; + vector unique; +}; + +#endif diff --git a/creature_view.h b/creature_view.h new file mode 100644 index 000000000..afdb62378 --- /dev/null +++ b/creature_view.h @@ -0,0 +1,20 @@ +#ifndef _CREATURE_VIEW_H +#define _CREATURE_VIEW_H + +#include "map_memory.h" +#include "view.h" + +class CreatureView { + public: + virtual const MapMemory& getMemory(const Level* l) const = 0; + virtual ViewIndex getViewIndex(Vec2 pos) const = 0; + virtual void refreshGameInfo(View::GameInfo&) const = 0; + virtual Vec2 getPosition() const = 0; + virtual bool staticPosition() const { return true; } + virtual bool canSee(const Creature*) const = 0; + virtual bool canSee(Vec2 position) const = 0; + virtual const Level* getLevel() const = 0; + virtual vector getUnknownAttacker() const = 0; +}; + +#endif diff --git a/creatures.txt b/creatures.txt new file mode 100644 index 000000000..68a2f03f0 --- /dev/null +++ b/creatures.txt @@ -0,0 +1,347 @@ +aasilisk +aasilith +aasilithid +aasimal +abold +abolder +abolem +aboleton +abolette +aboletter +achan +achant +achantle +allith +allithid +allithzer +amonstrou +anger +araneton +arro +arror +bargon +bargon tur +bargonne +bargouill +bargoyle +basilith +basilithid +basimal +basimar +basimare +basimaroid +blacer dra +bling +bling mon +bugbeast +bugbeaster +buleth +buleton +carrasque +celest +celester +celestiff +celestrou +celeth +celeton +celette +celetter +cental +centalker +chasa +chase stal +chromatin +chrope +chroper +chroperibo +chroperin +chropering +cloaken +cocker +cocker dra +darkman +darkmander +darkmant +demorhaz +demorian +demoriant +dester +destercap +desterkin +destiff +destiffon +destrou +devitable +devitablex +devitan +digestiff +digestrou +dinotaur +dire +dire anida +dire spawn +dire stal +dopper +dopperibo +dopperin +doppering +drachan +drachant +drachantle +eladrice +eladrick +elementaur +ette +etter +etter hulk +etterkin +fiend +fiendead +fiender +fiendrice +fiendrick +fiendrin +fiendring +fomorhaz +formia +frost mon +gammar +gammare +gammast +gammastiff +gammasu +gammasus +garghest +garghester +gargon +gargonne +gargouill +gargouille +gelaticore +gelaticorn +gelatin +gem dra +ghound +gian +gibber dra +gibberibo +gibberin +gibbering +gith +githid +githzer +goleth +goleton +golette +goletter +gorg +gorgouill +gorgouille +gray rend +grice +griculos +griculus +griff +guarding +half-dra +halfeshnee +halflin +hell horro +hell houl +hellar dra +hook horro +hook houl +hook hound +illip +illith +illithzer +inevil +inevitan +juible +karnov +kobolem +koboleth +koboleton +kobolette +koboletter +kraker +krend +krendead +krender +krendrice +krendrick +krendrin +krendring +krenshade +krenshadow +krenshasa +krenshasm +krenshasme +lammar +lammare +lammaroid +lammast +lammastiff +lill +lille +lizarding +locat +locathak +lycant +lycantle +mant +mant eatur +manthrope +manthroper +mantic dra +manticorn +mantle +marilisk +mast +masterkin +mastiff +mastiffon +mephinx +minosaur +monstrou +nalf-dra +nalflin +nalfling +nightmar +nightshar +nightshasa +nightshasm +nupper +nupperin +nuppering +oreant +oreantle +oreater +oreaterkin +oreatur +oreaturtle +oreaturtur +owlbeast +owlbeaster +phan +phant +phanthrope +phanticore +phantle +phasa +phase stal +planea +planeton +pseudodra +rakshade +rakshadow +rakshar +rakshasm +rakshasme +remogorg +remogorgon +remon +remonster +remonstrou +remorian +remoriant +retrice +retrick +retriculos +retriculus +retrieker +rutte +rutter +ruttercap +salaman +salamant +salamantle +sato +scorn +shamander +shamant +shamantle +shamblin +shambling +shocker +shri-kreen +shriever +shrievern +skelest +skelester +skelestiff +skelestrou +skeleth +skelette +skeletter +spect +sphit +stell houl +surtle +surture +surturtle +surturture +tend +tendead +tender +tendrice +tendrick +tendrin +tendring +thoth +thoth houl +thrieker +thriever +thrievern +titable +titablex +tojanimal +tojanimar +tojanimare +tread +treater +treaterkin +treatur +treature +treaturtle +treaturtur +umber kaen +umberibo +umberin +umbering +under kaen +under hulk +unic cread +unic dra +unicore +unicorpius +varghest +varghester +vargon +vargon tur +vargonne +vargouill +vargoyle +violem +violeth +violeton +violette +violetter +water kaen +water hulk +waterkin +wille +willend +willendead +willender +willendrin +winter dra +winterkin +wrai +wyver +yeen +yeth +yeth amon +yeth horro +yeth houl +yrthah diff --git a/debug.cpp b/debug.cpp new file mode 100644 index 000000000..360e3caa2 --- /dev/null +++ b/debug.cpp @@ -0,0 +1,78 @@ +#include "stdafx.h" + +using namespace std; + +Debug::Debug(DebugType t) : out((string[]) { "INFO ", "FATAL "}[t]), type(t) { +} + +static ofstream output; + +void Debug::init() { + output.open("log.out"); +} + +void Debug::add(const string& a) { + out += a; +} +Debug::~Debug() { + if (type == FATAL) { + output << out << endl; + output.flush(); + throw out; + } else { +#ifndef RELEASE + output << out << endl; + output.flush(); +#endif + } +} +Debug& Debug::operator <<(const string& msg) { + add(msg); + return *this; +} +Debug& Debug::operator <<(const int msg) { + add(convertToString(msg)); + return *this; +} +Debug& Debug::operator <<(const char msg) { + add(convertToString(msg)); + return *this; +} +Debug& Debug::operator <<(const double msg) { + add(convertToString(msg)); + return *this; +} +template +Debug& Debug::operator<<(const vector& container){ + (*this) << "{"; + for (const T& elem : container) { + (*this) << elem << ","; + } + (*this) << "}"; + return *this; +} + +template +Debug& Debug::operator<<(const vector >& container){ + (*this) << "{"; + for (int i : Range(container[0].size())) { + for (int j : Range(container.size())) { + (*this) << container[j][i] << ","; + } + (*this) << '\n'; + } + (*this) << "}"; + return *this; +} + +template Debug& Debug::operator <<(const vector&); +template Debug& Debug::operator <<(const vector&); +template Debug& Debug::operator <<(const vector&); +template Debug& Debug::operator <<(const vector&); +template Debug& Debug::operator <<(const vector >&); + +template NoDebug& NoDebug::operator <<(const vector&); +template NoDebug& NoDebug::operator <<(const vector&); +template NoDebug& NoDebug::operator <<(const vector&); +template NoDebug& NoDebug::operator <<(const vector&); +template NoDebug& NoDebug::operator <<(const vector >&); diff --git a/debug.h b/debug.h new file mode 100644 index 000000000..41d8914d8 --- /dev/null +++ b/debug.h @@ -0,0 +1,93 @@ +#ifndef _DEBUG_H +#define _DEBUG_H + +#include + +#define DEBUG + +#ifdef DEBUG + +#define CHECK(exp) if (!(exp)) Debug(FATAL) << __FILE__ << ":" << __LINE__ << ": " << #exp << " is false. " +//#define CHECKEQ(exp, exp2) if ((exp) != (exp2)) Debug(FATAL) << __FILE__ << ":" << __LINE__ << ": " << #exp << " = " << #exp2 << " is false. " << exp << " " << exp2 +#define TRY(exp, msg) do { try { exp; } catch (...) { Debug(FATAL) << __FILE__ << ":" << __LINE__ << ": " << #exp << " failed. " << msg; exp; } } while(0) + +#else + +#define CHECK(exp) if (!(exp)) NoDebug() << 1 +#define TRY(exp, msg) exp +#endif + +#ifndef WINDOWS + +#define MEASURE(exp, text) do { \ + timeval time1; \ + gettimeofday(&time1, nullptr); \ + suseconds_t m1 = time1.tv_usec + time1.tv_sec * 1000000; \ + exp; \ + gettimeofday(&time1, nullptr); \ + suseconds_t m2 = time1.tv_usec + time1.tv_sec * 1000000; \ + Debug() << text << " " << int(m2 - m1);} while(0); + +#else + +#define MEASURE(exp, text) exp; + +#endif + +enum DebugType { INFO, FATAL }; + +class NoDebug { + public: + NoDebug& operator <<(const string& msg) { return *this;} + NoDebug& operator <<(const int msg) {return *this;} + NoDebug& operator <<(const char msg) {return *this;} + NoDebug& operator <<(const double msg) {return *this;} + template + NoDebug& operator<<(const vector& container) {return *this;} + template + NoDebug& operator<<(const vector >& container) {return *this;} +}; + +class Debug { + public: + Debug(DebugType t = INFO); + static void init(); + Debug& operator <<(const string& msg); + Debug& operator <<(const int msg); + Debug& operator <<(const char msg); + Debug& operator <<(const double msg); + template + Debug& operator<<(const vector& container); + template + Debug& operator<<(const vector >& container); + ~Debug(); + + private: + string out; + DebugType type; + void add(const string& a); +}; + +template +const T& valueCheck(const T& e, const V& v, const string& msg) { + if (e != v) Debug(FATAL) << msg << " (" << e << " != " << v << ")"; + return e; +} + +template +T* notNullCheck(T* e, const string& msg) { + if (e == nullptr) Debug(FATAL) << msg; + return e; +} + +template +unique_ptr notNullCheck(unique_ptr e, const string& msg) { + if (e.get() == nullptr) Debug(FATAL) << msg; + return e; +} + +#define NOTNULL(e) notNullCheck(e, string(__FILE__) + ":" + convertToString(__LINE__) + ": " + #e + " is null.") +#define CHECKEQ(e, v) valueCheck(e, v, string(__FILE__) + ":" + convertToString(__LINE__) + ": " + #e + " != " + #v + " ") +#define CHECKEQ2(e, v, msg) valueCheck(e, v, string(__FILE__) + ":" + convertToString(__LINE__) + ": " + #e + " != " + #v + " " + msg) + +#endif diff --git a/demons.txt b/demons.txt new file mode 100644 index 000000000..7e5489797 --- /dev/null +++ b/demons.txt @@ -0,0 +1,664 @@ +Abadon +Abalacoda +Abbaddon +Abrand +Abrandyman +Abraxxus +Agrim +Agrimel +Agrit +Agrith +Agrithazae +Akira +Akira Fung +Akirahim +Akirahimon +Alastar +Alastaron +Alastaroth +Alasters +Alastery +Alastie +Alasto +Alasts +Alastur +Alastus +Algalial +Algaliante +Algalianty +Algalice +Algalichim +Algalichin +Alice +Alichim +Alichimon +Alichin +Alichina +Amain +Anafeloth +Anafeloz +Andariccia +Andarietta +Andarish +Andariska +Anyankou +Archim +Archimon +Archin +Archina +Archino +Arters +Ash +Asmoda +Asmodan +Asmodemon +Asta Burra +Astai +Astair +Astar +Astaro +Astarog +Astaronos +Aurok +Aurok-Han +Auron +Auronos +Auronove +Auronover +Azmoda +Azmodemon +Azmodeus +Bacarbas +Bacarbia +Bacarr +Bahuma +Bahumaimon +Bahumain +Bai Gui +Bai Tengu +Bai Terumi +Baltha +Balthan +Balthang +Balthant +Balthazor +Baphomero +Baphomeron +Baphrim +Baphrimel +Bara +Baraxas +Baraxxus +Barbaricco +Barbarilin +Barbarish +Barbariska +Barbia +Bart +Bartimael +Beastai +Beastair +Beastar +Beastaro +Beastarod +Beastarog +Beastaron +Beastaroth +Beasters +Beastery +Beasto +Beastor +Beastore +Beasts +Beastur +Beastus +Bebilina +Bebilleach +Bebilly +Bebiro +Bebiroth +Belfago +Belfagon +Beliarept +Belin +Belinazzo +Beltha +Belthan +Belthazae +Belthazar +Beth +Beth Shan +Beth Shaw +Blackhead +Blacoda +Blacodan +Blacodemon +Blacodeus +Briel +Briel Phan +Brietta +Burra +Cacoda +Cacodan +Cacodeus +Cadaven +Cailly +Calcabrezu +Camero +Cameron +Cameronos +Cameronove +Cand +Candaricco +Candariel +Candarish +Candariska +Castar +Castarog +Castaroth +Casters +Castery +Castie +Casto +Casts +Castur +Castus +Chernabor +Chernal +Cherno +Chtho +Ciel +Ciel Phan +Ciel Phant +Ciriatha +Ciriathan +Ciriathang +Ciriathant +Clor +Cloripus +Clortho +Clorthon +Cost +Costai +Costair +Costar +Costaro +Costarod +Costarog +Costaron +Costaronos +Costaroth +Crawcia +Dago +Dagon +Dagor +Dai Tengu +Dai Terumi +Dai Tza +Daimon +Daimonde +Daimonita +Dami +Damia +Damiel +Damon +Damonde +Damonita +Dant +Dantomhive +Danty +Deach +Deacoda +Deacodan +Deacodemon +Deacodeus +Decarbas +Decarr +Decarra +Delrit +Demon +Demonde +Devi +Deviatha +Deviathan +Deviathant +Deviatto +Dovicula +Draculus +Draga +Draghin +Draghina +Draghino +Drawly +Duma +Dumaimon +Dumaimonde +Dumat +Durietta +Elbub +Etrigon +Evilin +Evilith +Farfarept +Fausizz +Faustur +Faustus +Flagg +Frost +Frostai +Frostair +Frostar +Frostaro +Frostarod +Frostarog +Frostaron +Frostaroth +Fudo +Fung +Ghastai +Ghastaro +Ghastarod +Ghastarog +Ghastaroth +Ghasters +Ghastery +Ghastie +Ghasto +Ghastor +Ghastore +Ghasts +Ghastur +Ghastus +Ghira +Ghira Fudo +Ghira Fung +Ghiro +Ghiroth +Ghirothmog +Glabilin +Glabilina +Glabilith +Glabilly +Glabrina +Goth +Gregmory +Griselial +Griselin +Griselina +Grish +Griska +Gui +Haderas +Halfegor +Halfesh +Halfeshnee +Han +Hann +Hannon +Hannoroth +Hastar +Hastaro +Hastarod +Hastarog +Hasters +Hastery +Hastie +Hasto +Hastor +Hastore +Hasts +Hastus +Hello +Hellor +Helloripus +Hellortho +Hellorthon +Hellstrom +Henriel +Henrietta +Hessiant +Hessiante +Hessianty +Horseman +Infer +Infernabog +Infernabor +InuYash +InuYashin +InuYashina +InuYashino +InuYashira +InuYashiro +Jabog +Jadia Mor +Janes Dago +Janesh +Janeshnee +Japhomero +Japhomeron +Japhomet +Japhrim +Jara +Jarakiel +Jaraxas +Jinn +Kagur +Kahn +Khorn +Killabrezu +Killabrina +Killeach +Killeacoda +Killy +Knee +Kneesock +Knowby +Koakumur +Koakumura +Koakuta +Kong +Kron +Kron Briel +Kron Frost +Kron Hello +Kronove +Kronover +Lami +Lamiel +Lan +Levi +Levi Devi +Leviatha +Leviatto +Libicant +Libicante +Libicanty +Lilin +Lilina +Lilinazzo +Loc-Naar +Lorn +Maden +Makuma +Makumaimon +Makumain +Makumat +Makumur +Makumura +Malackhead +Malacodeus +Malam +Malfesh +Malfeshnee +Malfrek +Malial +Maliant +Maliarept +Malichimon +Malichin +Malichino +Mammael +Mammaeus +Mammaro +Mammaroth +Mann +Mannon +Mariccia +Maricco +Mariccolo +Mariel +Marietta +Marilin +Marilina +Mariselin +Marish +Mary +Mary Shang +Mary Sheba +Masselial +Mastai +Mastair +Mastaro +Mastarog +Mastaroth +Masters +Mastery +Mastie +Masto +Mastor +Masts +Mastur +Mastus +Meg Mastar +Meg Mastie +Meg Masto +Meg Mastor +Meg Masts +Meg Mastur +Meg Mastus +Mehrunesh +Mephileena +Mileentch +Miles +Molatoo +Molatoon +Molator +Molatore +Moore +Mor +Mundu +Naar +Nadia Mor +Nadis +Nalfegor +Nalfesh +Nalfrek +Nanas +Nanator +Nanatore +Nar +Narvaro +Narvarod +Narvaron +Narvaroth +Naustur +Naustus +Nebilin +Nebilina +Nebilith +Nebilitha +Nebilleach +Nebilly +Nebiro +Necrod +Necrodemon +Neurok +Neurok-Han +Neuron +Neuronos +Neuronove +Neurora +Nōgami +Nōgamia +Nōgamiel +Okuma +Okumaimon +Okumain +Okumat +Okumur +Okumura +Oyash +Oyasha +Oyashin +Oyashina +Oyashino +Oyashira +Phan +Phang +Phang Zu +Phant +Phante +Phanty +Piccia +Picco +Po Kahn +Quitoo +Ram +Ramuthy +Rand +Randariel +Randarilin +Randyman +Raver +Reno +Rin Okuma +Rin Okumat +Rin Okumur +Rofocal +Romero +Romeron +Romeronos +Romeronove +Romet +Ronos +Rubicant +Rubicanty +Rubicocco +Ruxpin +Ruxpine +Saeki +Salvatoo +Salvatoon +Salvator +Salvatore +Sammaeus +Sammaro +Sammarod +Sammarog +Sammaron +Sammaroth +Sammon +Sammonde +Sammonita +Sarda +Sargatan +Saurok +Saurok-Han +Saurora +Savant +Savante +Savanty +Scant +Scante +Scarbariel +Scarbas +Scarbia +Scarr +Scarra +Seloz +Sergil +Serguthra +Shan +Shang +Shang Zu +Shant +Shante +Shanty +Shao Fudo +Shao Fung +Shao Kong +Shaw +Sheba +Silin +Silina +Silinazzo +Silith +Silith-Nar +Slaanemba +Spar +Spardius +Spin +Stra +Straghin +Straghina +Strago +Stragon +Stragor +Tang +Tang Tsung +Tang Zu +Tchan +Tchang +Tchant +Tchante +Tchanty +Tchernabor +Tchernal +Tcherno +Teddy Reno +Tengu +Terumi +Thammael +Thammaeus +Thammaro +Thammarod +Thammarog +Thammaroth +Thammon +Thammonde +Thorn +Thorne +Tiama +Trigan +Triselin +Triselina +Triska +Tsung +Turok +Turon +Turonos +Turonove +Turora +Tza +Tzeena +Ungolial +Verguthra +Verguthy +Viola Ram +Violatoo +Violatoon +Vrina +Vrinazzo +Vrinz Clor +Vulgrit +Vulgrith +Vulgritha +Wormwoo +Xiao Fudo +Xiao Kahn +Xiao Kong +Yarn +Yash +Yasha +Yashin +Yashina +Yashinazzo +Yashino +Yashira +Yashiro +Yashiroth +Yuuki Tza +Zaldove +Zami +Zamia +Zanka +Zanshira +Zanshiro +Zanshiroth +Zellboy +Zello +Zellor +Zellortho +Zellorthon +Zellstrom +Zennoroth diff --git a/dogs.txt b/dogs.txt new file mode 100644 index 000000000..d6442db61 --- /dev/null +++ b/dogs.txt @@ -0,0 +1,56 @@ +Balei +Bandicò +Bandy +Bang +Beethover +Bendit +Binger +Bington +Bustein +Chifford +Cliffon +Dinger +Dington +Eins +Einson +Einster +Fergean +Fergeant +Fluffert +Flufferto +Garry +Genes +Gnashtank +Gnashtanka +Howar +Inher +Kasher +Kasherent +Kashtank +Kashtankee +Kree +Lock +Locket +Luck +Luckles +Marl +Marm +Pomper +Robins +Robinstein +Robinster +Rock +Rockjaw +Roven +Ruffert +Ruffy +Sandicò +Sandit +Satche +Scampey +Sergean +Sergus +Wellinger +Wellingo +Yank +Yanka diff --git a/dwarfs.txt b/dwarfs.txt new file mode 100644 index 000000000..c807ebdd4 --- /dev/null +++ b/dwarfs.txt @@ -0,0 +1,82 @@ +Bombul +Bori +Breck +Caled +Dadrik +Daleb +Dimrong +Drak +Drond +Drum +Drumdi +Duregan +Farik +Faril +Figgs +Fráin +Frói +Fróin +Fund +Fundi +Fundrik +Fundrin +Fundurim +Fundurimli +Fundurin +Gadrik +Ghar +Glói +Gomrun +Gomrung +Gomrunni +Gora +Gori +Gorri +Grodrin +Grogrim +Grogrimbul +Grogrimbur +Grogrimdal +Grogrimli +Grogrimnir +Grogrin +Grong +Grói +Gund +Gundi +Gundin +Gundrin +Gundurim +Gundurimli +Gundurin +Regar +Rhund +Rhundi +Rhundin +Rhundrik +Rhundrin +Rhundurim +Thingril +Thingrin +Thorem +Thorgril +Thorgrin +Thrun +Thrund +Thrundi +Thrundin +Thrundrik +Thrundrin +Thrundurin +Thrunni +Thrár +Thrói +Thróin +Zigril +Zigrim +Zigrimbul +Zigrimbur +Zigrimdal +Zigrimli +Zigrimnir +Zigrimwold diff --git a/effect.cpp b/effect.cpp new file mode 100644 index 000000000..ff8949d92 --- /dev/null +++ b/effect.cpp @@ -0,0 +1,340 @@ +#include "stdafx.h" + +using namespace std; + +map healingPoints { + {EffectStrength::WEAK, 5}, + {EffectStrength::NORMAL, 15}, + {EffectStrength::STRONG, 40}}; + +map sleepTime { + {EffectStrength::WEAK, 15}, + {EffectStrength::NORMAL, 80}, + {EffectStrength::STRONG, 200}}; + +map panicTime { + {EffectStrength::WEAK, 5}, + {EffectStrength::NORMAL, 15}, + {EffectStrength::STRONG, 40}}; + +map halluTime { + {EffectStrength::WEAK, 30}, + {EffectStrength::NORMAL, 100}, + {EffectStrength::STRONG, 250}}; + +map blindTime { + {EffectStrength::WEAK, 5}, + {EffectStrength::NORMAL, 15}, + {EffectStrength::STRONG, 45}}; + +map invisibleTime { + {EffectStrength::WEAK, 5}, + {EffectStrength::NORMAL, 15}, + {EffectStrength::STRONG, 45}}; + +map fireAmount { + {EffectStrength::WEAK, 0.5}, + {EffectStrength::NORMAL, 1}, + {EffectStrength::STRONG, 1}}; + +map attrBonusTime { + {EffectStrength::WEAK, 30}, + {EffectStrength::NORMAL, 100}, + {EffectStrength::STRONG, 450}}; + +map identifyNum { + {EffectStrength::WEAK, 1}, + {EffectStrength::NORMAL, 1}, + {EffectStrength::STRONG, 400}}; + +Effect::Effect(EffectType t) : type(t) {} + +EffectType Effect::getType() { + return type; +} + +PEffect Effect::getEffect(EffectType type) { + switch (type) { + case EffectType::HEAL: return PEffect(new HealingEffect()); + case EffectType::IDENTIFY: return PEffect(new IdentifyEffect()); + case EffectType::TELEPORT: return PEffect(new TeleportEffect()); + case EffectType::PORTAL: return PEffect(new PortalEffect()); + case EffectType::SLEEP: return PEffect(new SleepEffect()); + case EffectType::PANIC: return PEffect(new PanicEffect()); + case EffectType::RAGE: return PEffect(new RageEffect()); + case EffectType::ROLLING_BOULDER: return PEffect(new RollingBoulderEffect()); + case EffectType::FIRE: return PEffect(new FireEffect()); + case EffectType::SLOW: return PEffect(new SlowEffect()); + case EffectType::SPEED: return PEffect(new SpeedEffect()); + case EffectType::HALLU: return PEffect(new HalluEffect()); + case EffectType::BLINDNESS: return PEffect(new BlindnessEffect()); + case EffectType::INVISIBLE: return PEffect(new InvisibleEffect()); + case EffectType::STR_BONUS: return PEffect(new StrBonusEffect()); + case EffectType::DEX_BONUS: return PEffect(new DexBonusEffect()); + case EffectType::DESTROY_EQUIPMENT: return PEffect(new DestroyEquipmemntEffect()); + case EffectType::ENHANCE_ARMOR: return PEffect(new EnhanceArmorEffect()); + case EffectType::ENHANCE_WEAPON: return PEffect(new EnhanceWeaponEffect()); + case EffectType::FIRE_SPHERE_PET: return PEffect(new FireSpherePetEffect()); + case EffectType::GUARDING_BOULDER: return PEffect(new GuardingBuilderEffect()); + case EffectType::EMIT_POISON_GAS: return PEffect(new EmitPoisonGasEffect()); + default: Debug(FATAL) << "Can't construct effect " << (int)type; + } + return PEffect(nullptr); +} + +PEffect Effect::giveItemEffect(ItemId id, int num) { + return PEffect(new GiveItemEffect(id, num)); +} + +EmitPoisonGasEffect::EmitPoisonGasEffect() : Effect(EffectType::EMIT_POISON_GAS) {} + +void EmitPoisonGasEffect::applyToCreature(Creature* c, EffectStrength strength) { + for (Vec2 v : Vec2::directions8()) + c->getSquare(v)->addPoisonGas(1); + c->getSquare()->addPoisonGas(2); +} + +GuardingBuilderEffect::GuardingBuilderEffect() : Effect(EffectType::GUARDING_BOULDER) {} + +void GuardingBuilderEffect::applyToCreature(Creature* c, EffectStrength strength) { + Optional dest; + for (Vec2 v : Vec2::directions8(true)) + if (c->canMove(v) && !c->getSquare(v)->getCreature()) { + dest = v; + break; + } + Vec2 pos = c->getPosition(); + if (dest) + c->move(*dest); + else { + Effect::getEffect(EffectType::TELEPORT)->applyToCreature(c, EffectStrength::NORMAL); + } + if (c->getPosition() != pos) { + PCreature boulder = CreatureFactory::getGuardingBoulder(c->getTribe()); + c->getLevel()->addCreature(pos, std::move(boulder)); + } +} + +FireSpherePetEffect::FireSpherePetEffect() : Effect(EffectType::FIRE_SPHERE_PET) {} + +void FireSpherePetEffect::applyToCreature(Creature* c, EffectStrength strength) { + PCreature sphere = CreatureFactory::fromId( + CreatureId::FIRE_SPHERE, c->getTribe(), MonsterAIFactory::follower(c, 1)); + for (Vec2 v : Vec2::directions8(true)) + if (c->getSquare(v)->canEnter(sphere.get())) { + c->getLevel()->addCreature(c->getPosition() + v, std::move(sphere)); + c->globalMessage("A fire sphere appears."); + break; + } +} + +EnhanceArmorEffect::EnhanceArmorEffect() : Effect(EffectType::ENHANCE_ARMOR) {} + +void EnhanceArmorEffect::applyToCreature(Creature* c, EffectStrength strength) { + for (EquipmentSlot slot : randomPermutation({ EquipmentSlot::BODY_ARMOR, EquipmentSlot::HELMET})) + if (Item* item = c->getEquipment().getItem(slot)) { + c->you(MsgType::YOUR, item->getName() + " seems improved"); + item->addModifier(AttrType::DEFENSE, 1); + return; + } +} + +EnhanceWeaponEffect::EnhanceWeaponEffect() : Effect(EffectType::ENHANCE_WEAPON) {} + +void EnhanceWeaponEffect::applyToCreature(Creature* c, EffectStrength strength) { + if (Item* item = c->getEquipment().getItem(EquipmentSlot::WEAPON)) { + c->you(MsgType::YOUR, item->getName() + " seems improved"); + item->addModifier(chooseRandom({AttrType::TO_HIT, AttrType::DAMAGE}), 1); + } +} + +DestroyEquipmemntEffect::DestroyEquipmemntEffect() : Effect(EffectType::DESTROY_EQUIPMENT) {} + +void DestroyEquipmemntEffect::applyToCreature(Creature* c, EffectStrength strength) { + vector equiped; + for (Item* item : c->getEquipment().getItems()) + if (c->getEquipment().isEquiped(item)) + equiped.push_back(item); + Item* dest = chooseRandom(equiped); + c->you(MsgType::YOUR, dest->getName() + " crumbles to dust."); + c->steal({dest}); + return; +} + +HealingEffect::HealingEffect() : Effect(EffectType::HEAL) {} + +void HealingEffect::applyToCreature(Creature* c, EffectStrength strength) { + if (c->getHealth() < 1 || (strength == EffectStrength::STRONG && c->lostLimbs())) + c->heal(1, strength == EffectStrength::STRONG); + else + c->privateMessage("You feel refreshed."); +} + +SleepEffect::SleepEffect() : Effect(EffectType::SLEEP) {} + +void SleepEffect::applyToCreature(Creature* c, EffectStrength strength) { + Square *square = c->getLevel()->getSquare(c->getPosition()); + c->you(MsgType::FALL_ASLEEP, square->getName()); + c->sleep(Random.getRandom(sleepTime[strength])); +} + +GiveItemEffect::GiveItemEffect(ItemId i, int n) : Effect(EffectType::GIVE_ITEM), id(i), num(n) {} + +void GiveItemEffect::applyToCreature(Creature* c, EffectStrength strength) { + vector item = ItemFactory::fromId(id, num); + vector ref(item.size()); + transform(item.begin(), item.end(), ref.begin(), [](PItem& it) { return it.get();}); + c->getLevel()->getSquare(c->getPosition())->dropItems(std::move(item)); + c->onItemsAppeared(ref); +} + +IdentifyEffect::IdentifyEffect() : Effect(EffectType::IDENTIFY) {} + +void IdentifyEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->grantIdentify(identifyNum[strength]); +} + +PanicEffect::PanicEffect() : Effect(EffectType::PANIC) {} + +void PanicEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->panic(panicTime[strength]); +} + +RageEffect::RageEffect() : Effect(EffectType::RAGE) {} + +void RageEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->rage(panicTime[strength]); +} + +HalluEffect::HalluEffect() : Effect(EffectType::RAGE) {} + +void HalluEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->hallucinate(halluTime[strength]); +} + +StrBonusEffect::StrBonusEffect() : Effect(EffectType::STR_BONUS) {} + +void StrBonusEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->giveStrBonus(attrBonusTime[strength]); +} + +DexBonusEffect::DexBonusEffect() : Effect(EffectType::STR_BONUS) {} + +void DexBonusEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->giveDexBonus(attrBonusTime[strength]); +} + +SlowEffect::SlowEffect() : Effect(EffectType::SLOW) {} + +void SlowEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->slowDown(panicTime[strength]); +} + +SpeedEffect::SpeedEffect() : Effect(EffectType::SPEED) {} + +void SpeedEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->speedUp(panicTime[strength]); +} + +BlindnessEffect::BlindnessEffect() : Effect(EffectType::BLINDNESS) {} + +void BlindnessEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->blind(blindTime[strength]); +} + +InvisibleEffect::InvisibleEffect() : Effect(EffectType::INVISIBLE) {} + +void InvisibleEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->makeInvisible(invisibleTime[strength]); +} + +FireEffect::FireEffect() : Effect(EffectType::FIRE) {} + +void FireEffect::applyToCreature(Creature* c, EffectStrength strength) { + c->setOnFire(fireAmount[strength]); +} + +PortalEffect::PortalEffect() : Effect(EffectType::PORTAL) { +} + +void PortalEffect::applyToCreature(Creature* c, EffectStrength) { + Level* l = c->getLevel(); + for (Vec2 v : c->getPosition().neighbors8(true)) + if (l->getSquare(v)->canEnter(c)) { + l->globalMessage(v, "A magic portal appears."); + l->getSquare(v)->addTrigger(Trigger::getPortal( + ViewObject(ViewId::PORTAL, ViewLayer::LARGE_ITEM, "Portal"), l, v)); + return; + } +} + +TeleportEffect::TeleportEffect() : Effect(EffectType::TELEPORT) { +} + +void TeleportEffect::applyToCreature(Creature* c, EffectStrength) { + Vec2 pos; + int cnt = 1000; + int maxRadius = 8; + int minRadius = 4; + Level* l = c->getLevel(); + vector> dest; + for (Vec2 v : Rectangle(-maxRadius, -maxRadius, maxRadius, maxRadius)) + if (int(v.lengthD()) <= maxRadius) + dest.emplace_back(v.lengthD(), v); + sort(dest.begin(), dest.end(), + [] (const pair& e1, const pair& e2) { return e1.first > e2.first; }); + CHECK(dest.front().first > dest.back().first) << dest.front().first << " " << dest.front().second; + vector goodDest; + c->setHeld(nullptr); + for (int i : All(dest)) { + if (l->canMoveCreature(c, dest[i].second)) + goodDest.push_back(dest[i].second); + if (i > 0 && dest[i].first < dest[i - 1].first && goodDest.size() > 0) { + pos = chooseRandom(goodDest); + break; + } + if (dest[i].first < minRadius) + c->privateMessage("The spell didn't work."); + } + c->you(MsgType::TELE_DISAPPEAR, ""); + l->moveCreature(c, pos); + c->you(MsgType::TELE_APPEAR, ""); +} + +RollingBoulderEffect::RollingBoulderEffect() : Effect(EffectType::ROLLING_BOULDER) {} + +void RollingBoulderEffect::applyToCreature(Creature* c, EffectStrength strength) { + int maxDist = 7; + int minDist = 5; + Level* l = c->getLevel(); + for (int dist = maxDist; dist >= minDist; --dist) { + Vec2 pos = c->getPosition(); + vector possibleDirs; + for (Vec2 dir : Vec2::directions8()) { + bool good = true; + for (int i : Range(1, dist + 1)) + if (!l->getSquare(pos + dir * i)->canEnterEmpty(Creature::getDefault())) { + good = false; + break; + } + if (good) + possibleDirs.push_back(dir); + } + if (possibleDirs.empty()) + continue; + Vec2 dir = chooseRandom(possibleDirs); + l->globalMessage(pos + dir * dist, + MessageBuffer::important("A huge rolling boulder appears!"), + MessageBuffer::important("You hear a heavy boulder rolling.")); + Square* target = l->getSquare(pos + dir * dist); + if (target->canDestroy()) + target->destroy(1000); + if (Creature *c = target->getCreature()) { + c->you(MsgType::ARE, "killed by the boulder"); + c->die(); + } + l->addCreature(pos + dir * dist, CreatureFactory::getRollingBoulder(dir * (-1))); + return; + } +} + diff --git a/effect.h b/effect.h new file mode 100644 index 000000000..f77b4fadc --- /dev/null +++ b/effect.h @@ -0,0 +1,195 @@ +#ifndef _EFFECT_H +#define _EFFECT_H + +#include "util.h" +#include "enums.h" + +class Level; +class Creature; +class Item; + +enum class EffectStrength { WEAK, NORMAL, STRONG }; +enum class EffectType { + TELEPORT, + HEAL, + SLEEP, + IDENTIFY, + PANIC, + RAGE, + ROLLING_BOULDER, + FIRE, + SLOW, + SPEED, + HALLU, + STR_BONUS, + DEX_BONUS, + BLINDNESS, + INVISIBLE, + PORTAL, + GIVE_ITEM, + DESTROY_EQUIPMENT, + ENHANCE_ARMOR, + ENHANCE_WEAPON, + FIRE_SPHERE_PET, + GUARDING_BOULDER, + EMIT_POISON_GAS, +}; + +class Effect { + public: + virtual ~Effect() {} + static PEffect getEffect(EffectType type); + static PEffect giveItemEffect(ItemId, int num = 1); + virtual void applyToCreature(Creature*, EffectStrength) {} + EffectType getType(); + + protected: + Effect(EffectType); + + private: + EffectType type; +}; + +class EmitPoisonGasEffect : public Effect { + public: + EmitPoisonGasEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class GuardingBuilderEffect : public Effect { + public: + GuardingBuilderEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class FireSpherePetEffect : public Effect { + public: + FireSpherePetEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class EnhanceArmorEffect : public Effect { + public: + EnhanceArmorEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class EnhanceWeaponEffect : public Effect { + public: + EnhanceWeaponEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class DestroyEquipmemntEffect : public Effect { + public: + DestroyEquipmemntEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class GiveItemEffect : public Effect { + public: + GiveItemEffect(ItemId, int num); + virtual void applyToCreature(Creature*, EffectStrength); + + private: + ItemId id; + int num; +}; + +class TeleportEffect : public Effect { + public: + TeleportEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class PortalEffect : public Effect { + public: + PortalEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class HealingEffect : public Effect { + public: + HealingEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class SleepEffect : public Effect { + public: + SleepEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class PanicEffect : public Effect { + public: + PanicEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class RageEffect : public Effect { + public: + RageEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class HalluEffect : public Effect { + public: + HalluEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class StrBonusEffect : public Effect { + public: + StrBonusEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class DexBonusEffect : public Effect { + public: + DexBonusEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class IdentifyEffect : public Effect { + public: + IdentifyEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class RollingBoulderEffect : public Effect { + public: + RollingBoulderEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class FireEffect : public Effect { + public: + FireEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class SlowEffect : public Effect { + public: + SlowEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class SpeedEffect : public Effect { + public: + SpeedEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class BlindnessEffect : public Effect { + public: + BlindnessEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +class InvisibleEffect : public Effect { + public: + InvisibleEffect(); + virtual void applyToCreature(Creature*, EffectStrength); +}; + +#endif diff --git a/enemy_check.cpp b/enemy_check.cpp new file mode 100644 index 000000000..6596206e1 --- /dev/null +++ b/enemy_check.cpp @@ -0,0 +1,43 @@ +#include "stdafx.h" + + +EnemyCheck::EnemyCheck(double w) : weight(w){} + +double EnemyCheck::getWeight() const { + return weight; +} + +class AllEnemies : public EnemyCheck { + public: + AllEnemies(double weight) : EnemyCheck(weight) {} + + virtual bool hasStanding(const Creature*) const override { + return true; + } + + virtual double getStanding(const Creature*) const override { + return -1; + } +}; + +EnemyCheck* EnemyCheck::allEnemies(double weight) { + return new AllEnemies(weight); +} + +class FriendlyAnimals : public EnemyCheck { + public: + FriendlyAnimals(double weight) : EnemyCheck(weight) {} + + virtual bool hasStanding(const Creature* c) const override { + return c->isAnimal(); + } + + virtual double getStanding(const Creature* c) const override { + CHECK(c->isAnimal()); + return 1; + } +}; + +EnemyCheck* EnemyCheck::friendlyAnimals(double weight) { + return new FriendlyAnimals(weight); +} diff --git a/enemy_check.h b/enemy_check.h new file mode 100644 index 000000000..1d28c7f35 --- /dev/null +++ b/enemy_check.h @@ -0,0 +1,20 @@ +#ifndef _ENEMY_CHECK +#define _ENEMY_CHECK + +#include "creature.h" + +class EnemyCheck { + public: + EnemyCheck(double weight); + double getWeight() const; + virtual bool hasStanding(const Creature*) const = 0; + virtual double getStanding(const Creature*) const = 0; + + static EnemyCheck* allEnemies(double weight); + static EnemyCheck* friendlyAnimals(double weight); + + private: + double weight; +}; + +#endif diff --git a/entity.h b/entity.h new file mode 100644 index 000000000..1b99d1467 --- /dev/null +++ b/entity.h @@ -0,0 +1,10 @@ +#ifndef _ENTITY_H +#define _ENTITY_H + +class Entity { + public: + virtual void tick(double time) = 0; + +}; + +#endif diff --git a/enums.h b/enums.h new file mode 100644 index 000000000..fff9aab4b --- /dev/null +++ b/enums.h @@ -0,0 +1,398 @@ +#ifndef _ENUMS_H +#define _ENUMS_H + +#define ENUM_HASH(name) \ +namespace std { \ + template <> struct hash { \ + size_t operator()(const name& l) const { \ + return (size_t)l; \ + } \ + }; \ +} + +enum class MsgType { + FEEL, // better + BLEEDING_STOPS, + COLLAPSE, + FALL, + PANIC, + RAGE, + DIE_OF_BLEEDING, + ARE, // bleeding + YOUR, // your head is cut off + FALL_ASLEEP, // + WAKE_UP, + DIE, // + FALL_APART, + TELE_APPEAR, + TELE_DISAPPEAR, + ATTACK_SURPRISE, + CAN_SEE_HIDING, + SWING_WEAPON, + THRUST_WEAPON, + KICK, + PUNCH, + BITE, + CRAWL, + TRIGGER_TRAP, + DROP_WEAPON, + GET_HIT_NODAMAGE, // body part + HIT_THROWN_ITEM, + CRASH_THROWN_ITEM, + MISS_THROWN_ATTACK, + STAND_UP, + TURN_INVISIBLE, + TURN_VISIBLE, + ITEM_CRASHES, + ENTER_PORTAL, + HAPPENS_TO, + BURN, + DROWN, + SET_UP_TRAP, + KILLED_BY, + MISS_ATTACK}; // +enum class BodyPart { HEAD, TORSO, ARM, WING, LEG, BACK}; +enum class AttackType { CUT, STAB, CRUSH, PUNCH, BITE, HIT, SHOOT}; +enum class AttackLevel { LOW, MIDDLE, HIGH }; +enum class AttrType { STRENGTH, DAMAGE, TO_HIT, THROWN_DAMAGE, THROWN_TO_HIT, DEXTERITY, DEFENSE, SPEED, INV_LIMIT}; +enum class ItemType { WEAPON, RANGED_WEAPON, AMMO, ARMOR, SCROLL, POTION, BOOK, AMULET, TOOL, OTHER, GOLD, FOOD, + CORPSE }; +enum class EquipmentSlot { WEAPON, RANGED_WEAPON, BODY_ARMOR, HELMET, AMULET }; +enum class ArmorType { BODY_ARMOR, HELMET }; + +enum class SquareApplyType { DRINK, USE_CHEST, ASCEND, DESCEND, PRAY, SLEEP, TRAIN, WORKSHOP }; + +enum class MinionTask { EAT, SLEEP, TRAIN, TRAIN_IDLE, WORKSHOP, WORKSHOP_IDLE }; +enum class TrapType { BOULDER, POISON_GAS }; + +enum class SquareAttrib { + NO_DIG, + MOUNTAIN, + HILL, + LOWLAND, + CONNECT, + LAKE, + RIVER, + ROAD_CUT_THRU, + ROOM, +}; + +ENUM_HASH(SquareAttrib); + +enum class StairKey { DWARF, CRYPT, GOBLIN, PLAYER_SPAWN, PYRAMID, TOWER }; +enum class StairDirection { UP, DOWN }; + +enum class CreatureId { + GOBLIN, + GREAT_GOBLIN, + BANDIT, + + SPECIAL_MONSTER, + SPECIAL_MONSTER_HUMANOID, + + ZOMBIE, + VAMPIRE, + VAMPIRE_BAT, + MUMMY, + MUMMY_LORD, + + DWARF, + DWARF_BARON, + + IMP, + BILE_DEMON, + HELL_HOUND, + CHICKEN, + DUNGEON_HEART, + + KNIGHT, + AVATAR, + ARCHER, + PESEANT, + CHILD, + + ELF, + ELF_CHILD, + ELF_LORD, + HORSE, + COW, + SHEEP, + PIG, + + GNOME, + LEPRECHAUN, + + JACKAL, + DEER, + BOAR, + FOX, + VULTURE, + WOLF, + + DEATH, + NIGHTMARE, + FIRE_SPHERE, + KRAKEN, + BAT, + SNAKE, + CAVE_BEAR, + RAT}; + + +enum class ItemId { KNIFE, + SWORD, + SPECIAL_SWORD, + ELVEN_SWORD, + SPECIAL_ELVEN_SWORD, + BATTLE_AXE, + SPECIAL_BATTLE_AXE, + WAR_HAMMER, + SPECIAL_WAR_HAMMER, + SCYTHE, + BOW, + ARROW, + LEATHER_ARMOR, + LEATHER_HELM, + CHAIN_ARMOR, + IRON_HELM, + TELE_SCROLL, + PORTAL_SCROLL, + IDENTIFY_SCROLL, + BOULDER_SCROLL, + FIRE_SCROLL, + POISON_GAS_SCROLL, + DESTROY_EQ_SCROLL, + ENHANCE_A_SCROLL, + ENHANCE_W_SCROLL, + FIRE_SPHERE_SCROLL, + HEALING_POTION, + SLEEP_POTION, + BLINDNESS_POTION, + INVISIBLE_POTION, + PANIC_MUSHROOM, + RAGE_MUSHROOM, + STRENGTH_MUSHROOM, + DEXTERITY_MUSHROOM, + HALLU_MUSHROOM, + SLOW_POTION, + SPEED_POTION, + WARNING_AMULET, + HEALING_AMULET, + DEFENSE_AMULET, + FRIENDLY_ANIMALS_AMULET, + FIRST_AID_KIT, + ROCK, + GOLD_PIECE, + MUSHROOM_BOOK, + POTION_BOOK, + AMULET_BOOK, + BOULDER_TRAP_ITEM, + GAS_TRAP_ITEM, +}; + +enum class ViewLayer { + CREATURE, + LARGE_ITEM, + ITEM, + FLOOR, +}; + +enum class HighlightType { + BUILD, + POISON_GAS, +}; + +const static vector allLayers = + {ViewLayer::FLOOR, ViewLayer::ITEM, ViewLayer::LARGE_ITEM, ViewLayer::CREATURE}; + +ENUM_HASH(ViewLayer); + +enum class ViewId { + PLAYER, + ELF, + ELF_CHILD, + ELF_LORD, + ELVEN_SHOPKEEPER, + DWARF, + DWARF_BARON, + DWARVEN_SHOPKEEPER, + IMP, + BILE_DEMON, + HELL_HOUND, + CHICKEN, + KNIGHT, + AVATAR, + ARCHER, + PESEANT, + CHILD, + GREAT_GOBLIN, + GOBLIN, + BANDIT, + ZOMBIE, + VAMPIRE, + MUMMY, + MUMMY_LORD, + HORSE, + COW, + PIG, + SHEEP, + JACKAL, + DEER, + BOAR, + FOX, + BEAR, + WOLF, + BAT, + RAT, + SNAKE, + VULTURE, + GNOME, + VODNIK, + LEPRECHAUN, + KRAKEN, + KRAKEN2, + FIRE_SPHERE, + NIGHTMARE, + DEATH, + SPECIAL_BEAST, + SPECIAL_HUMANOID, + UNKNOWN_MONSTER, + + FLOOR, + SAND, + BRIDGE, + PATH, + GRASS, + WALL, + HILL, + MOUNTAIN, + SNOW, + GOLD_ORE, + WOOD_WALL, + BLACK_WALL, + YELLOW_WALL, + SECRETPASS, + DOWN_STAIRCASE, + UP_STAIRCASE, + CANIF_TREE, + DECID_TREE, + BUSH, + WATER, + MAGMA, + ABYSS, + DOOR, + FOUNTAIN, + CHEST, + COFFIN, + BED, + TORTURE_TABLE, + TRAINING_DUMMY, + WORKSHOP, + DUNGEON_HEART, + ALTAR, + GRAVE, + BARS, + BORDER_GUARD, + DESTROYED_FURNITURE, + BURNT_FURNITURE, + FALLEN_TREE, + BURNT_TREE, + + BODY_PART, + BONE, + SWORD, + ELVEN_SWORD, + KNIFE, + WAR_HAMMER, + BATTLE_AXE, + BOW, + ARROW, + SCROLL, + STEEL_AMULET, + COPPER_AMULET, + CRYSTAL_AMULET, + WOODEN_AMULET, + AMBER_AMULET, + BOOK, + FIRST_AID, + EFFERVESCENT_POTION, + MURKY_POTION, + SWIRLY_POTION, + VIOLET_POTION, + PUCE_POTION, + SMOKY_POTION, + FIZZY_POTION, + MILKY_POTION, + GOLD, + LEATHER_ARMOR, + LEATHER_HELM, + CHAIN_ARMOR, + IRON_HELM, + BOULDER, + UNARMED_BOULDER_TRAP, + PORTAL, + TRAP, + GAS_TRAP, + UNARMED_GAS_TRAP, + ROCK, + SLIMY_MUSHROOM, + PINK_MUSHROOM, + DOTTED_MUSHROOM, + GLOWING_MUSHROOM, + GREEN_MUSHROOM, + BLACK_MUSHROOM, + + TRAP_ITEM, + GUARD_POST, +}; + +enum class Gender { + MALE, + FEMALE, +}; + +enum class SquareType { + FLOOR, + BRIDGE, + PATH, + GRASS, + ROCK_WALL, + LOW_ROCK_WALL, + WOOD_WALL, + BLACK_WALL, + YELLOW_WALL, + MOUNTAIN, + GLACIER, + HILL, + SECRET_PASS, + WATER, + MAGMA, + GOLD_ORE, + ABYSS, + SAND, + DECID_TREE, + CANIF_TREE, + BUSH, + BED, + TORTURE_TABLE, + TRAINING_DUMMY, + WORKSHOP, + HATCHERY, + DUNGEON_HEART, + GRAVE, + ROLLING_BOULDER, + FOUNTAIN, + CHEST, + TREASURE_CHEST, + COFFIN, + IRON_BARS, + DOOR, + TRIBE_DOOR, + DOWN_STAIRS, + UP_STAIRS, + BORDER_GUARD, + ALTAR, +}; + + +#endif diff --git a/equipment.cpp b/equipment.cpp new file mode 100644 index 000000000..ed26b60d6 --- /dev/null +++ b/equipment.cpp @@ -0,0 +1,56 @@ +#include "stdafx.h" + +using namespace std; + +Item* Equipment::getItem(EquipmentSlot slot) const { + if (items.count(slot) > 0) + return items.at(slot); + else + return nullptr; +} + +bool Equipment::isEquiped(const Item* item) const { + for (auto elem : items) + if (elem.second == item) { + return true; + } + return false; +} + +EquipmentSlot Equipment::getSlot(const Item* item) const { + for (auto elem : items) + if (elem.second == item) { + return elem.first; + } + Debug(FATAL) << "Item not in any slot " << item->getAName(); + return EquipmentSlot::WEAPON; +} + +void Equipment::equip(Item* item, EquipmentSlot slot) { + items[slot] = item; + CHECK(hasItem(item)); +} + +void Equipment::unequip(EquipmentSlot slot) { + CHECK(items.count(slot) > 0); + items.erase(slot); +} + +PItem Equipment::removeItem(Item* item) { + if (isEquiped(item)) + unequip(getSlot(item)); + return Inventory::removeItem(item); +} + +vector Equipment::removeItems(const vector& items) { + vector ret; + for (Item* it : items) + ret.push_back(removeItem(it)); + return ret; +} + +vector Equipment::removeAllItems() { + items.clear(); + return Inventory::removeAllItems(); +} + diff --git a/equipment.h b/equipment.h new file mode 100644 index 000000000..4882c42ae --- /dev/null +++ b/equipment.h @@ -0,0 +1,23 @@ +#ifndef _EQUIPMENT_H +#define _EQUIPMENT_H + +#include "inventory.h" +#include "enums.h" + + +class Equipment : public Inventory { + public: + Item* getItem(EquipmentSlot slot) const; + bool isEquiped(const Item*) const; + EquipmentSlot getSlot(const Item*) const; + void equip(Item*, EquipmentSlot); + void unequip(EquipmentSlot slot); + PItem removeItem(Item*); + vector removeItems(const vector&); + vector removeAllItems(); + + private: + map items; +}; + +#endif diff --git a/event.cpp b/event.cpp new file mode 100644 index 000000000..2fbba42a9 --- /dev/null +++ b/event.cpp @@ -0,0 +1,67 @@ +#include "stdafx.h" + +vector EventListener::listeners; + +void EventListener::addListener(EventListener* l) { + listeners.push_back(l); +} + +void EventListener::removeListener(EventListener* l) { + removeElement(listeners, l); +} + +void EventListener::addPickupEvent(const Creature* c, const vector& items) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == c->getLevel() || l->getListenerLevel() == nullptr) + l->onPickupEvent(c, items); +} + +void EventListener::addDropEvent(const Creature* c, const vector& items) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == c->getLevel() || l->getListenerLevel() == nullptr) + l->onDropEvent(c, items); +} + +void EventListener::addItemsAppeared(const Level* level, Vec2 position, const vector& items) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == level) + l->onItemsAppeared(position, items); +} + +void EventListener::addKillEvent(const Creature* victim, const Creature* killer) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == victim->getLevel() || l->getListenerLevel() == nullptr) + l->onKillEvent(victim, killer); +} + +void EventListener::addAttackEvent(const Creature* victim, const Creature* attacker) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == victim->getLevel() || l->getListenerLevel() == nullptr) + l->onAttackEvent(victim, attacker); +} + +void EventListener::addThrowEvent(const Level* level, const Creature* thrower, + const Item* item, const vector& trajectory) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == level || l->getListenerLevel() == nullptr) + l->onThrowEvent(thrower, item, trajectory); +} + +void EventListener::addTriggerEvent(const Level* level, Vec2 pos) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == level || l->getListenerLevel() == nullptr) + l->onTriggerEvent(level, pos); +} + +void EventListener::addSquareReplacedEvent(const Level* level, Vec2 pos) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == level || l->getListenerLevel() == nullptr) + l->onSquareReplacedEvent(level, pos); +} + +void EventListener::addChangeLevelEvent(const Creature* c, const Level* level, Vec2 pos, + const Level* to, Vec2 toPos) { + for (EventListener* l : listeners) + if (l->getListenerLevel() == level || l->getListenerLevel() == nullptr) + l->onChangeLevelEvent(c, level, pos, to, toPos); +} diff --git a/event.h b/event.h new file mode 100644 index 000000000..81056fedb --- /dev/null +++ b/event.h @@ -0,0 +1,36 @@ +#ifndef _EVENT_H +#define _EVENT_H + + +class EventListener { + public: + virtual void onPickupEvent(const Creature*, const vector& items) {} + virtual void onDropEvent(const Creature*, const vector& items) {} + virtual void onItemsAppeared(Vec2 position, const vector& items) {} + virtual void onKillEvent(const Creature* victim, const Creature* killer) {} + virtual void onAttackEvent(const Creature* victim, const Creature* attacker) {} + virtual void onThrowEvent(const Creature* thrower, const Item* item, const vector& trajectory) {} + virtual void onTriggerEvent(const Level*, Vec2 pos) {} + virtual void onSquareReplacedEvent(const Level*, Vec2 pos) {} + virtual void onChangeLevelEvent(const Creature*, const Level* from, Vec2 pos, const Level* to, Vec2 toPos) {} + + static void addPickupEvent(const Creature*, const vector& items); + static void addDropEvent(const Creature*, const vector& items); + static void addItemsAppeared(const Level*, Vec2 position, const vector& items); + static void addKillEvent(const Creature* victim, const Creature* killer); + static void addAttackEvent(const Creature* victim, const Creature* attacker); + static void addThrowEvent(const Level*, const Creature* thrower, const Item* item, const vector& trajectory); + static void addTriggerEvent(const Level*, Vec2 pos); + static void addSquareReplacedEvent(const Level*, Vec2 pos); + static void addChangeLevelEvent(const Creature*, const Level* from, Vec2 pos, const Level* to, Vec2 toPos); + + static void addListener(EventListener*); + static void removeListener(EventListener*); + + virtual const Level* getListenerLevel() const { return nullptr; } + + private: + static vector listeners; +}; + +#endif diff --git a/field_of_view.cpp b/field_of_view.cpp new file mode 100644 index 000000000..69be5be49 --- /dev/null +++ b/field_of_view.cpp @@ -0,0 +1,114 @@ +#include "stdafx.h" + +using namespace std; + + +FieldOfView::FieldOfView(const Table& s) : squares(s), visibility(squares.getWidth(), squares.getHeight()) { +} + +bool FieldOfView::canSee(Vec2 from, Vec2 to) { + if ((from - to).lengthD() > sightRange) + return false; + if (!visibility[from]) + visibility[from] = Visibility(squares, from.x, from.y); + return visibility[from]->checkVisible(to.x - from.x, to.y - from.y); +} + +void FieldOfView::squareChanged(Vec2 pos) { + vector updateList; + if (!visibility[pos]) + visibility[pos] = Visibility(squares, pos.x, pos.y); + for (Vec2 v : visibility[pos]->getVisibleTiles()) + if (visibility[v] && visibility[v]->checkVisible(pos.x - v.x, pos.y - v.y)) { + visibility[v] = Nothing(); + } +} + +void FieldOfView::Visibility::setVisible(int x, int y) { + if (!visible[x + sightRange][y + sightRange] && x * x + y * y <= sightRange * sightRange) { + visible[x + sightRange][y + sightRange] = 1; + visibleTiles.push_back(Vec2(px + x, py + y)); + } +} + +static int totalIter = 0; +static int numSamples = 0; + +FieldOfView::Visibility::Visibility(const Table& squares, int x, int y) : px(x), py(y) { + memset(visible, 0, (2 * sightRange + 1) * (2 * sightRange + 1)); + calculate(2 * sightRange, 2 * sightRange,2 * sightRange, 2,-1,1,1,1, + [&](int px, int py) { return !squares[x + px][y + py]->canSeeThru(); }, + [&](int px, int py) { setVisible(px ,py); }); + calculate(2 * sightRange, 2 * sightRange,2 * sightRange, 2,-1,1,1,1, + [&](int px, int py) { return !squares[x + py][y - px]->canSeeThru(); }, + [&](int px, int py) { setVisible(py, -px); }); + calculate(2 * sightRange, 2 * sightRange,2 * sightRange,2,-1,1,1,1, + [&](int px, int py) { return !squares[x - px][y - py]->canSeeThru(); }, + [&](int px, int py) { setVisible(-px, -py); }); + calculate(2 * sightRange, 2 * sightRange,2 * sightRange,2,-1,1,1,1, + [&](int px, int py) { return !squares[x - py][y + px]->canSeeThru(); }, + [&](int px, int py) { setVisible(-py, px); }); + setVisible(0, 0); + ++numSamples; + totalIter += visibleTiles.size(); + if (numSamples%100 == 0) + Debug() << numSamples << " iterations " << totalIter / numSamples << " avg"; +} + +const vector& FieldOfView::Visibility::getVisibleTiles() const { + return visibleTiles; +} + +const vector& FieldOfView::getVisibleTiles(Vec2 from) { + if (!visibility[from]) { + visibility[from] = Visibility(squares, from.x, from.y); + } + return visibility[from]->getVisibleTiles(); +} + + +void FieldOfView::Visibility::calculate(int left, int right, int up, int h, int x1, int y1, int x2, int y2, + function isBlocking, function setVisible){ + if (y2*x1>=y1*x2) return; + if (h>up) return; + int leftx=x1, lefty=y1, rightx=x2, righty=y2; + int left_v=(int)floor((double)x1/y1*(h)), + right_v=(int)ceil((double)x2/y2*(h)), + left_b=(int)floor((double)x1/y1*(h-1)), + right_b=(int)ceil((double)x2/y2*(h+1)); + if (left_v % 2) + ++left_v; + if (right_v % 2) + --right_v; + if(left_b % 2) + ++left_b; + if(right_b % 2) + --right_b; + + if(left_b>=-left && left_b<=right && isBlocking(left_b/2,h/2)){ + leftx=left_b+1; + lefty=h+(left_b>=0?-1:1); + } + if(left_v<-left) left_v=-left; + if(right_v>right) right_v=right; + bool prevBlocking = false; + for (int i=left_v/2;i<=right_v/2;++i){ + setVisible(i, h / 2); + bool blocking = isBlocking(i, h / 2); + if(i > left_v / 2 && blocking && !prevBlocking) + calculate(left, right, up, h + 2, leftx, lefty, i * 2 - 1, h + (i<=0 ? -1:1), isBlocking, setVisible); + if(blocking){ + leftx=i*2+1; + lefty=h+(i>=0?-1:1); + } + prevBlocking = blocking; + } + calculate(left, right, up, h + 2, leftx, lefty, rightx, righty, isBlocking, setVisible); +} + +bool FieldOfView::Visibility::checkVisible(int x, int y) const { + return x >= -sightRange && y >= -sightRange && x <= sightRange && y <= sightRange && + visible[sightRange + x][sightRange + y] == 1; +} + + diff --git a/field_of_view.h b/field_of_view.h new file mode 100644 index 000000000..1790cc82b --- /dev/null +++ b/field_of_view.h @@ -0,0 +1,42 @@ +#ifndef _FIELD_OF_VIEW_H +#define _FIELD_OF_VIEW_H + +#include "util.h" +#include "square.h" + +class FieldOfView { + public: + FieldOfView(const Table& squares); + bool canSee(Vec2 from, Vec2 to); + const vector& getVisibleTiles(Vec2 from); + void squareChanged(Vec2 pos); + + private: + + const static int sightRange = 30; + + class Visibility { + char visible[sightRange * 2 + 1][sightRange * 2 + 1]; + vector visibleTiles; + void calculate(int,int,int,int, int, int, int, int, + function isBlocking, + function setVisible); + void setVisible(int, int); + + int px, py; + + public: + + bool checkVisible(int x,int y) const; + const vector& getVisibleTiles() const; + + Visibility(const Table& squares, int x, int y); + Visibility(Visibility&&) = default; + Visibility& operator = (Visibility&&) = default; + }; + + const Table& squares; + Table> visibility; +}; + +#endif diff --git a/fire.cpp b/fire.cpp new file mode 100644 index 000000000..ef94535cc --- /dev/null +++ b/fire.cpp @@ -0,0 +1,33 @@ +#include "stdafx.h" + +Fire::Fire(double objectWeight, double objectFlamability) : weight(objectWeight), flamability(objectFlamability) {} + +double epsilon = 0.001; + +void Fire::tick(Level* level, Vec2 position) { + burnt = min(1., burnt + size / weight); + size += (burnt * weight - size) / 10; + size *= (1 - burnt); + if (size < epsilon && burnt > 1 - epsilon) { + size = 0; + burnt = 1; + } +} + +void Fire::set(double amount) { + if (!isBurntOut() && amount > epsilon) + size = max(size, amount * flamability); +} + +bool Fire::isBurning() const { + return size > 0; +} + +double Fire::getSize() const { + return size; +} + +bool Fire::isBurntOut() const { + return burnt > 0.999 && size == 0; +} + diff --git a/fire.h b/fire.h new file mode 100644 index 000000000..2391ce40d --- /dev/null +++ b/fire.h @@ -0,0 +1,22 @@ +#ifndef _FIRE_H +#define _FIRE_H + +#include "util.h" + +class Fire { + public: + Fire(double objectWeight, double objectFlamability); + void tick(Level* level, Vec2 position); + void set(double amount); + bool isBurning() const; + double getSize() const; + bool isBurntOut() const; + + private: + double burnt = 0; + double size = 0; + double weight; + double flamability; +}; + +#endif diff --git a/first_names.txt b/first_names.txt new file mode 100644 index 000000000..242d7e335 --- /dev/null +++ b/first_names.txt @@ -0,0 +1,1758 @@ +Achar +Acharian +Achariard +Acharles +Acharraz +Achilbert +Achilip +Achilippe +Achillefer +Achilm +Adalarian +Adalarias +Adalariasz +Adalarin +Adalario +Adalariusz +Adalbor +Adriasz +Adriaume +Adrie +Adried +Adriel +Adriet +Aelfram +Aelfred +Aelfrid +Aelfried +Aelfriel +Aelfrien +Aelfriet +Agapin +Agatol +Agniec +Agniel +Agnier +Agnieu +Agniew +Agniewomir +Agobald +Agobaldem +Agobaldo +Agobaldric +Agobaldwin +Aigne +Alargomir +Alargost +Alarik +Alarin +Alario +Alarius +Alariusz +Alazar +Alazarz +Albercik +Albero +Albrech +Albrechier +Alche +Alchel +Aldebert +Aldebran +Aldebras +Aldem +Aldemar +Alder +Alderic +Alderice +Alderich +Aldericio +Alderyk +Aleaumir +Aleaumił +Aleaux +Alebranck +Alebras +Alejand +Alejander +Alejando +Aleksan +Alero +Alerome +Aleron +Alexand +Alexando +Alexandro +Alfon +Alfrem +Alfres +Aliau +Alodebert +Alodem +Aloderick +Alons +Alonstafa +Alonstan +Alonstavo +Alonstaw +Alureli +Alurelin +Alurent +Alurentier +Alurentin +Alurentule +Alurenty +Amadero +Amaderome +Amaderon +Amaderot +Amalvador +Amaniel +Amanielise +Amanimir +Amanio +Ambrond +Ambrosław +Ambrożan +Ami +Amiga +Amigan +Ammian +Ammianuel +Anastazar +Anastiagot +Anastian +Anastias +Anastiau +Anastien +Anastier +Anastor +Anaton +Ander +Anderanck +Anderand +Anderic +Anderich +Anderick +Anderyk +Andre +Annas +Annastaf +Annastan +Ansel +Anselin +Ansell +Anter +Antoigne +Antoignen +Antoigniel +Anton +Anzel +Apollivet +Apollivier +Apolo +Apolon +Apolonim +Apolonio +Apolonius +Apolonizy +Araimbe +Araimbert +Araimberto +Araime +Arbercik +Arbero +Archiel +Archier +Archierz +Archille +Archillot +Archim +Arman +Armaniec +Armaniel +Armanier +Armanieu +Armann +Armanuel +Arnald +Arnall +Arnolit +Arnolito +Arnolo +Arthuel +Arthuellen +Arvid +Aría +Augebero +Augusz +Augusłan +Augusław +Aurel +Aureliam +Aureliasz +Aureliaume +Auré +Aurégory +Avenebald +Aveneban +Avenebaut +Avenedykt +Aveneus +Aveneusz +Bader +Baderand +Baderic +Baderice +Baderich +Baderick +Baders +Baderyk +Baltazaïs +Balther +Bangier +Bangierd +Bangierre +Bangierry +Baptist +Baptistan +Baptistand +Baptistian +Baptistiau +Baptistof +Barabastin +Bardian +Barto +Bartolf +Bartolt +Bartunat +Bartur +Bartus +Bartłomir +Bartłorad +Bartłowit +Beniamian +Beniamir +Benian +Beniau +Benjamian +Benjamien +Benjamir +Benjamián +Bercin +Bereneusz +Berent +Berenville +Bernand +Bernaud +Bernaude +Bernaudien +Bernays +Bille +Billefer +Billemal +Billemar +Billen +Billerman +Billermann +Billermes +Billermo +Billermon +Billermond +Billermont +Billes +Billet +Blasil +Bochael +Bochal +Bochar +Bocharie +Bochart +Bochartain +Bochartwig +Bochaël +Bochał +Boemund +Boemundulf +Bogust +Bogustian +Bogustyn +Bohemon +Bohemont +Bolebaut +Bolebraham +Bolebran +Bolebrand +Bolebras +Boles +Bolesfor +Boleslaus +Bolestyn +Boryspyn +Boryst +Borystian +Borystien +Borystine +Borystiner +Borystyn +Borzy +Borzynian +Borzyniano +Borzyniec +Borzyniech +Borzyszar +Bouchar +Boucharles +Bouche +Bouchier +Bouchieu +Bouchwał +Bożamian +Bożamien +Bożamil +Bożamin +Bożamián +Boży +Bożymilip +Bożymir +Bożymitr +Bromiej +Brominiau +Brominique +Bromiłosz +Broni +Bronifacy +Bronim +Bronio +Bronius +Broniusz +Bronizy +Burchart +Burchibal +Burchibald +Burchiel +Burchier +Burchieu +Burner +Burnest +Burnester +Burnestero +Burnesto +Burnestor +Burnestyn +Busel +Busell +Buselm +Buthram +Błażesz +Carmes +Caspan +Caspazjan +Celes +Celesfor +Celeslaus +Celeslav +Celesław +Charl +Charlan +Charland +Charlando +Charlos +Chart +Chartin +Chartuś +Chartwig +Chocisław +Chris +Chriset +Chryspyn +Chryst +Chrystin +Chwalbert +Chwald +Chwaldo +Chwaldon +Chwalibor +Chwalise +Cicho +Cichosłaj +Claud +Claudier +Claudiusz +Clémeno +Clémens +Clémi +Clémy +Colbero +Colberome +Cosmar +Czesładyn +Czesłaj +Czesłan +Daimbero +Dalmar +Damiej +Damierz +Damierzy +Dargomiej +Dargosłan +Delio +Deliodolph +Deliodor +Derwin +Didien +Didietric +Didietrick +Didietron +Didietrond +Didietrus +Dietric +Dietron +Dietrond +Dietruche +Dietruchel +Dietrus +Dioni +Dionifacy +Dionim +Dionin +Dionio +Dionisław +Dionius +Dioniusz +Dobien +Dobrond +Dobrosłan +Dobrożan +Dobroży +Domin +Donall +Doriasz +Doriaume +Dortmunt +Duszard +Duszardin +Duszardo +Eckehar +Eckeharles +Eckeharraz +Eckehart +Eckhard +Eckhardo +Edgar +Edgart +Edmunt +Eduard +Eduart +Eduary +Edwaryspyn +Edwaryst +Edwarystyn +Egbercik +Egbero +Elias +Eligibert +Eligiberto +Eligismund +Eligismunt +Eligius +Emambard +Emanuardo +Emanuart +Emanuary +Emanus +Emanusz +Emman +Emmando +Emmaniel +Emmanier +Emmanieu +Emmaniew +Emmann +Engel +Engelarich +Engelarik +Engelio +Engelion +Engelmas +Engen +Engenius +Engenouet +Eorpwalis +Erengar +Erengard +Erengart +Ernes +Esdelard +Esdelarice +Esdelarico +Esdelarik +Esdelart +Esdelary +Esdelin +Esdelinard +Esdelind +Esdelino +Esdelint +Esdelinus +Esdelon +Esdelonso +Estouyn +Ethel +Ethelm +Eusebercik +Eusebert +Euseberto +Eusebertur +Ewary +Ewaryn +Ezech +Ezechar +Ezechard +Ezechardt +Ezecharles +Ezechier +Ezechierre +Ezechieu +Ezecht +Fabias +Fabiasz +Fabiesłaj +Fabiesław +Fabrician +Fabriel +Fangel +Fangelard +Fangelardo +Fangelardt +Fangelbert +Fangelm +Fangenon +Fangenot +Fangerus +Faust +Faustaf +Faustafa +Faustavo +Faustawian +Fauste +Faustin +Faustine +Feder +Federanck +Federic +Federich +Federick +Federond +Federonim +Federonius +Feders +Federyk +Felikar +Felikarp +Felikary +Felip +Felippe +Ferdynard +Fernabé +Fernald +Fernaldo +Fernand +Fernard +Fernardin +Fernardo +Fernardt +Fernaude +Fernays +Fiebran +Fiebranc +Fiebrancin +Fiebrancis +Fiebrand +Fiebrank +Fiebranz +Flambe +Flambero +Flambert +Flore +Florenzel +Florenzo +Florin +Floris +Fluel +Fluelle +Fluellefer +Fluellermo +Fluelles +Fortur +Fortus +Fortuś +Fourcaud +Fourchard +Fourchardo +Fourcharic +Fourchario +Fourchaël +Frederic +Frederico +Frederiet +Frederiete +Frederigo +Fremigius +Fremigiusz +Fremir +Fried +Friedhart +Friedrien +Frydery +Fryderyn +Frédrian +Frédric +Frédrien +Gabrice +Gabrician +Gabricio +Gabricion +Gamal +Gamalric +Gamalrich +Gamalvis +Ganel +Gardo +Gardomiej +Garsi +Garsieu +Gascouf +Gascoufil +Gaubercik +Gauchart +Gauche +Gaugust +Gaugustavo +Gaugustin +Gaugustyn +Gaétout +Gaétouyn +Gembe +Gentiago +Gentier +Gentierd +Gentierre +Gentin +Gerhard +Gerhardo +Gerwacy +Gesebius +Gesebiusz +Gilbero +Gildo +Gille +Gilleban +Gillefer +Gillet +Girian +Giriano +Girianolon +Giriard +Giriardin +Giriardo +Giriasz +Gniew +Gobel +Gobelard +Gobelarge +Gobelargen +Gobelarich +Gobelarik +Gobelart +Gobelary +Godebraham +Godebran +Godebrand +Godebras +Goder +Goderic +Goderich +Goderyk +Godfriel +Godic +Godichael +Godichar +Godichard +Godrian +Godriennes +Goliam +Golian +Goliano +Goliaume +Gonfred +Gonfreder +Gonfrie +Gonfriel +Gontiago +Gontian +Gontien +Gontiennet +Gontin +Gotelin +Gotelinard +Gotelinart +Gotelint +Gottfric +Gottfrid +Gotthew +Gotthewin +Gotthias +Gotthier +Gotthieu +Gotthilip +Gotthilipe +Gotthille +Goume +Goumel +Graciej +Gracint +Gracio +Gracy +Grefiryn +Grego +Gringer +Gringerus +Grzegor +Grzegory +Grégis +Grégor +Grégorz +Guent +Guentiau +Guentier +Guentin +Guentule +Guenty +Guentyn +Guerman +Guermanuel +Guermes +Guermo +Guermon +Guermond +Guillard +Guille +Guilles +Guilleslav +Guillet +Guillias +Guillibor +Guillot +Guine +Guinebor +Guiner +Gundechiel +Gundelino +Gunder +Gunders +Gundersz +Gunther +Gunthere +Gustan +Gustanc +Gustanck +Gustando +Gustantian +Gustantiau +Gustantin +Gustantine +Gustanto +Gustanty +Gustantyn +Gustazy +Gustazyli +Gwalbercik +Gwalbercin +Gwalbor +Gwalborad +Habrech +Habrechard +Habrechart +Habrechier +Habrechieu +Habrecht +Habrehard +Habrehardo +Habrehaue +Hadric +Hadrich +Hadrichard +Hadricho +Hadrien +Hadriennet +Haiet +Haietrice +Haietrich +Haietriche +Haietricho +Haietrick +Halebaut +Halebor +Haleborad +Halebraham +Halebras +Halin +Halind +Halino +Halint +Halinus +Halinusz +Hammon +Hanus +Harchibald +Harchiel +Harol +Hascot +Hasjürge +Hasjürges +Hebercik +Hebero +Hecel +Heceli +Hecelo +Hedwik +Hedwin +Heinric +Heinrice +Heinricio +Heinrick +Helio +Helion +Hensel +Henselm +Henselmar +Henselmuth +Herbrancis +Herchibald +Heribert +Hermelon +Hernold +Hernouen +Hernán +Herraz +Hersche +Herscher +Hewalbert +Hewalibor +Hewalibóg +Hewalise +Hiacy +Hiacyen +Hiero +Hierome +Hieron +Hieronin +Hieronio +Hieroniusz +Hieronizy +Hierot +Hilard +Hilargen +Hilaric +Hilarich +Hilarik +Hilart +Hildebranc +Hildefons +Hildemach +Hildemal +Hildemar +Hildemund +Hildemus +Hilderard +Hilderardo +Hilderian +Hipold +Hipolio +Hipoliodor +Hipolion +Hipollo +Hipollot +Hipolon +Hipolonso +Horaciej +Horacien +Horacjan +Hubercin +Hubero +Hueban +Huebastazy +Huebastian +Huebastias +Huebastiau +Huebastier +Huebastor +Huebaut +Huide +Huidelo +Huidelot +Humbe +Hunouet +Hunouf +Huroi +Ilbero +Ildefon +Inocelian +Inocelin +Inocelino +Inocelinus +Inocelio +Inocelion +Inocelipe +Inocent +Inocenther +Inocenthew +Inocentian +Inocentien +Inocentin +Inocequin +Irenebald +Irenebaldo +Ireneban +Irenebaut +Irenedykt +Irenel +Ireneus +Jacin +Jadwig +Jadwik +Jadwin +Japhe +Japher +Jarome +Jehar +Jehard +Jehardt +Jeharles +Jehart +Jehartain +Jeromir +Jerón +Jessart +Jessartain +Jessartin +Jessartine +Jessartuś +Jimens +Jiment +Joach +Joachar +Joachard +Joachardt +Joache +Johan +Johane +Johanne +Johannet +Jolliam +Jolliamin +Jollibron +Jollibrond +Jollivelet +Jose +Josepe +Julias +Juseph +Justian +Justiano +Justianus +Justien +Justyn +Justyniec +Justyniech +Jędrzego +Jędrzegor +Kanim +Kanimo +Kaspan +Kaspazjan +Kasto +Kastof +Kastophe +Kastopher +Kastout +Kazimiej +Kazimien +Kerrick +Kiliam +Kilias +Kiliasz +Kiliaume +Kiliaumin +Klaud +Klaude +Klaudien +Konst +Konstan +Konstand +Konstaniel +Konstanien +Konstanis +Kordin +Korner +Kornest +Kornester +Kornesto +Kornestyn +Krist +Kristan +Kristand +Kriste +Kristian +Kristias +Kristiau +Kristophe +Kristopher +Kristophet +Kryst +Krystiagot +Krystias +Krystiau +Krzesłan +Krzesław +Krzysłan +Krzysław +Ksaweł +Ksawełk +Lambe +Laured +Laureli +Laureliks +Laurelin +Laurelino +Leand +Leanderic +Leanderich +Leanderyk +Leando +Leandre +Leandres +Leavo +Lenard +Lenardo +Lenardomir +Letarwin +Letarwit +Lionet +Liudgar +Liudgart +Longier +Longierd +Longieron +Longierre +Longierz +Lorencjusz +Loreneusz +Lorengar +Lorengier +Lorengin +Lorent +Lorentiago +Lorentien +Lorentin +Lorenty +Lorentyn +Lothan +Lothas +Lothazar +Lothazarz +Lothazaïs +Lubero +Lubor +Ludolph +Ludwin +Luis Filio +Luthelbert +Luthew +Lämmer +Lämmerus +Maciech +Maciechard +Maciechier +Maciecht +Maciemin +Maciemir +Macien +Maillard +Maillaus +Maille +Maillebaut +Maillefer +Mailles +Maillesfor +Mailleslav +Maillian +Maillibor +Mainne +Makar +Makarp +Maksy +Maksymilip +Maksymir +Maleksand +Maleksando +Maleksy +Maleksymir +Malis +Malisław +Manfriel +Manfriet +Manfroi +Manfroin +Manold +Manolit +Mariard +Mariaumin +Mariel +María +Marían +Mathias +Mathiasz +Matthier +Matthilip +Matthilipe +Matthille +Matthilm +Maynand +Maynando +Maynandro +Medar +Medart +Meinhar +Meinhold +Meried +Meriel +Merien +Mervide +Mervidelo +Mervidelon +Mervidelot +Michal +Michar +Michard +Michardo +Michart +Michartwig +Miche +Micher +Migue +Miguein +Migueinall +Migueiner +Migueinri +Miguenel +Migueneusz +Miguet +Mikołady +Mikołan +Mikoław +Mittain +Mittainte +Mittainulf +Miłomir +Moham +Mustaf +Mustan +Mustanc +Mustancis +Mustanck +Mustand +Mustantiau +Mustantin +Mustanto +Mustanty +Mustavo +Mustavold +Mustaw +Mustazy +Mustazyli +Narcik +Narcin +Natan +Natancjusz +Natand +Nataniec +Natanien +Natanieu +Natanis +Natanty +Natantyn +Nesto +Nestof +Nestophe +Nestopher +Nestophere +Nestout +Niedhar +Niedhard +Niedhardt +Niedharian +Niedharles +Néapold +Néapolio +Néapolito +Néapollot +Néapolon +Névelest +Névelesto +Oktaw +Olgier +Olivis +Ondar +Ondard +Order +Orderand +Orderanz +Orderon +Orders +Orderyk +Orlan +Orric +Orrice +Orrich +Orricio +Orricion +Ortol +Ortold +Ortolt +Oswalbert +Oswalibor +Oswalibóg +Oswalise +Owidier +Owidierd +Owidierre +Owidierz +Owidius +Pabias +Pabiasz +Parcik +Parciso +Paris +Parist +Paristafa +Paristan +Paristanto +Paristian +Paristiano +Paristin +Paristo +Paristophe +Paristor +Paristout +Patrich +Patrichael +Patrichar +Patrichał +Patriche +Patrichel +Patrichelm +Petronim +Petronio +Petrou +Petruchard +Petruchiel +Philian +Philiano +Philianus +Philio +Philius +Philiusz +Pierran +Pierranc +Pierrancin +Pierrand +Pierranz +Pierric +Pierrice +Pierrich +Pierrick +Pierry +Plati +Platias +Platiasz +Platier +Platierry +Platierz +Polikar +Polikary +Poliks +Porchar +Porchard +Porchardt +Porcharles +Porchart +Porchibal +Porchibald +Porchieu +Poufier +Poufierond +Poufierre +Poufierry +Poufierz +Poufillaus +Poufillot +Poufin +Prician +Pricianus +Pricio +Quabian +Quabiano +Quabien +Quabienne +Quenancis +Quenanck +Quenard +Quenardo +Quenart +Quenartain +Quent +Quenther +Quentian +Quentiau +Quentien +Quentule +Quenty +Radosłan +Rague +Ragueinald +Ragueiner +Ragueneban +Raher +Rahew +Rahiel +Rahieu +Rainfrem +Rainfres +Rainus +Raphane +Raphanelo +Raphanelon +Ratien +Ratienne +Raymon +Raymotelin +Raymotelix +Raymoteus +Raymoteusz +Redward +Redwardin +Redwardo +Redwardt +Reinald +Reinard +Reinardo +Reine +Reinebor +Reinhard +Reinhardt +Remigar +Remigard +Remigart +Renon +Renouet +Renout +Ribal +Ricard +Richael +Richal +Richar +Richaric +Richart +Richaël +Ringeaumin +Ringeaumir +Ringeaux +Ringel +Ringelard +Ringelardo +Ringelbert +Ringelm +Ringelmar +Ringelmuth +Ringeno +Ringenon +Ringenot +Ringenoît +Rionel +Robercin +Robero +Rocel +Roceli +Rocelo +Roder +Roderancis +Roderic +Roderich +Rodericio +Roderico +Roders +Rodolf +Rogel +Rogelary +Rogelbert +Rogelberto +Rogelmas +Rolan +Ronall +Roncis +Rosceliks +Roscelion +Roscelipe +Rosse +Rossel +Rossell +Rostaf +Rostafa +Rostan +Rostanto +Rostaw +Rostazy +Rosław +Rotroni +Rotronim +Rotronin +Rotronius +Rotroniusz +Rotronizy +Roucas +Rudolph +Ruper +Russe +Russel +Russer +Ryszan +Saille +Saillefer +Saillestyn +Saillet +Saillian +Sailliaume +Saint +Sainto +Salvain +Salvainne +Salvainnes +Salvainnet +Salvainte +Salvainter +Sandelian +Sandelin +Sandelion +Sandre +Sandreas +Sandreskin +Santian +Santiano +Santianus +Santien +Santiennet +Santier +Sebastien +Sebastin +Sebastine +Sebastiner +Sebasz +Sehiel +Sehieu +Seifrie +Seifriel +Seifrien +Seifriet +Seneboud +Sergilio +Sergilip +Sergilippe +Sergius +Sernabé +Sernaldo +Sernando +Sernandro +Sernard +Sernaude +Serwazy +Serwazyli +Siegfric +Siegfrid +Sieghar +Siegharles +Siemond +Sigis +Sigmunt +Silvest +Silvester +Silvestero +Silvesto +Silvestor +Silvestyn +Sirio +Sobien +Sobiesłaj +Sobiesłan +Sofroi +Sofronim +Sofronin +Sofronio +Sofronius +Sofronizy +Sohal +Sohiel +Sohieu +Sonne +Sonnes +Soolebor +Sooleboud +Sooleslav +Soolesław +Staniel +Stanieu +Stanilo +Stanio +Stoignien +Stois +Strzego +Strzej +Stéphael +Stéphaël +Sulibrond +Sulibroży +Sulibóg +Sulis +Sulise +Syhiel +Syhieu +Sylvador +Sylvest +Sylvesto +Sylvestor +Sylvestre +Sylwest +Sylwesto +Sylwestor +Sylwestre +Sylwestyn +Syrius +Szymoteli +Szymotelin +Szymotelio +Szymoteus +Szymoteusz +Sébastazy +Sébastian +Sébastias +Sébastier +Sébastor +Sędomiej +Sław +Tadeus +Taillard +Taillargen +Taillaus +Taille +Taillemeno +Taillement +Tailler +Taillery +Tailles +Taillet +Taillian +Taillibor +Taillot +Tassamy +Teles +Teleslaus +Teleslav +Telestyn +Telesłan +Telesław +Teobard +Teobardzik +Teodolph +Teodosłaj +Teodosław +Tevenel +Tevenelon +Theodor +Theodrian +Theodrien +Thier +Thierd +Thierond +Thierre +Thierz +Thomain +Thomainnes +Thomainulf +Thoman +Thoucaud +Thoucaut +Thouchal +Thouchar +Thouchard +Thouchardt +Tiessamy +Tiesse +Tobian +Tomas +Tomisłan +Trist +Triste +Tristiago +Tristian +Tristias +Tristof +Tristophe +Tristopher +Tristophet +Turbert +Turbertram +Tęgomiej +Tęgominik +Ulric +Ulrice +Ulricio +Ulrick +Ulrico +Ursmas +Valen +Valentiago +Valentian +Valentien +Valentier +Valentierz +Valenty +Valentyn +Vanne +Vannet +Walard +Walart +Waldebras +Waldemach +Waldemund +Walderan +Walderiete +Walentin +Waler +Waleriasz +Walermo +Walerot +Wandech +Wandechier +Wandecht +Wandeli +Wandelian +Wandeliks +Wander +Wanders +Wandersz +Warnel +Warnest +Warnester +Warnesto +Warnestor +Warnestre +Warnestyn +Wawrzyniel +Wawrzynien +Wawrzynier +Wawrzynieu +Wawrzyniew +Wenan +Wenando +Wendecht +Wender +Wenders +Wendersz +Wernel +Wernester +Wernesto +Wernestor +Wespan +Wespar +Wibal +Wieli +Wieliks +Wielin +Wielind +Wielino +Wielinus +Wielio +Wieliodor +Wielion +Wielipe +Wiesłaj +Wilfram +Wilfred +Wilfric +Wilfrich +Wilfriel +Willibron +Willivelet +Wincent +Wincenther +Wincenthew +Wincentian +Wincentias +Wincentien +Wincentier +Wincentin +Wincentule +Wirgilian +Wirgiliano +Wirgilio +Wirgilip +Wirgilipe +Wirgilius +Wirgius +Wirgiusz +Wojciec +Wrociemir +Wrocisłan +Włodzik +Włodzimir +Ydeveno +Ydevenon +Ydevenonys +Ydevenost +Ydevenot +Ydevenouf +Ydevenoît +Zachael +Zachal +Zachar +Zachard +Zachardo +Zacharias +Zacharie +Zacharin +Zachart +Zachartain +Zachaël +Zachał +Zbigne +Zbignen +Zbigniec +Zbigniel +Zbignien +Zbignier +Zbignieu +Zdzisłan +Zenobius +Ziemond +Zwiniau +Zwiniaume +Zwiniaumel +Zwinik +Zwinique +Zygfrie +Zygfried +Zygfriel +Zygfriet +Zygmund +Zygmundulf diff --git a/gods.txt b/gods.txt new file mode 100644 index 000000000..43edb241b --- /dev/null +++ b/gods.txt @@ -0,0 +1,411 @@ +Adeka +Adekaga +Adekagagwa +Adekagaš +Adekavac +Aires +Airesna +Airesnik +Amot +Angwa +Angwaa +Angwusna +Angwuti +Apistotook +Asdzą +Ata Killa +Ata Killaa +Bagi +Bagiena +Bagienniyo +Bagiewit +Bagiewitha +Bagiewona +Baldak +Banna +Banniyo +Bast +Berstus +Buri +Cachi +Cachimines +Canook +Canooki +Canookii +Canoy +Catel +Cavilla +Cavillaa +Cavillapa +Cereplut +Chac +Conia +Coniaš +Coniašši +Coniaššu +Conir +Conirawa +Conirawals +Copaca +Copacari +Copacarilo +Coylla +Coyllaa +Coyllaca +Coyllapa +Coyllur +Coyllus +Coylluskab +Coylluskam +Della +Dellaa +Dellaca +Dellacati +Dellapa +Delli +Dellini +Delliniez +Dellinilii +Dellins +Dellur +Dellus +Delluskab +Delluskam +Dreka +Drekaga +Drekagagwa +Drekagaš +Dunia +Duzaga +Duzagagwa +Duzagagwaa +Dziewit +Dziewitha +Epuna +Epunamulla +Epunatel +Epunatha +Epunathor +Feronir +Feronirawa +Feroniraya +Fling +Flinga +Flingast +Flingwaa +Flingwusna +Flingwuti +Flini +Fliniez +Flinilii +Fliniling +Flinilinga +Flinilini +Flinilins +Fors +Friglav +Gendenwit +Germes +Germóðr +Gitch +Gitchei +Glus +Gluskam +Gohona +Gohonapa +Guaca +Guacari +Guacarilo +Guacati +Guachi +Guama +Guamac +Guaman +Guamanus +Hagone +Hashchʼí +Hatha +Hawennik +Hephaesta +Hephaestia +Hephaestuk +Hephthys +Herman +Hermansuri +Hermanus +Hesta +Hestuk +Hestus +Hora +Horakhty +Horana +Horanaco +Horanai +Horanaku +Horanakuru +Horun +Horunda +Horundaš +Huachi +Hózhǫ́ +Igalus +Igaluskab +Igaluskam +Illa +Illaa +Illaca +Illacari +Illacarilo +Illacati +Inda +Iosh +Jari +Jaricia +Jóhona +Jóhonapa +Jóhone +Jóhones +Jör +Kamun +Kamuna +Kamunamun +Kamunamuna +Kamunatel +Kamunatha +Kamunathor +Kares +Karesekui +Karesna +Karesnik +Karet +Katoyllur +Katoyllus +Kaš +Kašši +Kella +Kellaca +Kellacari +Kellacati +Kellapa +Kelli +Kelling +Kellingast +Kellini +Kelliniez +Kellinilii +Kellins +Kellur +Kellus +Kelluskab +Kelluskam +Killa +Killaa +Killaca +Killacari +Killacati +Killapa +Kokope +Kokopella +Kokopellur +Kokopellus +Kokosh +Kokosheka +Koli +Kolia +Koyana +Koyanaco +Koyanai +Koyanaku +Koyanakuru +Koyangwa +Koyangwaa +Koyanook +Koyanooki +Koyanookii +Koyanopus +Koyanoy +Kres +Kresekui +Kresna +Kresnasoak +Kupay +Laret +Larewit +Larun +Laruttaš +Leshat +Magn +Mals +Manitou +Manitra +Manitsa +Marog +Marun +Marunda +Marundaš +Marzyangwa +Marzyanoy +Mirinus +Miris +Mokope +Mokopella +Mokopellaa +Mokopelli +Mokopellur +Mokopellus +Mora +Morakhty +Muying +Muyinga +Muyingast +Muyingwuti +Nana +Nanaco +Nanaku +Nanakuru +Nanki +Nannik +Nanniyo +Nanopus +Nanoy +Neini +Neiniez +Neinilii +Neinilinga +Neinilini +Neinilins +Nephaesta +Nephaestia +Nephaestus +Neptis +Niskab +Njöfn +Njör +Njörð +Nádleehé +Olorus +Onatel +Onatequil +Onathor +Oołti'í +Orcuna +Orcunamun +Orcunamuna +Orcunatel +Orcunatha +Orcunathor +Osir +Osiriaš +Osirinus +Oźwie +Oźwiennik +Paca +Pacati +Pari +Parilo +Percuna +Percunamun +Percus +Peres +Peresekui +Peresnik +Piguer +Ping +Pingwa +Pingwaa +Pingwusna +Pingwuti +Poda +Poluno +Quil +Quiriaš +Quiris +Rugiena +Rugiennik +Rugienniyo +Rugiewona +Saluk +Seshy +Shaku +Silii +Siling +Silinga +Silingast +Silingwaa +Silingwuti +Silini +Silinilii +Siliniling +Silinilini +Silins +Sjör +Sjörð +Sjörðr +Summa +Summan +Summansuri +Supala +Svarowit +Svarowitha +Svetovis +Tabaldr +Tawa +Tawares +Tawaresna +Tawarewit +Taš +Taššu +Tella +Tellaa +Tellaca +Tellacati +Tellapa +Telli +Telling +Tellinga +Tellingast +Tellini +Telliniez +Tellinilii +Tellins +Tellur +Thona +Thonaaʼí +Thone +Thones +Tiraqutra +Tiraya +Tiwana +Tiwanaco +Tiwanai +Topieles +Tornga +Torngast +Torngastet +Trigg +Tó Neini +Urpihuana +Urpihuanai +Vediovid +Velec +Vesnik +Vestia +Vestuk +Vestus +Vodyana +Vodyanaco +Vodyanai +Vodyangwa +Vodyangwaa +Vodyanook +Vodyanooki +Vodyanopus +Vortum +Wirawa +Wirawals +Wirawares +Wirawaret +Wiraya +Zirnitou +Zirnitsa +Zorya diff --git a/inventory.cpp b/inventory.cpp new file mode 100644 index 000000000..4ffc4a2af --- /dev/null +++ b/inventory.cpp @@ -0,0 +1,67 @@ +#include "stdafx.h" + +using namespace std; + +void Inventory::addItem(PItem item) { + itemsCache.push_back(item.get()); + items.push_back(move(item)); +} + +void Inventory::addItems(vector v) { + for (PItem& it : v) + addItem(std::move(it)); +} + +PItem Inventory::removeItem(Item* itemRef) { + int ind = -1; + for (int i : All(items)) + if (items[i].get() == itemRef) { + ind = i; + break; + } + CHECK(ind > -1) << "Tried to remove unknown item."; + PItem item = std::move(items[ind]); + items.erase(items.begin() + ind); + removeElement(itemsCache, itemRef); + return item; +} + +vector Inventory::removeItems(vector items) { + vector ret; + for (Item* item : items) + ret.push_back(removeItem(item)); + return ret; +} + +vector Inventory::removeAllItems() { + itemsCache.clear(); + return move(items); +} + +vector Inventory::getItems(function predicate) const { + vector ret; + for (const PItem& item : items) + if (predicate(item.get())) + ret.push_back(item.get()); + return ret; +} + +vector Inventory::getItems() const { + return itemsCache; +} + +bool Inventory::hasItem(const Item* itemRef) const { + for (const PItem& item : items) + if (item.get() == itemRef) + return true; + return false; +} + +int Inventory::size() const { + return items.size(); +} + +bool Inventory::isEmpty() const { + return items.empty(); +} + diff --git a/inventory.h b/inventory.h new file mode 100644 index 000000000..54cbef668 --- /dev/null +++ b/inventory.h @@ -0,0 +1,30 @@ +#ifndef _INVENTORY_H +#define _INVENTORY_H + +#include + +#include "util.h" +#include "item.h" + +class Inventory { + public: + void addItem(PItem); + void addItems(vector); + PItem removeItem(Item* item); + vector removeItems(vector items); + vector removeAllItems(); + + vector getItems() const; + vector getItems(function predicate) const; + + bool hasItem(const Item*) const; + int size() const; + + bool isEmpty() const; + + private: + vector items; + vector itemsCache; +}; + +#endif diff --git a/item.cpp b/item.cpp new file mode 100644 index 000000000..a1997d2fa --- /dev/null +++ b/item.cpp @@ -0,0 +1,392 @@ +#include "stdafx.h" + +using namespace std; + +Item::Item(ViewObject o, const ItemAttributes& attr) + : ItemAttributes(attr), viewObject(o), inspected(everythingIdentified), fire(*weight, flamability) { +} + +void Item::identifyEverything() { + everythingIdentified = true; +} + +bool Item::isEverythingIdentified() { + return everythingIdentified; +} + +bool Item::everythingIdentified = false; + +static set ident; +bool Item::isIdentified(const string& name) { + return everythingIdentified || ident.count(name); +} + +ItemPredicate Item::effectPredicate(EffectType type) { + return [type](Item* item) { return item->getEffectType() == type; }; +} + +ItemPredicate Item::typePredicate(ItemType type) { + return [type](Item* item) { return item->getType() == type; }; +} + +ItemPredicate Item::typePredicate(vector type) { + return [&type](Item* item) { return contains(type, item->getType()); }; +} + +map> Item::stackItems(vector items) { + map> stacks = groupBy(items, [](const Item* item) { + return item->getAName(); + }); + map> ret; + for (auto elem : stacks) + if (elem.second.size() > 1) + ret.insert(make_pair( + convertToString(elem.second.size()) + " " + elem.second[0]->getAName(true), elem.second)); + else + ret.insert(elem); + return ret; +} + +void Item::identify(const string& name) { + Debug() << "Identify " << name; + ident.insert(name); +} + +void Item::identify() { + identify(*name); + inspected = true; +} + +bool Item::canIdentify() { + return identifiable; +} + +bool Item::isIdentified() { + return isIdentified(*name); +} + +void Item::onEquip(Creature* c) { + if (identifyOnEquip && c->isPlayer()) + identify(); + onEquipSpecial(c); +} + +void Item::onUnequip(Creature* c) { + onUnequipSpecial(c); +} + +void Item::setOnFire(double amount, const Level* level, Vec2 position) { + bool burning = fire.isBurning(); + string noBurningName = getTheName(); + fire.set(amount); + if (!burning && fire.isBurning()) { + level->globalMessage(position, noBurningName + " catches fire."); + viewObject.setBurning(fire.getSize()); + } +} + +double Item::getFireSize() const { + return fire.getSize(); +} + +void Item::tick(double time, Level* level, Vec2 position) { + if (fire.isBurning()) { + Debug() << getName() << " burning " << fire.getSize(); + level->getSquare(position)->setOnFire(fire.getSize()); + viewObject.setBurning(fire.getSize()); + fire.tick(level, position); + if (!fire.isBurning()) { + level->globalMessage(position, getTheName() + " burns out"); + discarded = true; + } + } + specialTick(time, level, position); +} + +void Item::onHitSquare(Vec2 position, Square* s) { + if (fragile) { + s->getConstLevel()->globalMessage(position, + getTheName() + " crashes on the " + s->getName(), "You hear a crash"); + discarded = true; + } else + s->getConstLevel()->globalMessage(position, getTheName() + " hits the " + s->getName()); +} + +void Item::onHitCreature(Creature* c, Attack attack) { + if (fragile) { + c->you(MsgType::ITEM_CRASHES, getTheName()); + discarded = true; + } else + c->you(MsgType::HIT_THROWN_ITEM, getTheName()); + if (c->takeDamage(attack)) + return; + if (effect && getType() == ItemType::POTION) { + Effect::getEffect(*effect)->applyToCreature(c, EffectStrength::NORMAL); + if (c->getLevel()->playerCanSee(c->getPosition())) + identify(); + } +} + +double Item::getApplyTime() const { + return applyTime; +} + +double Item::getWeight() const { + return *weight; +} + +string Item::getDescription() const { + return description; +} + +const ViewObject& Item::getViewObject() const { + return viewObject; +} + +string Item::getText(ItemType type) { + map m { + { ItemType::WEAPON,"Weapons" }, + { ItemType::RANGED_WEAPON,"Ranged weapons" }, + { ItemType::AMMO,"Projectiles" }, + { ItemType::AMULET,"Amulets" }, + { ItemType::ARMOR,"Armor" }, + { ItemType::SCROLL,"Scrolls" }, + { ItemType::POTION,"Potions" }, + { ItemType::FOOD,"Comestibles" }, + { ItemType::BOOK,"Books" }, + { ItemType::TOOL,"Tools" }, + { ItemType::CORPSE,"Corpses" }, + { ItemType::OTHER,"Other" }, + { ItemType::GOLD,"Gold" }}; + TRY(return m.at(type), "Unknown type"); +} + +ItemType Item::getType() const { + return *type; +} + +int Item::getPrice() const { + return price; +} + +void Item::setUnpaid(bool p) { + unpaid = p; +} + +bool Item::isUnpaid() const { + return unpaid; +} + +Optional Item::getTrapType() const { + return trapType; +} + +void Item::apply(Creature* c, Level* l) { + if (identifyOnApply && l->playerCanSee(c->getPosition())) + identify(*name); + if (effect) + Effect::getEffect(*effect)->applyToCreature(c, EffectStrength::NORMAL); + if (uses && (-- *uses) == 0) { + discarded = true; + if (usedUpMsg) + c->privateMessage(getTheName() + " is used up."); + } +} + +string Item::getApplyMsgThirdPerson() const { + switch (getType()) { + case ItemType::SCROLL: return "reads " + getAName(); + case ItemType::POTION: return "drinks " + getAName(); + case ItemType::BOOK: return "reads " + getAName(); + case ItemType::TOOL: return "applies " + getAName(); + case ItemType::FOOD: return "eats " + getAName(); + default: Debug(FATAL) << "Bad type for applying " << (int)getType(); + } + return ""; +} + +string Item::getApplyMsgFirstPerson() const { + switch (getType()) { + case ItemType::SCROLL: return "read " + getAName(); + case ItemType::POTION: return "drink " + getAName(); + case ItemType::BOOK: return "read " + getAName(); + case ItemType::TOOL: return "apply " + getAName(); + case ItemType::FOOD: return "eat " + getAName(); + default: Debug(FATAL) << "Bad type for applying " << (int)getType(); + } + return ""; +} + +string Item::getNoSeeApplyMsg() const { + switch (getType()) { + case ItemType::SCROLL: return "You hear someone reading"; + case ItemType::POTION: return ""; + case ItemType::BOOK: return "You hear someone reading "; + case ItemType::TOOL: return ""; + case ItemType::FOOD: return ""; + default: Debug(FATAL) << "Bad type for applying " << (int)getType(); + } + return ""; +} + +void Item::setName(const string& n) { + name = n; +} + +string Item::getName(bool plural, bool blind) const { + string suff = uses && displayUses && inspected ? string(" (") + convertToString(*uses) + ")" : ""; + if (fire.isBurning()) + suff.append(" (burning)"); + if (unpaid) + suff += " (" + convertToString(getPrice()) + (plural ? " zorkmids each)" : " zorkmids)"); + if (blind) + return getBlindName(plural); + if (isIdentified(*name)) + return getRealName(plural) + suff; + else + return getVisibleName(plural) + suff; +} + +string Item::getAName(bool getPlural, bool blind) const { + if (noArticle || getPlural) + return getName(getPlural, blind); + else + return addAParticle(getName(getPlural, blind)); +} + +string Item::getTheName(bool getPlural, bool blind) const { + string the = (noArticle || getPlural) ? "" : "the "; + return the + getName(getPlural, blind); +} + +string Item::getVisibleName(bool getPlural) const { + if (!getPlural) + return *name; + else { + if (plural) + return *plural; + else + return *name + "s"; + } +} + +static string withSign(int a) { + if (a >= 0) + return "+" + convertToString(a); + else + return convertToString(a); +} + +string Item::getArtifactName() const { + CHECK(artifactName); + return *artifactName; +} + +string Item::getNameAndModifiers(bool getPlural, bool blind) const { + if (inspected) { + string artStr = artifactName ? " named " + *artifactName : ""; + if (getType() == ItemType::WEAPON) + return getName(getPlural, blind) + artStr + + " (" + withSign(toHit) + " accuracy, " + withSign(damage) + " damage)"; + if (getType() == ItemType::RANGED_WEAPON) + return getName(getPlural, blind) + artStr + + " (" + withSign(rangedWeaponAccuracy) + " accuracy)"; + if (getType() == ItemType::ARMOR) + return getName(getPlural, blind) + artStr + " (" + withSign(defense) + " defense)"; + } + return getName(getPlural, blind); +} + +string Item::getRealName(bool getPlural) const { + if (!realName) + return getVisibleName(getPlural); + if (!getPlural) + return *realName; + else { + if (realPlural) + return *realPlural; + else + return *realName + "s"; + } +} + +string Item::getBlindName(bool plural) const { + if (blindName) + return *blindName + (plural ? "s" : ""); + else + return getName(plural, false); +} + +bool Item::isDiscarded() { + return discarded; +} + +Optional Item::getEffectType() const { + return effect; +} + +bool Item::canEquip() const { + return getType() == ItemType::WEAPON + || getType() == ItemType::RANGED_WEAPON + || getType() == ItemType::ARMOR + || getType() == ItemType::AMULET; +} + +EquipmentSlot Item::getEquipmentSlot() const { + CHECK(canEquip()); + if (getType() == ItemType::WEAPON) + return EquipmentSlot::WEAPON; + if (getType() == ItemType::RANGED_WEAPON) + return EquipmentSlot::RANGED_WEAPON; + if (getType() == ItemType::AMULET) + return EquipmentSlot::AMULET; + if (armorType == ArmorType::BODY_ARMOR) + return EquipmentSlot::BODY_ARMOR; + if (armorType == ArmorType::HELMET) + return EquipmentSlot::HELMET; + Debug(FATAL) << "other equipment slot"; + return EquipmentSlot::HELMET; +} + +int Item::getAccuracy() const { + return rangedWeaponAccuracy; +} + +void Item::addModifier(AttrType attributeType, int value) { + switch (attributeType) { + case AttrType::TO_HIT: toHit += value; break; + case AttrType::THROWN_TO_HIT: thrownToHit += value; break; + case AttrType::DEFENSE: defense += value; break; + case AttrType::DAMAGE: damage += value; break; + case AttrType::THROWN_DAMAGE: thrownDamage += value; break; + case AttrType::STRENGTH: strength += value; break; + case AttrType::DEXTERITY: dexterity += value; break; + case AttrType::SPEED: speed += value; break; + case AttrType::INV_LIMIT: break; + default: Debug(FATAL) << "Attribute not handled"; + } +} + +int Item::getModifier(AttrType attributeType) const { + switch (attributeType) { + case AttrType::TO_HIT: return toHit; + case AttrType::THROWN_TO_HIT: return thrownToHit; + case AttrType::DEFENSE: return defense; + case AttrType::DAMAGE: return damage; + case AttrType::THROWN_DAMAGE: return thrownDamage; + case AttrType::STRENGTH: return strength; + case AttrType::DEXTERITY: return dexterity; + case AttrType::SPEED: return speed; + case AttrType::INV_LIMIT: return 0; + default: Debug(FATAL) << "Attribute not handled"; + } + return 0; +} + +AttackType Item::getAttackType() const { + return attackType; +} + +bool Item::isWieldedTwoHanded() const { + return twoHanded; +} diff --git a/item.h b/item.h new file mode 100644 index 000000000..a26e8d4f6 --- /dev/null +++ b/item.h @@ -0,0 +1,104 @@ +#ifndef _ITEM_H +#define _ITEM_H + +#include "util.h" +#include "view_object.h" +#include "item_attributes.h" +#include "effect.h" +#include "enums.h" +#include "attack.h" +#include "fire.h" + +class Level; + +class Item; +typedef function ItemPredicate; + +class Item : private ItemAttributes { + public: + typedef ItemAttributes ItemAttributes; + Item(ViewObject o, const ItemAttributes&); + virtual ~Item() {} + + static void identifyEverything(); + static bool isEverythingIdentified(); + + virtual void apply(Creature*, Level*); + + bool isDiscarded(); + + const ViewObject& getViewObject() const; + + string getName(bool plural = false, bool blind = false) const; + string getTheName(bool plural = false, bool blind = false) const; + string getAName(bool plural = false, bool blind = false) const; + string getNameAndModifiers(bool plural = false, bool blind = false) const; + string getArtifactName() const; + + virtual Optional getEffectType() const; + ItemType getType() const; + + int getPrice() const; + void setUnpaid(bool); + bool isUnpaid() const; + + Optional getTrapType() const; + + bool canEquip() const; + EquipmentSlot getEquipmentSlot() const; + static string getText(ItemType type); + void addModifier(AttrType attributeType, int value); + int getModifier(AttrType attributeType) const; + + void tick(double time, Level*, Vec2 position); + + string getApplyMsgThirdPerson() const; + string getApplyMsgFirstPerson() const; + string getNoSeeApplyMsg() const; + + static void identify(const string& name); + void identify(); + bool isIdentified(); + bool canIdentify(); + void onEquip(Creature*); + void onUnequip(Creature*); + virtual void onEquipSpecial(Creature*) {} + virtual void onUnequipSpecial(Creature*) {} + virtual void setOnFire(double amount, const Level* level, Vec2 position); + double getFireSize() const; + + void onHitSquare(Vec2 position, Square*); + void onHitCreature(Creature* c, Attack attack); + + int getAccuracy() const; + double getApplyTime() const; + double getWeight() const; + string getDescription() const; + + AttackType getAttackType() const; + bool isWieldedTwoHanded() const; + + static ItemPredicate effectPredicate(EffectType); + static ItemPredicate typePredicate(ItemType); + static ItemPredicate typePredicate(vector); + + static map> stackItems(vector); + + protected: + virtual void specialTick(double time, Level*, Vec2 position) {} + void setName(const string& name); + ViewObject viewObject; + bool discarded = false; + bool inspected; + static bool everythingIdentified; + + private: + static bool isIdentified(const string& name); + string getVisibleName(bool plural) const; + string getRealName(bool plural) const; + string getBlindName(bool plural) const; + bool unpaid = false; + Fire fire; +}; + +#endif diff --git a/item_attributes.h b/item_attributes.h new file mode 100644 index 000000000..93c86e366 --- /dev/null +++ b/item_attributes.h @@ -0,0 +1,59 @@ +#ifndef _ITEM_ATTRIBUTES_H +#define _ITEM_ATTRIBUTES_H + +#include +#include + +#include "util.h" +#include "effect.h" +#include "enums.h" + +#define ITATTR(X) ItemAttributes([&](ItemAttributes& i) { X }) + +class EnemyCheck; + +class ItemAttributes { + public: + ItemAttributes(function fun) { + fun(*this); + } + + MustInitialize name; + string description; + MustInitialize weight; + MustInitialize type; + Optional realName; + Optional realPlural; + Optional plural; + Optional blindName; + Optional firingWeapon; + Optional artifactName; + Optional trapType; + double flamability = 0; + int price = 0; + bool noArticle = false; + int damage = 0; + int toHit = 0; + int thrownDamage = 0; + int thrownToHit = 0; + int rangedWeaponAccuracy = 0; + int defense = 0; + int strength = 0; + int dexterity = 0; + int speed = 0; + bool twoHanded = false; + AttackType attackType = AttackType::HIT; + double attackTime = 1; + Optional armorType; + double applyTime = 1; + bool fragile = false; + Optional effect; + Optional uses; + bool usedUpMsg = false; + bool displayUses = false; + bool identifyOnApply = true; + bool identifiable = false; + bool identifyOnEquip = true; +}; + +#endif diff --git a/item_factory.cpp b/item_factory.cpp new file mode 100644 index 000000000..d143b2640 --- /dev/null +++ b/item_factory.cpp @@ -0,0 +1,771 @@ +#include "stdafx.h" + +using namespace std; + +class FireScroll : public Item { + public: + FireScroll(const ViewObject& obj, const ItemAttributes& attr) : Item(obj, attr) {} + + virtual void apply(Creature* c, Level* l) override { + if (l->playerCanSee(c->getPosition())) + identify(); + set = true; + } + + virtual void specialTick(double time, Level* level, Vec2 position) override { + if (set) { + setOnFire(0.03, level, position); + set = false; + } + } + + private: + bool set = false; +}; + +class AmuletOfWarning : public Item { + public: + AmuletOfWarning(const ViewObject& obj, const ItemAttributes& attr, int r) : Item(obj, attr), radius(r) {} + + virtual void specialTick(double time, Level* level, Vec2 position) override { + Creature* owner = level->getSquare(position)->getCreature(); + if (owner && owner->getEquipment().isEquiped(this)) { + Rectangle rect = Rectangle(position.x - radius, position.y - radius, + position.x + radius + 1, position.y + radius + 1); + bool isDanger = false; + bool isBigDanger = false; + for (Vec2 v : rect) { + if (!level->inBounds(v)) + continue; + if (Creature* c = level->getSquare(v)->getCreature()) { + if (!owner->canSee(c) && c->isEnemy(owner)) { + int diff = c->getAttr(AttrType::DAMAGE) - owner->getAttr(AttrType::DAMAGE); + if (diff > 5) + isBigDanger = true; + else + if (diff > 0) + isDanger = true; + } + } + } + if (isBigDanger) + owner->privateMessage(getTheName() + " vibrates vigorously"); + else + if (isDanger) + owner->privateMessage(getTheName() + " vibrates"); + } + } + + private: + int radius; +}; + +class AmuletOfHealing : public Item { + public: + AmuletOfHealing(const ViewObject& obj, const ItemAttributes& attr) : Item(obj, attr) {} + + virtual void specialTick(double time, Level* level, Vec2 position) override { + Creature* owner = level->getSquare(position)->getCreature(); + if (owner && owner->getEquipment().isEquiped(this)) { + if (lastTick == -1) + lastTick = time; + else { + owner->heal((time - lastTick) / 20); + } + lastTick = time; + } else + lastTick = -1; + } + + private: + double lastTick = -1; +}; + +class AmuletOfEnemyCheck : public Item { + public: + AmuletOfEnemyCheck(const ViewObject& obj, const ItemAttributes& attr, EnemyCheck* c) : Item(obj, attr), check(c) {} + + virtual void onEquipSpecial(Creature* c) { + c->addEnemyCheck(check); + } + + virtual void onUnequipSpecial(Creature* c) { + c->removeEnemyCheck(check); + } + + private: + EnemyCheck* check; +}; + +class Corpse : public Item { + public: + Corpse(const ViewObject& obj, const ViewObject& obj2, const ItemAttributes& attr, const string& rottenN, double rottingT) : + Item(obj, attr), + object2(obj2), + rottingTime(rottingT), + rottenName(rottenN) { + } + + virtual void specialTick(double time, Level* level, Vec2 position) override { + if (rottenTime == -1) + rottenTime = time + rottingTime; + if (time >= rottenTime && !rotten) { + setName(rottenName); + viewObject = object2; + rotten = true; + } else if (getWeight() > 10 && !rotten && !level->getSquare(position)->isCovered() && Random.roll(35)) { + for (Vec2 v : position.neighbors8(true)) + if (level->inBounds(v)) { + PCreature vulture = CreatureFactory::fromId( + CreatureId::VULTURE, Tribe::pest, MonsterAIFactory::scavengerBird(v)); + if (level->getSquare(v)->canEnter(vulture.get())) { + level->addCreature(v, std::move(vulture)); + level->globalMessage(v, "A vulture lands near " + getTheName()); + rottenTime -= 40; + break; + } + } + } + } + + private: + ViewObject object2; + bool rotten = false; + double rottenTime = -1; + double rottingTime; + string rottenName; +}; + +PItem ItemFactory::corpse(CreatureId id, ItemType type) { + PCreature c = CreatureFactory::fromId(id, Tribe::monster); + return corpse(c->getName() + " corpse", c->getName() + " skeleton", c->getWeight()); +} + +PItem ItemFactory::corpse(const string& name, const string& rottenName, double weight, ItemType type) { + const double rotTime = 300; + return PItem(new Corpse( + ViewObject(ViewId::BODY_PART, ViewLayer::ITEM, name), + ViewObject(ViewId::BONE, ViewLayer::ITEM, rottenName), + ITATTR( + i.name = name; + i.type = type; + i.weight = weight;), + rottenName, + rotTime)); +} + +class Potion : public Item { + public: + Potion(ViewObject object, const ItemAttributes& attr) : Item(object, attr) {} + + virtual void setOnFire(double amount, const Level* level, Vec2 position) override { + heat += amount; + Debug() << getName() << " heat " << heat; + if (heat > 0.1) { + level->globalMessage(position, getAName() + " boils and explodes!"); + discarded = true; + } + } + + virtual void specialTick(double time, Level* level, Vec2 position) override { + heat = max(0., heat - 0.005); + } + + private: + double heat = 0; +}; + +class SkillBook : public Item { + public: + SkillBook(ViewObject object, const ItemAttributes& attr, Skill* s) : Item(object, attr), skill(s) {} + + virtual void apply(Creature* c, Level* l) override { + c->addSkill(skill); + } + + private: + Skill* skill; +}; + +class TrapItem : public Item { + public: + TrapItem(ViewObject object, ViewObject _trapObject, const ItemAttributes& attr, PEffect _effect) + : Item(object, attr), effect(std::move(_effect)), trapObject(_trapObject) { + } + + virtual void apply(Creature* c, Level* l) override { + CHECK(effect); + c->you(MsgType::SET_UP_TRAP, ""); + c->getSquare()->addTrigger(Trigger::getTrap(trapObject, + l, c->getPosition(), std::move(effect), c->getTribe())); + discarded = true; + } + + private: + PEffect effect; + ViewObject trapObject; +}; + +static vector amulet_looks = { "steel", "copper", "crystal", "wooden", "amber"}; +static vector amulet_ids = { ViewId::STEEL_AMULET, ViewId::COPPER_AMULET, ViewId::CRYSTAL_AMULET, ViewId::WOODEN_AMULET, ViewId::AMBER_AMULET}; + +static vector mushroom_looks = { "slimy", "pink", "dotted", "glowing", "green", "black"}; +static vector mushroom_ids = {ViewId::PINK_MUSHROOM, ViewId::DOTTED_MUSHROOM, ViewId::GLOWING_MUSHROOM, ViewId::GREEN_MUSHROOM, ViewId::BLACK_MUSHROOM, ViewId::SLIMY_MUSHROOM}; + +static vector potion_looks = { "effervescent", "murky", "swirly", "violet", "puce", "smoky", "fizzy", "milky" }; +static vector potion_ids = { ViewId::EFFERVESCENT_POTION, ViewId::MURKY_POTION, ViewId::SWIRLY_POTION, ViewId::VIOLET_POTION, ViewId::PUCE_POTION, ViewId::SMOKY_POTION, ViewId::FIZZY_POTION, ViewId::MILKY_POTION}; +static vector scroll_looks; + +ItemFactory::ItemFactory(const vector>& _items, + const vector& _unique) : unique(_unique) { + for (auto elem : _items) { + items.push_back(elem.first); + weights.push_back(elem.second); + } +} + +PItem ItemFactory::random(Optional seed) { + if (unique.size() > 0) { + ItemId id = unique.back(); + unique.pop_back(); + return fromId(id); + } + if (seed) { + RandomGen gen; + gen.init(*seed); + return fromId(items[gen.getRandom(items.size())]); + } + return fromId(chooseRandom(items, weights)); +} + +vector ItemFactory::getAll() { + vector ret; + for (ItemId id : unique) + ret.push_back(fromId(id)); + for (ItemId id : items) + ret.push_back(fromId(id)); + return ret; +} + +ItemFactory ItemFactory::villageShop() { + return ItemFactory({ + {ItemId::TELE_SCROLL, 5 }, + {ItemId::PORTAL_SCROLL, 5 }, + {ItemId::IDENTIFY_SCROLL, 5 }, + {ItemId::FIRE_SCROLL, 5 }, + {ItemId::FIRE_SPHERE_SCROLL, 5 }, + {ItemId::HEALING_POTION, 7 }, + {ItemId::SLEEP_POTION, 5 }, + {ItemId::SLOW_POTION, 5 }, + {ItemId::SPEED_POTION,5 }, + {ItemId::BLINDNESS_POTION, 5 }, + {ItemId::INVISIBLE_POTION, 2 }, + {ItemId::WARNING_AMULET, 1 }, + {ItemId::HEALING_AMULET, 1 }, + {ItemId::DEFENSE_AMULET, 1 }, + {ItemId::FIRST_AID_KIT, 5} }); +} + +ItemFactory ItemFactory::dwarfShop() { + return ItemFactory({ + {ItemId::KNIFE, 5 }, + {ItemId::SWORD, 2 }, + {ItemId::BATTLE_AXE, 2 }, + {ItemId::WAR_HAMMER, 2 }, + {ItemId::LEATHER_ARMOR, 2 }, + {ItemId::CHAIN_ARMOR, 1 }, + {ItemId::LEATHER_HELM, 2 }, + {ItemId::IRON_HELM, 1} }, + {ItemId::PORTAL_SCROLL}); +} + +ItemFactory ItemFactory::goblinShop() { + return ItemFactory({ + {ItemId::KNIFE, 5 }, + {ItemId::SWORD, 2 }, + {ItemId::BATTLE_AXE, 1 }, + {ItemId::WAR_HAMMER, 2 }, + {ItemId::LEATHER_ARMOR, 2 }, + {ItemId::CHAIN_ARMOR, 1 }, + {ItemId::LEATHER_HELM, 2 }, + {ItemId::IRON_HELM, 1 }, + {ItemId::PANIC_MUSHROOM, 1 }, + {ItemId::RAGE_MUSHROOM, 1 }, + {ItemId::STRENGTH_MUSHROOM, 1 }, + {ItemId::DEXTERITY_MUSHROOM, 1} }); +} + +ItemFactory ItemFactory::workshop() { + return ItemFactory({ + {ItemId::FIRST_AID_KIT, 5 }, + {ItemId::BOULDER_TRAP_ITEM, 5 }, + {ItemId::GAS_TRAP_ITEM, 5 }, + {ItemId::SWORD, 2 }, + {ItemId::BATTLE_AXE, 1 }, + {ItemId::WAR_HAMMER, 1 }, + {ItemId::LEATHER_ARMOR, 2 }, + {ItemId::CHAIN_ARMOR, 1 }, + {ItemId::LEATHER_HELM, 2 }, + {ItemId::IRON_HELM, 1 }, + {ItemId::SPECIAL_SWORD, 0.1}, + {ItemId::SPECIAL_BATTLE_AXE, 0.1}, + {ItemId::SPECIAL_WAR_HAMMER, 0.1}}); +} + +ItemFactory ItemFactory::potions() { + return ItemFactory({ + {ItemId::HEALING_POTION, 1 }, + {ItemId::SLEEP_POTION, 1 }, + {ItemId::SLOW_POTION, 1 }, + {ItemId::BLINDNESS_POTION, 1 }, + {ItemId::INVISIBLE_POTION, 1 }, + {ItemId::SPEED_POTION, 1 }}); +} + +ItemFactory ItemFactory::scrolls() { + return ItemFactory({ + {ItemId::TELE_SCROLL, 1 }, + {ItemId::IDENTIFY_SCROLL, 1 }, + {ItemId::FIRE_SCROLL, 1 }, + {ItemId::FIRE_SPHERE_SCROLL, 1 }, + {ItemId::PORTAL_SCROLL, 1 }}); +} + +ItemFactory ItemFactory::mushrooms() { + return ItemFactory({ + {ItemId::STRENGTH_MUSHROOM, 1 }, + {ItemId::DEXTERITY_MUSHROOM, 1 }, + {ItemId::PANIC_MUSHROOM, 1 }, + {ItemId::HALLU_MUSHROOM, 8 }, + {ItemId::RAGE_MUSHROOM, 1 }}); +} + +ItemFactory ItemFactory::amulets() { + return ItemFactory({ + {ItemId::WARNING_AMULET, 1}, + {ItemId::HEALING_AMULET, 1}, + {ItemId::DEFENSE_AMULET, 1}, + {ItemId::FRIENDLY_ANIMALS_AMULET, 1},} + ); +} + +ItemFactory ItemFactory::dungeon() { + return ItemFactory({ + {ItemId::KNIFE, 50 }, + {ItemId::SWORD, 50 }, + {ItemId::SPECIAL_SWORD, 1 }, + {ItemId::BATTLE_AXE, 10 }, + {ItemId::SPECIAL_BATTLE_AXE, 1 }, + {ItemId::WAR_HAMMER, 20 }, + {ItemId::SPECIAL_WAR_HAMMER, 1 }, + {ItemId::LEATHER_ARMOR, 20 }, + {ItemId::CHAIN_ARMOR, 0 }, + {ItemId::LEATHER_HELM, 20 }, + {ItemId::IRON_HELM, 0 }, + {ItemId::TELE_SCROLL, 30 }, + {ItemId::PORTAL_SCROLL, 10 }, + {ItemId::IDENTIFY_SCROLL, 30 }, + {ItemId::FIRE_SCROLL, 30 }, + {ItemId::FIRE_SPHERE_SCROLL, 30 }, + {ItemId::HEALING_POTION, 50 }, + {ItemId::SLEEP_POTION, 50 }, + {ItemId::SLOW_POTION, 50 }, + {ItemId::SPEED_POTION, 50 }, + {ItemId::BLINDNESS_POTION, 30 }, + {ItemId::INVISIBLE_POTION, 10 }, + {ItemId::FIRST_AID_KIT, 30 }}); +} + +ItemFactory ItemFactory::singleType(ItemId id) { + return ItemFactory({{id, 1}}); +} + +void ItemFactory::init() { + for (int i : Range(100)) + scroll_looks.push_back(toUpper(NameGenerator::scrolls.getNext())); + random_shuffle(potion_looks.begin(), potion_looks.end(),[](int a) { return Random.getRandom(a);}); + random_shuffle(amulet_looks.begin(), amulet_looks.end(),[](int a) { return Random.getRandom(a);}); +} + +PItem getPotion(int numLooks, string name, EffectType effect, int price, string description) { + return PItem(new Potion( + ViewObject(potion_ids[numLooks], ViewLayer::ITEM, "Potion"), ITATTR( + i.name = potion_looks[numLooks] + " potion"; + i.realName = "potion of " + name; + i.realPlural = "potions of " + name; + i.description = description; + i.blindName = "potion"; + i.type = ItemType::POTION; + i.fragile = true; + i.thrownToHit = 6; + i.weight = 0.3; + i.effect = effect; + i.price = price; + i.flamability = 0.3; + i.identifiable = true; + i.uses = 1;))); +} + +PItem getScroll(string name, EffectType effect, int price, string description) { + return PItem(new Item( + ViewObject(ViewId::SCROLL, ViewLayer::ITEM, "Scroll"), ITATTR( + i.name = "scroll labelled " + name; + i.plural= "scrolls labelled " + name; + i.description = description; + /* i.realName = "scroll of " + name; + i.realPlural = "scrolls of " + name;*/ + i.blindName = "scroll"; + i.type= ItemType::SCROLL; + i.weight = 0.1; + i.thrownDamage = -10; + i.effect = effect; + i.price = price; + i.flamability = 1; +// i.identifiable = true; + i.uses = 1;))); +} + +PItem getMushroom(string name, EffectType effect, string description) { + return PItem(new Item( + ViewObject(mushroom_ids[0], ViewLayer::ITEM, "Mushroom"), ITATTR( + i.name = "mushroom"; + i.realName = name + " mushroom"; + i.description = description; + i.type= ItemType::FOOD; + i.weight = 0.1; + i.thrownDamage = -15; + i.effect = effect; + i.price = 80; + i.identifyOnApply = false; + i.uses = 1;))); +} + +static int maybePlusMinusOne(int prob) { + if (Random.roll(prob)) + return Random.getRandom(2) * 2 - 1; + return 0; +} + +vector>> badArtifactNames { + {"sword", { "bang", "crush", "fist"}}, + {"battle axe", {"crush", "tooth", "razor", "fist", "bite", "bolt", "sword"}}, + {"war hammer", {"blade", "tooth", "bite", "bolt", "sword", "steel"}}}; + +void makeArtifact(ItemAttributes& i) { + bool good; + do { + good = true; + i.artifactName = NameGenerator::weaponNames.getNext(); + for (auto elem : badArtifactNames) + for (auto pattern : elem.second) + if (contains(toLower(*i.artifactName), pattern) && contains(*i.name, elem.first)) { + Debug() << "Rejected artifact " << *i.name << " " << *i.artifactName; + good = false; + } + } while (!good); + Debug() << "Making artifact " << *i.name << " " << *i.artifactName; + i.damage += Random.getRandom(1, 4); + i.toHit += Random.getRandom(1, 4); + i.name = "antique " + *i.name; + i.price *= 15; +} + +PItem ItemFactory::fromId(ItemId id) { + bool artifact = false; + switch (id) { + case ItemId::KNIFE: return PItem(new Item( + ViewObject(ViewId::KNIFE, ViewLayer::ITEM, "Knife"), ITATTR( + i.name = "knife"; + i.plural = "knives"; + i.type = ItemType::WEAPON; + i.weight = 0.3; + i.damage = 5 + maybePlusMinusOne(4); + i.toHit = maybePlusMinusOne(4); + i.attackTime = 0.7; + i.thrownDamage = 3; + i.thrownToHit = 3; + i.price = 5; + i.attackType = AttackType::STAB;))); + case ItemId::SPECIAL_SWORD: artifact = true; + case ItemId::SWORD: return PItem(new Item( + ViewObject(ViewId::SWORD, ViewLayer::ITEM, "Sword"), ITATTR( + i.name = "sword"; + i.type = ItemType::WEAPON; + i.weight = 1.5; + i.damage = 8 + maybePlusMinusOne(4); + i.toHit = 3 + maybePlusMinusOne(4); + i.price = 20; + if (artifact) { + makeArtifact(i); + } + i.attackType = AttackType::CUT;))); + case ItemId::SPECIAL_ELVEN_SWORD: artifact = true; + case ItemId::ELVEN_SWORD: return PItem(new Item( + ViewObject(ViewId::ELVEN_SWORD, ViewLayer::ITEM, "Elven sword"), ITATTR( + i.name = "elven sword"; + i.type = ItemType::WEAPON; + i.weight = 1; + i.damage = 9 + maybePlusMinusOne(4); + i.toHit = 5 + maybePlusMinusOne(4); + i.price = 120; + if (artifact) { + makeArtifact(i); + } + i.attackType = AttackType::CUT;))); + case ItemId::SPECIAL_BATTLE_AXE: artifact = true; + case ItemId::BATTLE_AXE: return PItem(new Item( + ViewObject(ViewId::BATTLE_AXE, ViewLayer::ITEM, "Battle axe"), ITATTR( + i.name = "battle axe"; + i.type = ItemType::WEAPON; + i.weight = 8; + i.damage = 14 + maybePlusMinusOne(4); + i.toHit = 0 + maybePlusMinusOne(4); + i.attackTime = 1.2; + i.twoHanded = true; + i.price = 140; + if (artifact) { + makeArtifact(i); + } + i.attackType = AttackType::CUT;))); + case ItemId::SPECIAL_WAR_HAMMER: artifact = true; + case ItemId::WAR_HAMMER: return PItem(new Item( + ViewObject(ViewId::WAR_HAMMER, ViewLayer::ITEM, "War hammer"), ITATTR( + i.name = "war hammer"; + i.type = ItemType::WEAPON; + i.weight = 8; + i.damage = 12 + maybePlusMinusOne(4); + i.toHit = 0 + maybePlusMinusOne(4); + i.attackTime = 1.2; + i.twoHanded = true; + i.price = 100; + if (artifact) { + makeArtifact(i); + } + i.attackType = AttackType::CRUSH;))); + case ItemId::SCYTHE: return PItem(new Item( + ViewObject(ViewId::SWORD, ViewLayer::ITEM, "Scythe"), ITATTR( + i.name = "scythe"; + i.type = ItemType::WEAPON; + i.weight = 5; + i.damage = 12 + maybePlusMinusOne(4); + i.toHit = 0 + maybePlusMinusOne(4); + i.attackTime = 1; + i.twoHanded = true; + i.price = 100; + i.attackType = AttackType::CUT;))); + case ItemId::BOW: return PItem(new RangedWeapon( + ViewObject(ViewId::BOW, ViewLayer::ITEM, "Short bow"), ITATTR( + i.name = "short bow"; + i.type = ItemType::RANGED_WEAPON; + i.weight = 1; + i.rangedWeaponAccuracy = 10; + i.price = 60;))); + case ItemId::ARROW: return PItem(new Item( + ViewObject(ViewId::ARROW, ViewLayer::ITEM, "Arrow"), ITATTR( + i.name = "arrow"; + i.type = ItemType::AMMO; + i.weight = 0.1; + i.thrownDamage = 5; + i.thrownToHit = -5; + i.price = 60;))); + case ItemId::LEATHER_ARMOR: return PItem(new Item( + ViewObject(ViewId::LEATHER_ARMOR, ViewLayer::ITEM, "Armor"), ITATTR( + i.name = "leather armor"; + i.type = ItemType::ARMOR; + i.weight = 7; + i.armorType = ArmorType::BODY_ARMOR; + i.price = 20; + i.defense = 3 + maybePlusMinusOne(4);))); + case ItemId::LEATHER_HELM: return PItem(new Item( + ViewObject(ViewId::LEATHER_HELM, ViewLayer::ITEM, "Helmet"), ITATTR( + i.name = "leather helm"; + i.type = ItemType::ARMOR; + i.weight = 1.5; + i.armorType = ArmorType::HELMET; + i.price = 5; + i.defense = 1 + maybePlusMinusOne(4);))); + case ItemId::CHAIN_ARMOR: return PItem(new Item( + ViewObject(ViewId::CHAIN_ARMOR, ViewLayer::ITEM, "Armor"), ITATTR( + i.name = "chain armor"; + i.type = ItemType::ARMOR; + i.weight = 15; + i.armorType = ArmorType::BODY_ARMOR; + i.price = 170; + i.defense = 5 + maybePlusMinusOne(4);))); + case ItemId::IRON_HELM: return PItem(new Item( + ViewObject(ViewId::IRON_HELM, ViewLayer::ITEM, "Helmet"), ITATTR( + i.name = "iron helm"; + i.type = ItemType::ARMOR; + i.weight = 4; + i.armorType = ArmorType::HELMET; + i.price = 70; + i.defense=2 + maybePlusMinusOne(4);))); + case ItemId::WARNING_AMULET: return PItem( + new AmuletOfWarning(ViewObject(amulet_ids[0], ViewLayer::ITEM, "Amulet"), + ITATTR( + i.name = amulet_looks[0] + " amulet"; + i.realName = "amulet of warning"; + i.description = "Warns about dangerous beasts and enemies."; + i.type = ItemType::AMULET; + i.price = 300; + i.identifiable = true; + i.weight = 0.3;), 5)); + case ItemId::HEALING_AMULET: return PItem( + new AmuletOfHealing(ViewObject(amulet_ids[1], ViewLayer::ITEM, "Amulet"), + ITATTR( + i.name = amulet_looks[1] + " amulet"; + i.realName = "amulet of healing"; + i.description = "Slowly heals all wounds."; + i.type = ItemType::AMULET; + i.price = 300; + i.identifiable = true; + i.weight = 0.3;))); + case ItemId::DEFENSE_AMULET: return PItem( + new Item(ViewObject(amulet_ids[2], ViewLayer::ITEM, "Amulet"), + ITATTR( + i.name = amulet_looks[2] + " amulet"; + i.realName = "amulet of defense"; + i.description = "Increases the toughness of your skin and flesh, making you harder to wound."; + i.type = ItemType::AMULET; + i.price = 300; + i.identifiable = true; + i.defense = 5 + maybePlusMinusOne(4); + i.weight = 0.3;))); + case ItemId::FRIENDLY_ANIMALS_AMULET: return PItem( + new AmuletOfEnemyCheck(ViewObject(amulet_ids[3], ViewLayer::ITEM, "Amulet"), + ITATTR( + i.name = amulet_looks[3] + " amulet"; + i.realName = "amulet of nature affinity"; + i.description = "Makes all animals peaceful."; + i.type = ItemType::AMULET; + i.price = 300; + i.identifiable = true; + i.weight = 0.3;), EnemyCheck::friendlyAnimals(0.5))); + case ItemId::FIRST_AID_KIT: return PItem(new Item( + ViewObject(ViewId::FIRST_AID, ViewLayer::ITEM, "First aid kit"), ITATTR( + i.name = "first aid kit"; + i.weight = 0.5; + i.type = ItemType::TOOL; + i.applyTime = 3; + i.uses = Random.getRandom(3, 6); + i.usedUpMsg = true; + i.displayUses = true; + i.price = 10; + i.effect = EffectType::HEAL;))); + case ItemId::BOULDER_TRAP_ITEM: return PItem(new Item( + ViewObject(ViewId::TRAP_ITEM, ViewLayer::ITEM, "Unarmed trap"), ITATTR( + i.name = "boulder trap"; + i.weight = 0.5; + i.type = ItemType::TOOL; + i.applyTime = 3; + i.uses = 1; + i.usedUpMsg = true; + i.effect = EffectType::GUARDING_BOULDER; + i.trapType = TrapType::BOULDER; + i.price = 10;))); + case ItemId::GAS_TRAP_ITEM: return PItem(new TrapItem( + ViewObject(ViewId::TRAP_ITEM, ViewLayer::ITEM, "Unarmed trap"), + ViewObject(ViewId::GAS_TRAP, ViewLayer::LARGE_ITEM, "Trap"),ITATTR( + i.name = "gas trap"; + i.weight = 0.5; + i.type = ItemType::TOOL; + i.applyTime = 3; + i.uses = 1; + i.usedUpMsg = true; + i.trapType = TrapType::POISON_GAS; + i.price = 10;), Effect::getEffect(EffectType::EMIT_POISON_GAS))); + case ItemId::HEALING_POTION: return getPotion(0, "healing", EffectType::HEAL, 40, "Heals all your wounds."); + case ItemId::SLEEP_POTION: return getPotion(1, "sleep", EffectType::SLEEP, 40, + "Puts anyone to sleep immediately."); + case ItemId::SLOW_POTION: return getPotion(2, "slowness", EffectType::SLOW, 40, + "Makes all your moves very sluggish"); + case ItemId::SPEED_POTION: return getPotion(3, "speed", EffectType::SPEED, 60, + "Makes all your moves very swift"); + case ItemId::BLINDNESS_POTION: return getPotion(4, "blindness", EffectType::BLINDNESS, 60, + "Makes you blind for some time."); + case ItemId::INVISIBLE_POTION: return getPotion(5, "invisibility", EffectType::INVISIBLE, 60, + "Makes you and your belongings invisible for a short time."); + case ItemId::PANIC_MUSHROOM: return getMushroom("panic", EffectType::PANIC, + "Makes the one who ate it favor defensive actions over offensive."); + case ItemId::RAGE_MUSHROOM: return getMushroom("rage", EffectType::RAGE, + "Makes the one who ate it favor offensive actions over deffensive."); + case ItemId::HALLU_MUSHROOM: return getMushroom("magic", EffectType::HALLU, + "Has a strong hallucinogenic effect."); + case ItemId::STRENGTH_MUSHROOM: return getMushroom("strength", EffectType::STR_BONUS, + "Gives you extra strength for a while."); + case ItemId::DEXTERITY_MUSHROOM: return getMushroom("dexterity", EffectType::DEX_BONUS, + "Gives you extra dexterity for a while."); + case ItemId::TELE_SCROLL: return getScroll("effugium", EffectType::TELEPORT, 50, + "Teleports the reader away by a short distance"); + case ItemId::PORTAL_SCROLL: return getScroll("ianuae magicae", EffectType::PORTAL, 50, "Creates a magical portal. Two of such portals allow instant travel between them."); + case ItemId::IDENTIFY_SCROLL: if (!Item::isEverythingIdentified()) + return getScroll("rium propositum", EffectType::IDENTIFY, 15, + "Identifies a hidden or magical use of a chosen item."); + else + return fromId(chooseRandom({ItemId::ENHANCE_W_SCROLL, ItemId::ENHANCE_A_SCROLL})); + case ItemId::BOULDER_SCROLL: return getScroll("rolling boulder", EffectType::ROLLING_BOULDER, 100, ""); + case ItemId::POISON_GAS_SCROLL: return getScroll("poison gas", EffectType::EMIT_POISON_GAS, 100, ""); + case ItemId::DESTROY_EQ_SCROLL: return getScroll("destruction", EffectType::DESTROY_EQUIPMENT, 100, + "Destroys a piece of equipment of the reader."); + case ItemId::ENHANCE_W_SCROLL: return getScroll("melius telum", EffectType::ENHANCE_WEAPON, 100, + "Increases the damage or accuracy of a weapon."); + case ItemId::ENHANCE_A_SCROLL: return getScroll("melius armatus", EffectType::ENHANCE_ARMOR, 100, + "Increases the defense value of a piece of armor."); + case ItemId::FIRE_SPHERE_SCROLL: return getScroll("sphaera ignis", EffectType::FIRE_SPHERE_PET, 100, + "Creates a sphere of fire."); + case ItemId::FIRE_SCROLL: return PItem(new FireScroll( + ViewObject(ViewId::SCROLL, ViewLayer::ITEM, "Scroll"), ITATTR( + i.name = "scroll labelled combustio"; + i.plural= "scrolls labelled combustio"; + i.description = "Sets itself on fire."; + i.blindName = "scroll"; + i.type= ItemType::SCROLL; + i.weight = 0.1; + i.thrownDamage = -10; + i.price = 15; + i.flamability = 1; + i.uses = 1;))); + case ItemId::MUSHROOM_BOOK: return PItem(new SkillBook( + ViewObject(ViewId::BOOK, ViewLayer::ITEM, "Book"), ITATTR( + i.name = "book of mushrooms"; + i.weight = 0.5; + i.type = ItemType::BOOK; + i.applyTime = 3; + i.price = 100;), Skill::mushrooms)); + case ItemId::POTION_BOOK: return PItem(new SkillBook( + ViewObject(ViewId::BOOK, ViewLayer::ITEM, "Book"), ITATTR( + i.name = "book of potions"; + i.weight = 0.5; + i.type = ItemType::BOOK; + i.applyTime = 3; + i.price = 100;), Skill::potions)); + case ItemId::AMULET_BOOK: return PItem(new SkillBook( + ViewObject(ViewId::BOOK, ViewLayer::ITEM, "Book"), ITATTR( + i.name = "book of amulets"; + i.weight = 0.5; + i.type = ItemType::BOOK; + i.applyTime = 3; + i.price = 100;), Skill::amulets)); + case ItemId::ROCK: return PItem(new Item( + ViewObject(ViewId::ROCK, ViewLayer::ITEM, "Rock"), ITATTR( + i.name = "rock"; + i.type = ItemType::OTHER; + i.price = 0; + i.weight = 0.3;))); + case ItemId::GOLD_PIECE: return PItem(new Item( + ViewObject(ViewId::GOLD, ViewLayer::ITEM, "Gold"), ITATTR( + i.name = "gold piece"; + i.type = ItemType::GOLD; + i.price = 1; + i.weight = 0.01;))); + } + Debug(FATAL) << "unhandled item " << (int) id; + return PItem(nullptr); +} + +vector ItemFactory::fromId(ItemId id, int num) { + vector ret; + for (int i : Range(num)) + ret.push_back(fromId(id)); + return ret; +} diff --git a/item_factory.h b/item_factory.h new file mode 100644 index 000000000..aec70f342 --- /dev/null +++ b/item_factory.h @@ -0,0 +1,46 @@ +#ifndef _ITEM_FACTORY +#define _ITEM_FACTORY + +#include +#include +#include + +#include "util.h" +#include "name_generator.h" + +class Item; + + +class ItemFactory { + public: + ItemFactory(const vector>&, + const vector& unique = vector()); + PItem random(Optional seed = Nothing()); + vector getAll(); + + static ItemFactory dungeon(); + static ItemFactory potions(); + static ItemFactory scrolls(); + static ItemFactory mushrooms(); + static ItemFactory amulets(); + static ItemFactory villageShop(); + static ItemFactory dwarfShop(); + static ItemFactory goblinShop(); + static ItemFactory workshop(); + static ItemFactory singleType(ItemId); + + static PItem fromId(ItemId); + static vector fromId(ItemId, int num); + static PItem corpse(const string& name, const string& rottenName, double weight, ItemType = ItemType::CORPSE); + static PItem corpse(CreatureId, ItemType type = ItemType::CORPSE); + static PItem trapItem(PTrigger trigger, string trapName); + + static void init(); + + private: + vector items; + vector weights; + vector unique; +}; + +#endif diff --git a/level.cpp b/level.cpp new file mode 100644 index 000000000..fcd6b1ed3 --- /dev/null +++ b/level.cpp @@ -0,0 +1,401 @@ +#include "stdafx.h" + +using namespace std; + + +Level::Level(Table s, Model* m, vector l, const string& message, const string& n) + : squares(std::move(s)), locations(l), model(m), fieldOfView(squares), entryMessage(message), + name(n), player(nullptr) { + for (Vec2 pos : squares.getBounds()) { + squares[pos]->setLevel(this); + Optional> link = squares[pos]->getLandingLink(); + if (link) + landingSquares[*link].push_back(pos); + } + for (Location *l : locations) + l->setLevel(this); +} + +void Level::addCreature(Vec2 position, PCreature c) { + putCreature(position, c.get()); + model->addCreature(std::move(c)); +} + +void Level::putCreature(Vec2 position, Creature* c) { + creatures.push_back(c); + CHECK(getSquare(position)->getCreature() == nullptr); + c->setLevel(this); + c->setPosition(position); + //getSquare(position)->putCreatureSilently(c); + getSquare(position)->putCreature(c); + notifyLocations(c); +} + +void Level::notifyLocations(Creature* c) { + for (Location* l : locations) + if (c->getPosition().inRectangle(l->getBounds())) + l->onCreature(c); +} + +void Level::replaceSquare(Vec2 pos, PSquare square) { + if (contains(tickingSquares, getSquare(pos))) + removeElement(tickingSquares, getSquare(pos)); + Creature* c = squares[pos]->getCreature(); + for (Item* it : squares[pos]->getItems()) + square->dropItem(squares[pos]->removeItem(it)); + square->setCovered(squares[pos]->isCovered()); + squares[pos] = std::move(square); + squares[pos]->setPosition(pos); + squares[pos]->setLevel(this); + if (c) { + squares[pos]->putCreatureSilently(c); + } + updateVisibility(pos); +} + +const Creature* Level::getPlayer() const { + return player; +} + +const Location* Level::getLocation(Vec2 pos) const { + for (Location* l : locations) + if (pos.inRectangle(l->getBounds())) + return l; + return nullptr; +} + +const vector Level::getAllLocations() const { + return locations; +} + +vector Level::getLandingSquares(StairDirection dir, StairKey key) const { + if (landingSquares.count({dir, key})) + return landingSquares.at({dir, key}); + else + return {}; +} + +Vec2 Level::landCreature(StairDirection direction, StairKey key, Creature* creature) { + vector landing = landingSquares.at({direction, key}); + return landCreature(landing, creature); +} + +Vec2 Level::landCreature(StairDirection direction, StairKey key, PCreature creature) { + Vec2 pos = landCreature(direction, key, creature.get()); + model->addCreature(std::move(creature)); + return pos; +} + +Vec2 Level::landCreature(vector landing, Creature* creature) { + CHECK(creature); + if (entryMessage != "") { + creature->privateMessage(entryMessage); + entryMessage = ""; + } + queue> q; + for (Vec2 pos : randomPermutation(landing)) + q.push(make_pair(pos, pos)); + while (!q.empty()) { + pair v = q.front(); + q.pop(); + if (squares[v.first]->canEnter(creature)) { + putCreature(v.first, creature); + return v.second; + } else + for (Vec2 next : v.first.neighbors8(true)) + if (squares[next]->canEnterEmpty(creature)) + q.push(make_pair(next, v.second)); + } + Debug(FATAL) << "Failed to find any square to put creature"; + return Vec2(0, 0); +} + +void Level::throwItem(PItem item, Attack attack, int maxDist, Vec2 position, Vec2 direction) { + int cnt = 1; + vector trajectory; + for (Vec2 v = position + direction;; v += direction) { + trajectory.push_back(v); + if (getSquare(v)->itemBounces(item.get())) { + item->onHitSquare(v, getSquare(v)); + trajectory.pop_back(); + EventListener::addThrowEvent(this, attack.getAttacker(), item.get(), trajectory); + if (!item->isDiscarded()) + getSquare(v - direction)->dropItem(std::move(item)); + return; + } + if (++cnt > maxDist || getSquare(v)->itemLands(item.get(), attack)) { + EventListener::addThrowEvent(this, attack.getAttacker(), item.get(), trajectory); + getSquare(v)->onItemLands(std::move(item), attack, maxDist - cnt - 1, direction); + return; + } + } +} + +void Level::killCreature(Creature* creature) { + removeElement(creatures, creature); + getSquare(creature->getPosition())->removeCreature(); + model->removeCreature(creature); + if (creature->isPlayer()) + setPlayer(nullptr); +} + +void Level::updateVisibility(Vec2 changedSquare) { + fieldOfView.squareChanged(changedSquare); +} + +void Level::globalMessage(Vec2 position, const string& ifPlayerCanSee, const string& cannot) const { + if (player) { + if (playerCanSee(position)) + player->privateMessage(ifPlayerCanSee); + else + player->privateMessage(cannot); + } +} + +void Level::changeLevel(StairDirection dir, StairKey key, Creature* c) { + Vec2 fromPosition = c->getPosition(); + removeElement(creatures, c); + getSquare(c->getPosition())->removeCreature(); + Vec2 toPosition = model->changeLevel(dir, key, c); + EventListener::addChangeLevelEvent(c, this, fromPosition, c->getLevel(), toPosition); +} + +void Level::changeLevel(Level* destination, Vec2 landing, Creature* c) { + Vec2 fromPosition = c->getPosition(); + removeElement(creatures, c); + getSquare(c->getPosition())->removeCreature(); + model->changeLevel(destination, landing, c); + EventListener::addChangeLevelEvent(c, this, fromPosition, destination, landing); +} + +void Level::setPlayer(Creature* player) { + this->player = player; +} + +const vector& Level::getAllCreatures() const { + return creatures; +} + +vector& Level::getAllCreatures() { + return creatures; +} + +bool Level::canSee(Vec2 from, Vec2 to) const { + return fieldOfView.canSee(from, to); +} + +bool Level::playerCanSee(Vec2 pos) const { + return player != nullptr && player->canSee(pos); +} + +bool Level::playerCanSee(const Creature* c) const { + return player != nullptr && player->canSee(c); +} + +bool Level::canMoveCreature(const Creature* creature, Vec2 direction) const { + Vec2 position = creature->getPosition(); + Vec2 destination = position + direction; + if (!inBounds(destination)) + return false; + return getSquare(destination)->canEnter(creature); +} + +void Level::moveCreature(Creature* creature, Vec2 direction) { + CHECK(canMoveCreature(creature, direction)); + Vec2 position = creature->getPosition(); + Square* nextSquare = getSquare(position + direction); + Square* thisSquare = getSquare(position); + thisSquare->removeCreature(); + creature->setPosition(position + direction); + nextSquare->putCreature(creature); + notifyLocations(creature); +} + +void Level::swapCreatures(Creature* c1, Creature* c2) { + Vec2 position1 = c1->getPosition(); + Vec2 position2 = c2->getPosition(); + Square* square1 = getSquare(position1); + Square* square2 = getSquare(position2); + square1->removeCreature(); + square2->removeCreature(); + c1->setPosition(position2); + c2->setPosition(position1); + square1->putCreature(c2); + square2->putCreature(c1); + notifyLocations(c1); + notifyLocations(c2); +} + + +vector Level::getVisibleTiles(const Creature* c) const { + static vector emptyVec; + if (!c->isBlind()) + return fieldOfView.getVisibleTiles(c->getPosition()); + else + return emptyVec; +} + +unordered_map objectList; + +void Level::setBackgroundLevel(const Level* l, Vec2 offs) { + backgroundLevel = l; + backgroundOffset = offs; +} + +static unordered_map background; + + +const Square* Level::getSquare(Vec2 pos) const { + return squares[pos].get(); +} + +Square* Level::getSquare(Vec2 pos) { + return squares[pos].get(); +} + +void Level::addTickingSquare(Vec2 pos) { + Square* s = squares[pos].get(); + if (!contains(tickingSquares, s)) + tickingSquares.push_back(s); +} + +vector Level::getTickingSquares() const { + return tickingSquares; +} + +Level::Builder::Builder(int width, int height, const string& n) : squares(width, height), heightMap(width, height), attrib(width, height), type(width, height), name(n) { +} + +Level::Builder::Builder(Builder&& other) : + squares(std::move(other.squares)), + heightMap(std::move(other.heightMap)), + attrib(std::move(other.attrib)), + type(std::move(other.type)), + creatures(std::move(other.creatures)), + entryMessage(other.entryMessage), + name(other.name) { +} + +bool Level::Builder::hasAttrib(Vec2 pos, SquareAttrib attr) { + CHECK(squares[pos] != nullptr); + return attrib[pos].count(attr); +} + +void Level::Builder::addAttrib(Vec2 pos, SquareAttrib attr) { + attrib[pos].insert(attr); +} + +void Level::Builder::removeAttrib(Vec2 pos, SquareAttrib attr) { + attrib[pos].erase(attr); +} + +Square* Level::Builder::getSquare(Vec2 pos) { + return squares[pos].get(); +} + +SquareType Level::Builder::getType(Vec2 pos) { + return type[pos]; +} + +void Level::Builder::putSquare(Vec2 pos, SquareType t, Optional at) { + squares[pos].reset(SquareFactory::get(t)); + squares[pos]->setPosition(pos); + if (at) + attrib[pos].insert(*at); + type[pos] = t; +} + +void Level::Builder::putSquare(Vec2 pos, SquareType t, vector attribs) { + squares[pos].reset(SquareFactory::get(t)); + squares[pos]->setPosition(pos); + for (auto at : attribs) + attrib[pos].insert(at); + type[pos] = t; +} + +void Level::Builder::putSquare(Vec2 pos, Square* square, SquareType t, Optional attr) { + square->setPosition(pos); + squares[pos].reset(std::move(square)); + if (attr) + attrib[pos].insert(*attr); + type[pos] = t; +} + +void Level::Builder::addLocation(Location* l) { + locations.push_back(l); +} + +void Level::Builder::setHeightMap(Vec2 pos, double h) { + heightMap[pos] = h; +} + +double Level::Builder::getHeightMap(Vec2 pos) { + return heightMap[pos]; +} + +void Level::Builder::setCovered(Vec2 pos) { + covered.insert(pos); +} + +void Level::Builder::putCreature(Vec2 pos, PCreature creature) { + creature->setPosition(pos); + creatures.push_back(NOTNULL(std::move(creature))); +} + +bool Level::Builder::canPutCreature(Vec2 pos, Creature* c) { + if (!squares[pos]->canEnter(c)) + return false; + for (PCreature& c : creatures) { + if (c->getPosition() == pos) + return false; + } + return true; +} + +void Level::Builder::setMessage(const string& message) { + entryMessage = message; +} + +Level* Level::Builder::build(Model* m, bool surface) { + for (Vec2 v : heightMap.getBounds()) { + squares[v]->setHeight(heightMap[v]); + if (covered.count(v) || !surface) { + Debug() << "Covered " << v; + squares[v]->setCovered(true); + } + } + Level* l = new Level(std::move(squares), m, locations, entryMessage, name); + for (PCreature& c : creatures) { + Vec2 pos = c->getPosition(); + l->addCreature(pos, std::move(c)); + } + return l; +} + +int Level::Builder::getWidth() { + return squares.getWidth(); +} + +int Level::Builder::getHeight() { + return squares.getHeight(); +} + +bool Level::inBounds(Vec2 pos) const { + return pos.inRectangle(getBounds()); +} + +Rectangle Level::getBounds() const { + return Rectangle(0, 0, getWidth(), getHeight()); +} + +int Level::getWidth() const { + return squares.getWidth(); +} + +int Level::getHeight() const { + return squares.getHeight(); +} + +const string& Level::getName() const { + return name; +} diff --git a/level.h b/level.h new file mode 100644 index 000000000..acaf085b5 --- /dev/null +++ b/level.h @@ -0,0 +1,232 @@ +#ifndef _LEVEL_H +#define _LEVEL_H + +#include +#include +#include +#include "util.h" +#include "time_queue.h" +#include "debug.h" +#include "view.h" +#include "field_of_view.h" +#include "square_factory.h" + +class Model; +class Square; +class View; +class Player; + + +/** A class representing a single level of the dungeon or the overworld. All events occuring on the level are performed by this class.*/ +class Level { + public: + + /** 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); + + /** Swaps positions of two creatures. */ + void swapCreatures(Creature*, Creature*); + + /** Puts \paramname{creature} on \paramname{position}. \paramname{creature} ownership is assumed by the model.*/ + void addCreature(Vec2 position, PCreature creature); + + /** Puts the \paramname{creature} on \paramname{position}. */ + void putCreature(Vec2 position, Creature* creature); + + //@{ + /** Finds an appropriate square for the \paramname{creature} changing level from \paramname{direction}. + The square's method Square::isLandingSquare must return true for \paramname{direction}. + Returns the position of the stairs that were used. */ + Vec2 landCreature(StairDirection direction, StairKey key, Creature* creature); + Vec2 landCreature(StairDirection direction, StairKey key, PCreature creature); + //@} + + /** Lands the creature on the level randomly choosing one of the given squares. + Returns the position of the stairs that were used.*/ + Vec2 landCreature(vector landing, Creature* creature); + + /** Returns the landing squares for given direction and stair key. See Square::getLandingLink() */ + vector getLandingSquares(StairDirection, StairKey) const; + + /** Removes the creature from \paramname{position} from the level and model. The creature object is deleted.*/ + void killCreature(Creature*); + + /** Recalculates visibility data assuming that \paramname{changedSquare} has changed + its obstructing/non-obstructing attribute. */ + void updateVisibility(Vec2 changedSquare); + + /** Returns width of the level.*/ + int getWidth() const; + + /** Returns height of the level.*/ + int getHeight() const; + + /** Checks \paramname{pos} lies within the level's boundaries.*/ + bool inBounds(Vec2 pos) const; + + /** Returns the level's boundaries.*/ + Rectangle getBounds() const; + + /** Returns the name of the level. */ + const string& getName() const; + + //@{ + /** Returns the given square. \paramname{pos} must lie within the boundaries. */ + const Square* getSquare(Vec2 pos) const; + Square* getSquare(Vec2 pos); + //@} + + void replaceSquare(Vec2 pos, PSquare square); + + /** The given square's method Square::tick() will be called every turn. */ + void addTickingSquare(Vec2 pos); + + /** Returns all squares that must be ticked. */ + vector getTickingSquares() const; + + /** Moves the creature to a different level according to \paramname{direction}. */ + void changeLevel(StairDirection direction, StairKey key, Creature* c); + + /** Moves the creature to a given level. */ + void changeLevel(Level* destination, Vec2 landing, Creature* c); + + /** Performs a throw of the item, with all consequences of the event.*/ + void throwItem(PItem item, Attack attack, int maxDist, Vec2 position, Vec2 direction); + + /** Sets the creature that is assumed to be the player.*/ + void setPlayer(Creature* player); + + /** Sets the level to be rendered in the background with given offset.*/ + void setBackgroundLevel(const Level*, Vec2 offset); + + //@{ + /** Returns all creatures on this level. */ + const vector& getAllCreatures() const; + vector& getAllCreatures(); + //@} + + /** Checks whether one square is visible from the other. This function is not guaranteed to be simmetrical.*/ + bool canSee(Vec2 from, Vec2 to) const; + + /** Returns all tiles visible by a creature.*/ + vector getVisibleTiles(const Creature*) const; + + /** Checks if the player can see a given square.*/ + bool playerCanSee(Vec2 pos) const; + + /** Checks if the player can see a given creature.*/ + bool playerCanSee(const Creature*) const; + + /** Displays \paramname{playerCanSee} message if the player can see position \paramname{pos}, and \paramname{cannot} otherwise.*/ + void globalMessage(Vec2 position, const string& playerCanSee, const string& cannot = "") const; + + /** Returns the player creature.*/ + const Creature* getPlayer() const; + + /** Returns name of the given location. Returns empty string if none. **/ + const Location* getLocation(Vec2) const; + + const vector getAllLocations() const; + + /** Class used to initialize a level object.*/ + class Builder { + public: + /** Constructs a builder with given size and name. */ + Builder(int width, int height, const string& name); + + /** Move constructor.*/ + Builder(Builder&&); + + /** Returns a given square.*/ + Square* getSquare(Vec2); + + /** Checks if it's possible to put a creature on given square.*/ + bool canPutCreature(Vec2, Creature*); + + /** Puts a creatue on a given square. If the square is later changed to something else, the creature remains.*/ + void putCreature(Vec2, PCreature); + + /** Sets the message displayed when the player first enters the level.*/ + void setMessage(const string& message); + + /** Builds the level. The level will keep reference to the model. + \paramname{surface} tells if this level is on the Earth surface.*/ + Level* build(Model*, bool surface); + + /** Returns the width of the level.*/ + int getWidth(); + + /** Returns the height of the level.*/ + int getHeight(); + + //@{ + /** Puts a square on given position. Sets optional attributes of the square. The attributes remain if the square is changed.*/ + void putSquare(Vec2, SquareType, Optional = Nothing()); + void putSquare(Vec2, SquareType, vector attribs); + void putSquare(Vec2, Square*, SquareType, Optional = Nothing()); + //@} + + /** Returns the square type.*/ + SquareType getType(Vec2); + + /** Checks if the given square has an attribute.*/ + bool hasAttrib(Vec2 pos, SquareAttrib attr); + + /** Adds attribute to given square. The attribute will remain if the square is changed.*/ + void addAttrib(Vec2 pos, SquareAttrib attr); + + /** Removes attribute from given square.*/ + void removeAttrib(Vec2 pos, SquareAttrib attr); + + /** Sets the height of the given square.*/ + void setHeightMap(Vec2 pos, double h); + + /** Returns the height of the given square.*/ + double getHeightMap(Vec2 pos); + + /** Sets the location name for the given square. The name will remain if the square is changed.*/ + void addLocation(Location*); + + /** Marks given square as covered. The value will remain if square is changed.*/ + void setCovered(Vec2); + + private: + Table squares; + Table heightMap; + vector locations; + unordered_set covered; + Table> attrib; + Table type; + vector creatures; + string entryMessage; + string name; + }; + + typedef unique_ptr PBuilder; + + private: + Table squares; + map, vector> landingSquares; + vector locations; + vector tickingSquares; + vector creatures; + Model* model; + mutable FieldOfView fieldOfView; + string entryMessage; + string name; + Creature* player; + const Level* backgroundLevel = nullptr; + Vec2 backgroundOffset; + View* view; + + Level(Table s, Model*, vector, const string& message, const string& name); + + /** Notify relevant locations about creature position. */ + void notifyLocations(Creature*); +}; + +#endif diff --git a/level_generator.h b/level_generator.h new file mode 100644 index 000000000..26cc3d661 --- /dev/null +++ b/level_generator.h @@ -0,0 +1,21 @@ +#ifndef _LEVEL_GENERATOR +#define _LEVEL_GENERATOR + +#include +#include "util.h" +#include "level.h" +#include "square_factory.h" +#include "item_factory.h" +#include "creature_factory.h" + +class StaticLevelGenerator { + public: + + Level* createLevel(const char* filename, + SquareFactory* squareFactory, + ItemFactory* itemFactory, + CreatureFactory* createFactory); +}; + + +#endif diff --git a/level_maker.cpp b/level_maker.cpp new file mode 100644 index 000000000..df89ca8bd --- /dev/null +++ b/level_maker.cpp @@ -0,0 +1,1452 @@ +#include "stdafx.h" + +using namespace std; + + +class SquarePredicate { + public: + virtual bool apply(Level::Builder* builder, Vec2 pos) = 0; +}; + +class AttribPredicate : public SquarePredicate { + public: + AttribPredicate(SquareAttrib attr) : onAttr(attr) {} + + virtual bool apply(Level::Builder* builder, Vec2 pos) override { + return builder->hasAttrib(pos, onAttr); + } + + private: + SquareAttrib onAttr; +}; + +class TypePredicate : public SquarePredicate { + public: + TypePredicate(SquareType type) : onType(1, type) {} + + TypePredicate(vector types) : onType(types) {} + + virtual bool apply(Level::Builder* builder, Vec2 pos) override { + return contains(onType, builder->getType(pos)); + } + + private: + vector onType; +}; + +class AlwaysTrue : public SquarePredicate { + public: + virtual bool apply(Level::Builder* builder, Vec2 pos) override { + return true; + } +}; + +class RoomMaker : public LevelMaker { + public: + RoomMaker(int _minRooms,int _maxRooms, + int _minSize, int _maxSize, + SquareType _wallType, + Optional _onType, + LevelMaker* _shopMaker = nullptr, + bool _diggableCorners = false) : + minRooms(_minRooms), + maxRooms(_maxRooms), + minSize(_minSize), + maxSize(_maxSize), + wallType(_wallType), + squareType(_onType), + shopMaker(_shopMaker), + diggableCorners(_diggableCorners) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + int spaceBetween = 0; + Table taken(area.getKX(), area.getKY()); + for (Vec2 v : area) + taken[v] = squareType && squareType != builder->getType(v); + int rooms = Random.getRandom(minRooms, maxRooms); + int numShop = shopMaker ? Random.getRandom(rooms) : -1; + for (int i : Range(rooms)) { + Vec2 p, k; + bool good; + int cnt = 100; + do { + k = Vec2(Random.getRandom(minSize, maxSize), Random.getRandom(minSize, maxSize)); + p = Vec2(area.getPX() + spaceBetween + Random.getRandom(area.getW() - k.x - 2 * spaceBetween), + area.getPY() + spaceBetween + Random.getRandom(area.getH() - k.y - 2 * spaceBetween)); + good = true; + for (Vec2 v : Rectangle(k.x + 2 * spaceBetween, k.y + 2 * spaceBetween)) + if (taken[p + v - Vec2(spaceBetween,spaceBetween)]) { + good = false; + break; + } + } while (!good && --cnt > 0); + if (cnt == 0) { + Debug() << "Placed only " << i << " rooms out of " << rooms; + break; + } + for (Vec2 v : Rectangle(k)) + taken[v + p] = 1; + for (Vec2 v : Rectangle(k - Vec2(2, 2))) + builder->putSquare(p + v + Vec2(1, 1), SquareType::FLOOR, SquareAttrib::ROOM); + for (int i : Range(p.x, p.x + k.x)) { + if (i == p.x || i == p.x + k.x - 1) { + builder->putSquare(Vec2(i, p.y), wallType, + !diggableCorners ? Optional(SquareAttrib::NO_DIG) : Nothing()); + builder->putSquare(Vec2(i, p.y + k.y - 1), wallType, + !diggableCorners ? Optional(SquareAttrib::NO_DIG) : Nothing()); + } else { + builder->putSquare(Vec2(i, p.y), wallType); + builder->putSquare(Vec2(i, p.y + k.y - 1), wallType); + } + } + for (int i : Range(p.y, p.y + k.y)) { + if (i > p.y && i < p.y + k.y - 1) { + builder->putSquare(Vec2(p.x, i), wallType); + builder->putSquare(Vec2(p.x + k.x - 1, i), wallType); + } + } + if (i == numShop) + shopMaker->make(builder, Rectangle(p.x, p.y, p.x + k.x, p.y + k.y)); + } + } + + private: + int minRooms; + int maxRooms; + int minSize; + int maxSize; + SquareType wallType; + Optional squareType; + LevelMaker* shopMaker; + bool diggableCorners; +}; + +class Connector : public LevelMaker { + public: + Connector(initializer_list door) : doorProb(door) { + CHECKEQ((int) door.size(), 3); + } + double getValue(Level::Builder* builder, Vec2 pos, Rectangle area) { + if (builder->getSquare(pos)->canEnter(Creature::getDefault())) + return 1; + if (builder->hasAttrib(pos, SquareAttrib::LAKE)) + return 15; + if (builder->hasAttrib(pos, SquareAttrib::RIVER)) + return 5; + if (builder->hasAttrib(pos, SquareAttrib::NO_DIG)) + return ShortestPath::infinity; + int numCorners = 0; + int numTotal = 0; + for (Vec2 v : Vec2::directions8()) + if ((pos + v).inRectangle(area) && builder->getSquare(pos + v)->canEnter(Creature::getDefault())) { + if (abs(v.x) == abs(v.y)) + ++numCorners; + ++numTotal; + } + if (numCorners == 1) + return 1000; + if (numTotal - numCorners > 1) + return 5; + return 3; + } + + void connect(Level::Builder* builder, Vec2 p1, Vec2 p2, Rectangle area) { + ShortestPath path(area, + [builder, this, &area](Vec2 pos) { return getValue(builder, pos, area); }, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(true), p1 ,p2); + Vec2 prev(-100, -100); + for (Vec2 v = p2; v != p1; v = path.getNextMove(v)) { + if (!builder->getSquare(v)->canEnter(Creature::getDefault())) { + char sym; + SquareType newType = SquareType(0); + switch (builder->getType(v)) { + case SquareType::ROCK_WALL: + newType = chooseRandom({ + SquareType::PATH, + SquareType::DOOR, + SquareType::SECRET_PASS}, doorProb); + break; + case SquareType::ABYSS: + case SquareType::WATER: + case SquareType::MAGMA: + newType = SquareType::BRIDGE; + break; + case SquareType::YELLOW_WALL: + case SquareType::BLACK_WALL: + newType = SquareType::PATH; + break; + default: + Debug(FATAL) << "Unhandled square type " << (int)builder->getType(v); + } + builder->putSquare(v, newType); + } + if (builder->getType(v) == SquareType::PATH || + builder->getType(v) == SquareType::BRIDGE || + builder->getType(v) == SquareType::DOOR || + builder->getType(v) == SquareType::SECRET_PASS) { + builder->getSquare(v)->addTravelDir(path.getNextMove(v) - v); + if (prev.x > -100) + builder->getSquare(v)->addTravelDir(prev - v); + } + prev = v; + } + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + int dead_end = 1000000; + Vec2 p1, p2; + for (int i : Range(30)) { + do { + p1 = Vec2(Random.getRandom(area.getPX(), area.getKX()), Random.getRandom(area.getPY(), area.getKY())); + } while (!builder->getSquare(p1)->canEnter(Creature::getDefault()) && !Random.roll(dead_end)); + do { + p2 = Vec2(Random.getRandom(area.getPX(), area.getKX()), Random.getRandom(area.getPY(), area.getKY())); + } while (!builder->getSquare(p2)->canEnter(Creature::getDefault()) && !Random.roll(dead_end)); + connect(builder, p1, p2, area); + } + ShortestPath connections(area, + [builder](Vec2 pos)->double { + if (builder->getSquare(pos)->canEnter(Creature::getDefault())) + return 1; + else + return ShortestPath::infinity;}, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions8(), p1, p2); + for (Vec2 v : area) + if (builder->getSquare(v)->canEnter(Creature::getDefault()) && !connections.isReachable(v)) { + connect(builder, p1, v, area); + } + } + + private: + initializer_list doorProb; +}; + +class DungeonFeatures : public LevelMaker { + public: + DungeonFeatures(SquareType _onType, map > _squareTypes, + Optional setAttr = Nothing()): + squareTypes(_squareTypes), onType(_onType), attr(setAttr) { + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + vector available; + for (Vec2 v : area) + if (builder->getType(v) == onType) + available.push_back(v); + int maxTotal = 0; + for (auto iter : squareTypes) { + maxTotal += iter.second.second - 1; + } + CHECK(maxTotal <= available.size()) << "Not enough available squares " << (int)available.size() + << " for " << maxTotal << " features."; + for (auto iter : squareTypes) { + int num = Random.getRandom(iter.second.first, iter.second.second); + for (int i : Range(num)) { + int vInd = Random.getRandom(available.size()); + builder->putSquare(available[vInd], iter.first); + if (attr) + builder->addAttrib(available[vInd], *attr); + removeIndex(available, vInd); + } + } + } + + private: + map > squareTypes; + SquareType onType; + Optional attr; +}; + +class Creatures : public LevelMaker { + public: + Creatures(CreatureFactory cf, int minc, int maxc, MonsterAIFactory actorF, Optional type = Nothing()) : + cfactory(cf), minCreature(minc), maxCreature(maxc), actorFactory(actorF), squareType(type) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + int numCreature = Random.getRandom(minCreature, maxCreature); + Table taken(area.getKX(), area.getKY()); + for (int i : Range(numCreature)) { + PCreature creature = cfactory.random(actorFactory); + Vec2 pos; + do { + pos = Vec2(Random.getRandom(area.getPX(), area.getKX()), Random.getRandom(area.getPY(), area.getKY())); + } while (!builder->canPutCreature(pos, creature.get()) + || (squareType && builder->getType(pos) != *squareType)); + builder->putCreature(pos, std::move(creature)); + taken[pos] = 1; + } + } + + private: + CreatureFactory cfactory; + int minCreature; + int maxCreature; + MonsterAIFactory actorFactory; + Optional squareType; +}; + +class Items : public LevelMaker { + public: + Items(ItemFactory _factory, SquareType _onType, int minc, int maxc) : + factory(_factory), onType(_onType), minItem(minc), maxItem(maxc) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + int numItem = Random.getRandom(minItem, maxItem); + for (int i : Range(numItem)) { + Vec2 pos; + do { + pos = Vec2(Random.getRandom(area.getPX(), area.getKX()), Random.getRandom(area.getPY(), area.getKY())); + } while (builder->getType(pos) != onType); + builder->getSquare(pos)->dropItem(factory.random()); + } + } + + private: + ItemFactory factory; + SquareType onType; + int minItem; + int maxItem; +}; + +class River : public LevelMaker { + public: + River(int _width, SquareType _squareType) : width(_width), squareType(_squareType){} + + virtual void make(Level::Builder* builder, Rectangle area) override { + int wind = 5; + int middle = (area.getPX() + area.getKX()) / 2; + int px = Random.getRandom(middle - wind, middle + width); + int kx = px + Random.getRandom(-wind, wind); // Random.getRandom(area.getPX(), area.getKX()) - width; + if (kx < 0) + kx = 0; + if (kx >= area.getKX() - width) + kx = area.getKX() - width - 1; + int tot = 5; + for (int h : Range(tot)) { + int height = area.getPY() * (tot - h) / tot + area.getKY() * h / tot; + int height2 = area.getPY() * (tot - h - 1) / tot + area.getKY() * (h + 1) / tot; + vector line = straightLine(px, height, kx, (h == tot - 1) ? area.getKY() : height2); + for (Vec2 v : line) + for (int i : Range(width)) + builder->putSquare(v + Vec2(i, 0), squareType, SquareAttrib::RIVER); + px = kx; + kx = px + Random.getRandom(-wind, wind); + if (kx < 0) + kx = 0; + if (kx >= area.getKX() - width) + kx = area.getKX() - width - 1; + } + } + + private: + + vector straightLine(int x0, int y0, int x1, int y1){ + Debug() << "Line " << x1 << " " << y0 << " " << x1 << " " << y1; + int dx = x1 - x0; + int dy = y1 - y0; + vector ret{ Vec2(x0, y0)}; + if (abs(dx) > abs(dy)) { // slope < 1 + double m = (double) dy / (double) dx; // compute slope + double b = y0 - m*x0; + dx = (dx < 0) ? -1 : 1; + while (x0+dx != x1) { + x0 += dx; + ret.push_back(Vec2(x0,(int)roundf(m*x0+b))); + } + } else + if (dy != 0) { // slope >= 1 + double m = (double) dx / (double) dy; // compute slope + double b = x0 - m*y0; + dy = (dy < 0) ? -1 : 1; + while (y0+dy != y1) { + y0 += dy; + ret.push_back(Vec2((int)round(m*y0+b),y0)); + } + } + return ret; + } + + int width; + SquareType squareType; +}; + +/*class MountainRiver : public LevelMaker { + public: + MountainRiver(int num, char w) : number(num), water(w) {} + virtual void make(Level::Builder* builder, Rectangle area) override { + for (int i : Range(number)) { + Vec2 pos(Random.getRandom(area.getPX(), area.getKX()), + Random.getRandom(area.getPY(), area.getKY())); + while (1) { + builder->putSquare(pos, SquareFactory::fromSymbol(water), SquareType::RIVER); + double h = builder->getHeightMap(pos); + double lowest = 10000000; + Vec2 dir; + for (Vec2 v : Vec2::neighbors8(true)) { + double d; + if ((pos + v).inRectangle(area) && (d = builder->getHeightMap(pos + v)) < lowest && builder->getType(pos + v) != SquareType::RIVER) { + lowest = d; + dir = v; + } + } + if (lowest > 100000) + break;; + // if (lowest < h) { + pos = pos + dir; + // } else + // break; + } + } + } + + private: + int number; + char water; +};*/ + +class Blob : public LevelMaker { + public: + Blob(SquareType insideSquare, Optional borderSquare = Nothing(), + Optional _attrib = Nothing()) : + inside(insideSquare), border(borderSquare), attrib(_attrib) {} + + void addSquare(Level::Builder* builder, Vec2 pos) { + builder->putSquare(pos, inside, attrib); + if (border) + for (Vec2 dir : pos.neighbors8()) + if (builder->getType(dir) != inside) + builder->putSquare(dir, *border, attrib); + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + vector squares; + Table isInside(area, 0); + Vec2 center((area.getKX() + area.getPX()) / 2, (area.getKY() + area.getPY()) / 2); + squares.push_back(center); + addSquare(builder, center); + int maxSquares = area.getW() * area.getH() / 3; + int numSquares = 0; + isInside[center] = 1; + while (1) { + Vec2 pos = squares[Random.getRandom(squares.size())]; + for (Vec2 next : pos.neighbors4(true)) { + if (next.inRectangle(Rectangle(area.getPX() + 1, area.getPY() + 1, area.getKX() - 1, area.getKY() - 1)) + && !isInside[next]) { + Vec2 proj = next - center; + proj.y *= area.getW(); + proj.y /= area.getH(); + if (Random.getDouble() <= 1. - proj.lengthD() / (area.getW() / 2)) { + addSquare(builder, next); + squares.push_back(next); + isInside[next] = 1; + if (++numSquares >= maxSquares) + return; + } + break; + } + } + } + } + + private: + SquareType inside; + Optional border; + Optional attrib; +}; + +class Empty : public LevelMaker { + public: + Empty(SquareType s) : square(s) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (Vec2 v : area) + builder->putSquare(v, square); + } + + private: + SquareType square; +}; + +Vec2 LevelMaker::getRandomExit(Rectangle rect) { + CHECK(rect.getW() > 2 && rect.getH() > 2); + int w1 = Random.getRandom(2); + int w2 = Random.getRandom(2); + int d1 = Random.getRandom(1, rect.getW() - 1); + int d2 = Random.getRandom(1, rect.getH() - 1); + return Vec2( + rect.getPX() + d1 * w1 + (1 - w1) * w2 * (rect.getW() - 1), + rect.getPY() + d2 * (1 - w1) + w1 * w2 * (rect.getH() - 1)); +} + +class Buildings : public LevelMaker { + public: + Buildings(int buildings, + int _minSize, int _maxSize, + SquareType _wall, SquareType _floor, SquareType _door, + bool _align, + vector _insideMakers, + bool _roadConnection = true) : + numBuildings(buildings), + minSize(_minSize), maxSize(_maxSize), + align(_align), + wall(_wall), door(_door), floor(_floor), + insideMakers(_insideMakers), + roadConnection(_roadConnection) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + Table filled(area); + int width = area.getW(); + int height = area.getH(); + for (Vec2 v : area) + filled[v] = 0; + int sizeVar = 1; + int spaceBetween = 1; + int alignHeight = 0; + if (align) { + alignHeight = height / 2 - 2 + Random.getRandom(5); + } + int nextw = -1; + for (int i = 0; i < numBuildings; ++i) { + bool spaceOk = true; + int w, h, px, py; + int cnt = 10000; + bool buildingRow; + do { + buildingRow = Random.getRandom(2); + spaceOk = true; + w = Random.getRandom(minSize, maxSize); + h = Random.getRandom(minSize, maxSize); + if (nextw > -1 && nextw + w < area.getKX()) { + px = nextw; + nextw = -1; + } else + px = area.getPX() + Random.getRandom(width - w - 2 * spaceBetween + 1) + spaceBetween; + if (!align) + py = area.getPY() + Random.getRandom(height - h - 2 * spaceBetween + 1) + spaceBetween; + else { + py = area.getPY() + (buildingRow == 1 ? alignHeight - h - 1 : alignHeight + 2); + if (py + h >= area.getKY() || py < area.getPY()) { + spaceOk = false; + continue; + } + } + Vec2 tmp(px - spaceBetween, py - spaceBetween); + for (Vec2 v : Rectangle(w + spaceBetween * 2 + 1, h + spaceBetween * 2 + 1)) + if (!(tmp + v).inRectangle(area) || filled[px + v.x - spaceBetween][py + v.y - spaceBetween]) { + spaceOk = false; + break; + } + } while (!spaceOk && --cnt > 0); + CHECK(cnt > 0) << "Failed to add " << numBuildings - i << " buildings out of " << numBuildings; + if (Random.roll(1)) + nextw = px + w; + for (Vec2 v : Rectangle(w + 1, h + 1)) { + filled[Vec2(px, py) + v] = true; + builder->putSquare(Vec2(px, py) + v, wall); + builder->setCovered(Vec2(px, py) + v); + } + for (Vec2 v : Rectangle(w - 1, h - 1)) { + builder->putSquare(Vec2(px + 1, py + 1) + v, floor, SquareAttrib::ROOM); + } + Vec2 doorLoc = align ? + Vec2(px + Random.getRandom(1, w), + py + (buildingRow * h)) : + getRandomExit(Rectangle(px, py, px + w + 1, py + h + 1)); + builder->putSquare(doorLoc, door); + if (i < insideMakers.size()) + insideMakers[i]->make(builder, Rectangle(px, py, px + w + 1, py + h + 1)); + } + if (roadConnection) + builder->putSquare(Vec2((area.getPX() + area.getKX()) / 2, area.getPY() + alignHeight), + SquareType::PATH, SquareAttrib::CONNECT); + } + + private: + int numBuildings; + int minSize; + int maxSize; + bool align; + SquareType wall, door, floor; + vector insideMakers; + bool roadConnection; +}; + +class BorderGuard : public LevelMaker { + public: + + BorderGuard(LevelMaker* inside, SquareType _type = SquareType::BORDER_GUARD) : type(_type), insideMaker(inside) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (int i : Range(area.getPX(), area.getKX())) { + builder->putSquare(Vec2(i, area.getPY()), type); + builder->putSquare(Vec2(i, area.getKY() - 1), type); + } + for (int i : Range(area.getPY(), area.getKY())) { + builder->putSquare(Vec2(area.getPX(), i), type); + builder->putSquare(Vec2(area.getKX() - 1, i), type); + } + insideMaker->make(builder, Rectangle(area.getPX() + 1, area.getPY() + 1, area.getKX() - 1, area.getKY() - 1)); + } + + private: + SquareType type; + LevelMaker* insideMaker; + +}; + +class MakerQueue : public LevelMaker { + public: + MakerQueue() = default; + MakerQueue(vector _makers) : makers(_makers) {} + + void addMaker(LevelMaker* maker) { + makers.push_back(maker); + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (LevelMaker* maker : makers) + maker->make(builder, area); + } + + private: + vector makers; +}; + +class RandomLocations : public LevelMaker { + public: + RandomLocations(const vector& _insideMakers, + const vector>& _sizes, SquarePredicate* pred, bool _separate = true) + : insideMakers(_insideMakers), sizes(_sizes), predicate(pred), separate(_separate) { + CHECK(insideMakers.size() == sizes.size()); + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + /* if (onAttr) { + string out; + for (int i : Range(area.getPX(), area.getKX())) { + for (int j : Range(area.getPY(), area.getKY())) + out.append(!builder->hasAttrib(Vec2(i, j), *onAttr) ? "0" : "1"); + Debug() << out; + out = ""; + } + }*/ + makeCnt(builder, area, 100); + } + + void makeCnt(Level::Builder* builder, Rectangle area, int tries) const { + vector occupied; + for (int i : All(insideMakers)) { + LevelMaker* maker = insideMakers[i]; + int width = sizes[i].first; + int height = sizes[i].second; + CHECK(width <= area.getW() && height <= area.getH()); + int px; + int py; + int cnt = 10000; + bool ok; + do { + ok = true; + px = area.getPX() + Random.getRandom(area.getW() - width); + py = area.getPY() + Random.getRandom(area.getH() - height); + if (!predicate->apply(builder, Vec2(px, py)) || + !predicate->apply(builder, Vec2(px + width - 1, py)) || + !predicate->apply(builder, Vec2(px + width - 1, py + height - 1)) || + !predicate->apply(builder, Vec2(px, py + height - 1))) + ok = false; + else + if (separate) + for (Rectangle r : occupied) + if (r.intersects(Rectangle(px, py, px + width, py + height))) { + ok = false; + break; + } + } while (!ok && --cnt > 0); + if (cnt == 0) { + if (tries > 0) { + makeCnt(builder, area, tries - 1); + return; + } + else { + Debug(FATAL) << "Failed to find free space for " << (int)sizes.size() << " areas"; + } + } + Rectangle bounds(px, py, px + width, py + height); + occupied.push_back(bounds); + } + CHECK(insideMakers.size() == occupied.size()); + for (int i : All(insideMakers)) + insideMakers[i]->make(builder, occupied[i]); + } + + private: + vector insideMakers; + vector> sizes; + SquarePredicate* predicate; + bool separate; +}; + +class Margin : public LevelMaker { + public: + Margin(int s, LevelMaker* in) : size(s), inside(in) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + CHECK(area.getW() > 2 * size && area.getH() > 2 * size); + inside->make(builder, Rectangle( + area.getPX() + size, + area.getPY() + size, + area.getKX() - size, + area.getKY() - size)); + } + + private: + int size; + LevelMaker* inside; +}; + +class Shrine : public LevelMaker { + public: + Shrine(Deity* _deity, SquareType _floorType, SquarePredicate* wallPred, + SquareType _newWall, LevelMaker* _locationMaker) : deity(_deity), floorType(_floorType), + wallPredicate(wallPred), newWall(_newWall), locationMaker(_locationMaker) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (int i : Range(10009)) { + Vec2 pos = area.randomVec2(); + if (!wallPredicate->apply(builder, pos)) + continue; + int numFloor = 0; + int numWall = 0; + for (Vec2 fl : pos.neighbors8()) { + if (builder->getType(fl) == floorType) + ++numFloor; + if (wallPredicate->apply(builder, fl)) + ++numWall; + } + if (numFloor < 3 || numWall < 5) { + Debug() << "Numbers " << numFloor << " " << numWall; + continue; + } + builder->putSquare(pos, SquareFactory::getAltar(deity), SquareType::ALTAR); + for (Vec2 fl : pos.neighbors8()) + if (wallPredicate->apply(builder, fl)) + builder->putSquare(fl, newWall); + if (locationMaker) + locationMaker->make(builder, Rectangle(pos - Vec2(1, 1), pos + Vec2(2, 2))); + Debug() << "Created a shrine of " << deity->getHabitatString(); + return; + } + Debug() << "Didn't find a good place for the shrine of " << deity->getHabitatString(); + } + + private: + Deity* deity; + SquareType floorType; + SquarePredicate* wallPredicate; + SquareType newWall; + LevelMaker* locationMaker; +}; + +void addAvg(int x, int y, const Table& wys, double& avg, int& num) { + Vec2 pos(x, y); + if (pos.inRectangle(wys.getBounds())) { + avg += wys[pos]; + ++num; + } +} + +Table genNoiseMap(Rectangle area, bool lowMiddle, double varianceMult) { + int width = 1; + while (width < area.getW() - 1 || width < area.getH() - 1) + width *= 2; + ++width; + Table wys(width, width); + wys[0][0] = 1; + wys[0][width - 1] = 1; + wys[width - 1][0] = 1; + wys[width - 1][width - 1] = 1; + wys[(width - 1) / 2][(width - 1) / 2] = lowMiddle ? 0 : 1; + + double variance = 0.5; + double heightDiff = 0.1; + for (int a = width - 1; a >= 2; a /= 2) { + if (a < width - 1) + for (Vec2 pos1 : Rectangle((width - 1) / a, (width - 1) / a)) { + Vec2 pos = pos1 * a; + double avg = (wys[pos] + wys[pos.x + a][pos.y] + wys[pos.x][pos.y + a] + wys[pos.x + a][pos.y + a]) / 4; + wys[pos.x + a / 2][pos.y + a / 2] = + avg + variance * (Random.getDouble() * 2 - 1); + } + for (Vec2 pos1 : Rectangle((width - 1) / a, (width - 1) / a + 1)) { + Vec2 pos = pos1 * a; + double avg = 0; + int num = 0; + addAvg(pos.x + a / 2, pos.y - a / 2, wys, avg, num); + addAvg(pos.x, pos.y, wys, avg, num); + addAvg(pos.x + a, pos.y, wys, avg, num); + addAvg(pos.x + a / 2, pos.y + a / 2, wys, avg, num); + wys[pos.x + a / 2][pos.y] = + avg / num + variance * (Random.getDouble() * 2 - 1); + } + for (Vec2 pos1 : Rectangle((width - 1) / a + 1, (width - 1) / a)) { + Vec2 pos = pos1 * a; + double avg = 0; + int num = 0; + addAvg(pos.x - a / 2, pos.y + a / 2, wys, avg, num); + addAvg(pos.x, pos.y, wys, avg, num); + addAvg(pos.x, pos.y + a , wys, avg, num); + addAvg(pos.x + a / 2, pos.y + a / 2, wys, avg, num); + wys[pos.x][pos.y + a / 2] = + avg / num + variance * (Random.getDouble() * 2 - 1); + } + variance *= varianceMult; + } + Table ret(area); + Vec2 offset(area.getPX(), area.getPY()); + for (Vec2 v : area) { + Vec2 lv((v.x - offset.x) * width / area.getW(), (v.y - offset.y) * width / area.getH()); + ret[v] = wys[lv]; + } + return ret; +} + +vector sortedValues(const Table& t) { + vector values; + for (Vec2 v : t.getBounds()) { + values.push_back(t[v]); + } + sort(values.begin(), values.end()); +/* vector tmp(values); + unique(tmp.begin(), tmp.end()); + string out; + for (double d : values) + out.append(convertToString(d) + " "); + Debug() << (int)tmp.size() << " unique values out of " << (int)values.size() << " " << out;*/ + return values; +} + +class Mountains : public LevelMaker { + public: + Mountains(double r) : ratio(r) {} + + + virtual void make(Level::Builder* builder, Rectangle area) override { + Table wys = genNoiseMap(area, true, 0.7); + vector values = sortedValues(wys); + double cutOffValHill = values[(int)(ratio * double(values.size()))]; + double cutOffVal = values[(int)((0.5 + ratio) / 1.5 * double(values.size()))]; + double cutOffValSnow = values[(int)((3. + ratio) / 4. * double(values.size()))]; + int gCnt = 0, mCnt = 0, hCnt = 0, lCnt = 0; + for (Vec2 v : area) { + builder->setHeightMap(v, wys[v]); + if (wys[v] > cutOffValSnow) { + builder->putSquare(v, SquareType::GLACIER, SquareAttrib::ROAD_CUT_THRU); + ++gCnt; + } + else if (wys[v] > cutOffVal) { + builder->putSquare(v, SquareType::MOUNTAIN, {SquareAttrib::MOUNTAIN, SquareAttrib::ROAD_CUT_THRU}); + ++mCnt; + } + else if (wys[v] > cutOffValHill) { + builder->putSquare(v, SquareType::HILL, SquareAttrib::HILL); + ++hCnt; + } + else { + builder->addAttrib(v, SquareAttrib::LOWLAND); + ++lCnt; + } + } + Debug() << "Terrain distribution " << gCnt << " glacier, " << mCnt << " mountain, " << hCnt << " hill, " << lCnt << " lowland"; + } + + private: + double ratio; +}; + +class Roads : public LevelMaker { + public: + Roads(SquareType roadSquare) : square(roadSquare) {} + + double getValue(Level::Builder* builder, Vec2 pos) { + if (!builder->getSquare(pos)->canEnter(Creature::getDefault()) && + !builder->hasAttrib(pos, SquareAttrib::ROAD_CUT_THRU)) + return ShortestPath::infinity; + SquareType type = builder->getType(pos); + if (type == SquareType::PATH) + return 0.1; + return pow(1 + builder->getHeightMap(pos), 2); + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + vector points; + for (Vec2 v : area) + if (builder->hasAttrib(v, SquareAttrib::CONNECT)) { + points.push_back(v); + Debug() << "Connecting point " << v; + } + for (int ind : Range(1, points.size())) { + Vec2 p1 = points[ind]; + Vec2 p2 = points[ind - 1]; + ShortestPath path(area, + [builder, this, &area](Vec2 pos) { return getValue(builder, pos); }, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(true), p1 ,p2); + Vec2 prev(-1, -1); + for (Vec2 v = p2; v != p1; v = path.getNextMove(v)) { + if (v != p2 && v != p1 && builder->getType(v) != SquareType::PATH) + builder->putSquare(v, SquareType::PATH); + if (prev.x > -1) + builder->getSquare(v)->addTravelDir(prev - v); + builder->getSquare(v)->addTravelDir(path.getNextMove(v) - v); + prev = v; + } + builder->getSquare(p1)->addTravelDir(prev - p1); + } + } + + private: + SquareType square; +}; + +class StartingPos : public LevelMaker { + public: + + StartingPos(SquareType type) : squareType(type) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (Vec2 pos : area) + if (builder->getType(pos) == squareType) + builder->getSquare(pos)->setLandingLink(StairDirection::UP, StairKey::PLAYER_SPAWN); + } + + private: + SquareType squareType; +}; + +class Vegetation : public LevelMaker { + public: + Vegetation(double _ratio, double _density, SquareType _onType, vector _types, vector _probs) + : ratio(_ratio), density(_density), types(_types), probs(_probs), onType(_onType) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + Table wys = genNoiseMap(area, false, 0.9); + vector values = sortedValues(wys); + double cutoff = values[values.size() * ratio]; + for (Vec2 v : area) + if (builder->getType(v) == onType && wys[v] < cutoff && Random.getDouble() <= density) + builder->putSquare(v, chooseRandom(types, probs)); + } + + private: + double ratio; + double density; + vector types; + vector probs; + SquareType onType; +}; + +class LocationMaker : public LevelMaker { + public: + LocationMaker(Location* l) : location(l) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + location->setBounds(area); + builder->addLocation(location); + } + + private: + Location* location; +}; + +class AddAttrib : public LevelMaker { + public: + AddAttrib(SquareAttrib attr, bool _remove = false) : attrib(attr), remove(_remove) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + for (Vec2 v : area) + if (!remove) + builder->addAttrib(v, attrib); + else + builder->removeAttrib(v, attrib); + } + + private: + SquareAttrib attrib; + bool remove; +}; + +class FlockAndLeader : public LevelMaker { + public: + FlockAndLeader(CreatureId _leader, CreatureId _flock, Tribe* _tribe, int _minFlock, int _maxFlock) : + leaderId(_leader), flockId(_flock), tribe(_tribe), minFlock(_minFlock), maxFlock(_maxFlock) { + } + + virtual void make(Level::Builder* builder, Rectangle area) override { + int num = Random.getRandom(minFlock, maxFlock); + PCreature leader = CreatureFactory::fromId(leaderId, tribe); + vector creatures = CreatureFactory::getFlock(num, flockId, leader.get()); + creatures.push_back(std::move(leader)); + for (PCreature& creature : creatures) { + Vec2 pos; + int cnt = 100; + do { + pos = area.randomVec2(); + } while (!builder->canPutCreature(pos, creature.get()) && --cnt > 0); + CHECK(cnt > 0) << "Can't find square for flock"; + builder->putCreature(pos, std::move(creature)); + } + } + + private: + CreatureId leaderId, flockId; + Tribe* tribe; + int minFlock, maxFlock; +}; + +class Stairs : public LevelMaker { + public: + Stairs(StairDirection dir, StairKey k, SquareType _onType, Optional _setAttr = Nothing()) : + direction(dir), key(k), onType(_onType), setAttr(_setAttr) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + vector pos; + for (Vec2 v : area) + if (builder->getType(v) == onType) + pos.push_back(v); + CHECK(pos.size() > 0) << "Couldn't find position for stairs " << area; + SquareType type = direction == StairDirection::DOWN ? SquareType::DOWN_STAIRS : SquareType::UP_STAIRS; + builder->putSquare(pos[Random.getRandom(pos.size())], SquareFactory::getStairs(direction, key), type, setAttr); + } + + private: + StairDirection direction; + StairKey key; + SquareType onType; + Optional setAttr; +}; + +class ShopMaker : public LevelMaker { + public: + ShopMaker(ItemFactory _factory, Tribe* _tribe, int _numItems) + : factory(_factory), tribe(_tribe), numItems(_numItems) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + Location *loc = new Location(); + builder->addLocation(loc); + loc->setBounds(area); + PCreature shopkeeper = CreatureFactory::getShopkeeper(loc, tribe); + vector pos; + for (Vec2 v : area) + if (builder->getSquare(v)->canEnter(shopkeeper.get()) && builder->getType(v) == SquareType::FLOOR) + pos.push_back(v); + builder->putCreature(pos[Random.getRandom(pos.size())], std::move(shopkeeper)); + for (int i : Range(numItems)) { + Vec2 v = pos[Random.getRandom(pos.size())]; + builder->getSquare(v)->dropItem(factory.random()); + } + } + + private: + ItemFactory factory; + Tribe* tribe; + int numItems; +}; + +class LevelExit : public LevelMaker { + public: + LevelExit(SquareType _exitType, Optional _attrib = Nothing()) + : exitType(_exitType), attrib(_attrib) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + builder->putSquare(getRandomExit(area), exitType, attrib); + } + + private: + SquareType exitType; + Optional attrib; +}; + +class Division : public LevelMaker { + public: + Division(double _vRatio, double _hRatio, + LevelMaker* _upperLeft, LevelMaker* _upperRight, LevelMaker* _lowerLeft, LevelMaker* _lowerRight) : + vRatio(_vRatio), hRatio(_hRatio), + upperLeft(_upperLeft), upperRight(_upperRight), lowerLeft(_lowerLeft), lowerRight(_lowerRight) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + int vDiv = area.getPY() + min(area.getH() - 1, max(1, (int) (vRatio * area.getH()))); + int hDiv = area.getPX() + min(area.getW() - 1, max(1, (int) (vRatio * area.getW()))); + if (upperLeft) + upperLeft->make(builder, Rectangle(area.getPX(), area.getPY(), hDiv, vDiv)); + if (upperRight) + upperRight->make(builder, Rectangle(hDiv, area.getPY(), area.getKX(), vDiv)); + if (lowerLeft) + lowerLeft->make(builder, Rectangle(area.getPX(), vDiv, hDiv, area.getKY())); + if (lowerRight) + lowerRight->make(builder, Rectangle(hDiv, vDiv, area.getKX(), area.getKY())); + } + + private: + double vRatio, hRatio; + LevelMaker *upperLeft, *upperRight, *lowerLeft, *lowerRight; +}; + +class Circle : public LevelMaker { + public: + Circle(ItemId _item) : item(_item) {} + + virtual void make(Level::Builder* builder, Rectangle area) override { + Vec2 center = area.middle(); + double r = min(area.getH(), area.getW()) / 2 - 1; + Vec2 lastPos; + for (double a = 0; a < 3.1415 * 2; a += Random.getDouble() * r / 10) { + Vec2 pos = center + Vec2(sin(a) * r, cos(a) * r); + if (pos != lastPos) { + builder->getSquare(pos)->dropItem(ItemFactory::fromId(item)); + lastPos = pos; + } + } + } + + private: + ItemId item; +}; + +LevelMaker* underground() { + MakerQueue* queue = new MakerQueue(); + if (Random.roll(1)) { + LevelMaker* cavern = new Blob(SquareType::PATH); + vector vCavern; + vector> sizes; + int minSize = Random.getRandom(5, 15); + int maxSize = minSize + Random.getRandom(3, 10); + for (int i : Range(sqrt(Random.getRandom(4, 100)))) { + int size = Random.getRandom(minSize, maxSize); + sizes.push_back(make_pair(size, size)); + vCavern.push_back(cavern); + } + queue->addMaker(new RandomLocations(vCavern, sizes, new AlwaysTrue(), false)); + } + switch (Random.getRandom(3)) { + case 1: queue->addMaker(new River(3, chooseRandom({SquareType::WATER, SquareType::MAGMA}))); + break; + case 2:{ + int numLakes = sqrt(Random.getRandom(1, 100)); + SquareType lakeType = chooseRandom({SquareType::WATER, SquareType::MAGMA}, {1, 1}); + vector> sizes; + for (int i : Range(numLakes)) { + int size = Random.getRandom(6, 20); + sizes.emplace_back(size, size); + } + queue->addMaker(new RandomLocations( + vector(numLakes, new Blob(lakeType, Nothing(), SquareAttrib::LAKE)), + sizes, new AlwaysTrue(), false)); + /* Deity* deity = Deity::getDeity( + (lakeType == SquareType::MAGMA) ? DeityHabitat::FIRE : DeityHabitat::WATER); + queue->addMaker(new Shrine(deity, lakeType, + new TypePredicate(lakeType), SquareType::ROCK_WALL, nullptr)); + if (lakeType == SquareType::WATER) { + queue->addMaker(new Creatures(CreatureFactory::singleType(Tribe::monster, CreatureId::KRAKEN), 1, 2, + MonsterAIFactory::monster(), SquareType::WATER)); + } + if (lakeType == SquareType::MAGMA) { + queue->addMaker(new Creatures(CreatureFactory::singleType(Tribe::monster, CreatureId::FIRE_SPHERE), 1, 4, + MonsterAIFactory::monster(), SquareType::MAGMA)); + }*/ + break; + } + default: break; + } + return queue; +} + +LevelMaker* LevelMaker::roomLevel(CreatureFactory cfactory, vector up, vector down) { + map > featureCount { + { SquareType::FOUNTAIN, make_pair(0, 3) }, + { SquareType::CHEST, make_pair(3, 7)}, + { SquareType::TORTURE_TABLE, make_pair(2, 3)}}; + MakerQueue* queue = new MakerQueue(); + queue->addMaker(new Empty(SquareType::BLACK_WALL)); + queue->addMaker(underground()); + queue->addMaker(new RoomMaker(8, 15, 4, 7, SquareType::ROCK_WALL, SquareType::BLACK_WALL)); + queue->addMaker(new Connector({5, 3, 0})); + if (Random.roll(2)) { + Deity* deity = Deity::getDeity(chooseRandom({DeityHabitat::STONE, DeityHabitat::EARTH})); + queue->addMaker(new Shrine(deity, SquareType::FLOOR, + new TypePredicate({SquareType::ROCK_WALL, SquareType::BLACK_WALL}), SquareType::ROCK_WALL, nullptr)); + } + queue->addMaker(new DungeonFeatures(SquareType::FLOOR,featureCount)); + queue->addMaker(new DungeonFeatures(SquareType::PATH, {{ SquareType::ROLLING_BOULDER, make_pair(0, 3) }})); + for (StairKey key : down) + queue->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::FLOOR)); + for (StairKey key : up) + queue->addMaker(new Stairs(StairDirection::UP, key, SquareType::FLOOR)); + queue->addMaker(new Creatures(cfactory, 10, 15, MonsterAIFactory::monster())); + queue->addMaker(new Items(ItemFactory::dungeon(), SquareType::FLOOR, 5, 10)); + return new BorderGuard(queue, SquareType::BLACK_WALL); +} + +LevelMaker* LevelMaker::cryptLevel(CreatureFactory cfactory, vector up, vector down) { + MakerQueue* queue = new MakerQueue(); + map > featureCount { + { SquareType::COFFIN, make_pair(3, 7)}}; + queue->addMaker(new Empty(SquareType::BLACK_WALL)); + queue->addMaker(new RoomMaker(8, 15, 3, 5, SquareType::ROCK_WALL, SquareType::BLACK_WALL)); + queue->addMaker(new Connector({1, 3, 1})); + queue->addMaker(new DungeonFeatures(SquareType::FLOOR,featureCount)); + for (StairKey key : down) + queue->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::FLOOR)); + for (StairKey key : up) + queue->addMaker(new Stairs(StairDirection::UP, key, SquareType::FLOOR)); + queue->addMaker(new Creatures(cfactory, 10, 15, MonsterAIFactory::monster())); + queue->addMaker(new Items(ItemFactory::dungeon(), SquareType::FLOOR, 5, 10)); + return new BorderGuard(queue, SquareType::BLACK_WALL); +} + +MakerQueue* village(CreatureFactory factory, Location* loc) { + MakerQueue* queue = new MakerQueue(); + map > featureCount { + { SquareType::CHEST, make_pair(0, 3) }, + { SquareType::BED, make_pair(0, 3) }}; + queue->addMaker(new LocationMaker(loc)); + queue->addMaker(new Empty(SquareType::GRASS)); + vector insideMakers { + new ShopMaker(ItemFactory::villageShop(), Tribe::elven, 10), + new Creatures(CreatureFactory::singleType(Tribe::elven, CreatureId::PIG), 3, 5, MonsterAIFactory::monster()), + new DungeonFeatures(SquareType::FLOOR, featureCount)}; + queue->addMaker(new Buildings(6, 3, 7, + SquareType::WOOD_WALL, SquareType::FLOOR, SquareType::DOOR, + true, insideMakers)); + queue->addMaker(new Creatures(factory, 10, 20, MonsterAIFactory::stayInLocation(loc), SquareType::GRASS)); + return queue; +} + +MakerQueue* banditCamp() { + MakerQueue* queue = new MakerQueue(); + map > featureCount { + { SquareType::CHEST, make_pair(4, 9) }, + { SquareType::BED, make_pair(2, 4) }}; + queue->addMaker(new Empty(SquareType::GRASS)); + vector insideMakers { + new DungeonFeatures(SquareType::FLOOR, featureCount)}; + queue->addMaker(new Buildings(1, 5, 7, + SquareType::WOOD_WALL, SquareType::FLOOR, SquareType::DOOR, + false, insideMakers, false)); + Location* loc = new Location("bandit hideout", "The bandits have robbed many travelers and townsfolk."); + queue->addMaker(new LocationMaker(loc)); + queue->addMaker(new Creatures(CreatureFactory::singleType(Tribe::goblin, CreatureId::BANDIT), 3, 7, + MonsterAIFactory::stayInLocation(loc), SquareType::GRASS)); + return queue; +} + +LevelMaker* dungeonEntrance(StairKey key, SquareType onType, const string& dungeonDesc) { + MakerQueue* queue = new MakerQueue(); + queue->addMaker(new Stairs(StairDirection::DOWN, key, onType, SquareAttrib::CONNECT)); + queue->addMaker(new LocationMaker(new Location("dungeon entrance", dungeonDesc))); + return new RandomLocations({queue}, {make_pair(1, 1)}, new TypePredicate(SquareType::MOUNTAIN)); +} + +LevelMaker* makeLake() { + MakerQueue* queue = new MakerQueue(); + Location* loc = new Location(); + queue->addMaker(new LocationMaker(loc)); + queue->addMaker(new Blob(SquareType::WATER, SquareType::SAND, SquareAttrib::LAKE)); + // queue->addMaker(new Creatures(CreatureFactory::singleType(CreatureId::VODNIK), 2, 5, MonsterAIFactory::stayInLocation(loc))); + return queue; +} + +LevelMaker* LevelMaker::towerLevel(Optional down, Optional up) { + MakerQueue* queue = new MakerQueue(); + queue->addMaker(new Empty(up ? SquareType::ROCK_WALL : SquareType::LOW_ROCK_WALL)); + queue->addMaker(new Margin(1, new Empty(SquareType::FLOOR))); + queue->addMaker(new Margin(1, new AddAttrib(SquareAttrib::ROOM))); + queue->addMaker(new AddAttrib(SquareAttrib::ROAD_CUT_THRU, true)); + LevelMaker* downStairs = (down) ? new Stairs(StairDirection::DOWN, *down, SquareType::FLOOR) : nullptr; + LevelMaker* upStairs = (up) ? new Stairs(StairDirection::UP, *up, SquareType::FLOOR) : nullptr; + queue->addMaker(new Division(0.5, 0.5, upStairs, nullptr, nullptr, downStairs)); + return queue; +} + + +LevelMaker* LevelMaker::topLevel( + CreatureFactory forrestCreatures, + CreatureFactory villageCreatures, + Location* villageLocation, + CreatureFactory cementaryCreatures, + CreatureFactory goblins, + CreatureFactory pyramidCreatures) { + MakerQueue* queue = new MakerQueue(); + vector vegetationLow { SquareType::CANIF_TREE, SquareType::BUSH }; + vector vegetationHigh { SquareType::DECID_TREE, SquareType::BUSH }; + vector probs { 2, 1 }; + LevelMaker* lake = makeLake(); + int numLakes = 1;//Random.getRandom(1, 2); + vector> subSizes; + vector subMakers; + for (int i : Range(numLakes)) { + subSizes.emplace_back(Random.getRandom(60, 120), Random.getRandom(60, 120)); + subMakers.push_back(lake); + } + MakerQueue* elvenVillage = village(villageCreatures, villageLocation); + elvenVillage->addMaker(new StartingPos(SquareType::GRASS)); + subMakers.push_back(elvenVillage); + subSizes.emplace_back(30, 20); + for (int i : Range(Random.getRandom(3))) { + subMakers.push_back(banditCamp()); + subSizes.emplace_back(12,8); + } + int numCementeries = 1; + for (int i : Range(numCementeries)) { + Location* loc = new Location("old cemetery", "Terrible evil is said to be lurking there."); + subMakers.push_back(new MakerQueue({ + new LocationMaker(loc), + new Margin(1, new Buildings(1, 2, 3, SquareType::ROCK_WALL, SquareType::FLOOR, SquareType::DOOR, false, {}, false)), + new DungeonFeatures(SquareType::GRASS, {{ SquareType::GRAVE, make_pair(5, 15) }}), + new Stairs(StairDirection::DOWN, StairKey::CRYPT, SquareType::FLOOR), +// new Creatures(cementaryCreatures, 2, 5, MonsterAIFactory::stayInLocation(loc)), + })); + subSizes.emplace_back(10, 10); + } + int numSheepFlocks = 0; + for (int i : Range(numSheepFlocks)) { + subMakers.push_back(new FlockAndLeader(CreatureId::ELF, CreatureId::SHEEP, Tribe::elven, 5, 10)); + subSizes.emplace_back(15, 15); + } + int numStoneCircles = Random.getRandom(2, 7); + /* for (int i : Range(numStoneCircles)) { + subMakers.push_back(new Circle(ItemId::BOULDER)); + int size = Random.getRandom(14, 32); + subSizes.emplace_back(size, size); + }*/ + MakerQueue* pyramid = new MakerQueue(); + pyramid->addMaker(new LocationMaker(new Location("ancient pyramid", "Terrible evil is said to be lurking there."))); + pyramid->addMaker(new Margin(1, pyramidLevel(pyramidCreatures, {StairKey::PYRAMID}, {}))); + subMakers.push_back(pyramid); + subSizes.emplace_back(17, 17); + queue->addMaker(new Empty(SquareType::GRASS)); + queue->addMaker(new Mountains(0.5)); + // queue->addMaker(new MountainRiver(30, '=')); + queue->addMaker(new Vegetation(0.7, 0.5, SquareType::GRASS, vegetationLow, probs)); + queue->addMaker(new Vegetation(0.4, 0.5, SquareType::HILL, vegetationHigh, probs)); + queue->addMaker(new Vegetation(0.2, 0.3, SquareType::MOUNTAIN, vegetationHigh, probs)); + queue->addMaker(new Margin(100, + new RandomLocations(subMakers, subSizes, new AttribPredicate(SquareAttrib::LOWLAND)))); + MakerQueue* tower = new MakerQueue(); + tower->addMaker(towerLevel(StairKey::TOWER, Nothing())); + tower->addMaker(new LocationMaker(Location::towerTopLocation())); +// queue->addMaker(new Margin(100, +// new RandomLocations({tower}, {make_pair(4, 4)}, new AttribPredicate(SquareAttrib::MOUNTAIN)))); + queue->addMaker(new Margin(100, dungeonEntrance(StairKey::DWARF, SquareType::MOUNTAIN, "Our enemies the dwarves are living there."))); + // queue->addMaker(new Margin(100, dungeonEntrance(StairKey::GOBLIN, SquareType::MOUNTAIN, "Our enemies the goblins are living there."))); + queue->addMaker(new Roads(SquareType::PATH)); + /*Deity* deity = Deity::getDeity(chooseRandom({DeityHabitat::STARS, DeityHabitat::TREES})); + queue->addMaker(new Shrine(deity, SquareType::PATH, + new TypePredicate({SquareType::GRASS, SquareType::DECID_TREE, SquareType::BUSH}), SquareType::WOOD_WALL, new LocationMaker(new Location("shrine", "It is dedicated to the god " + deity->getName()))));*/ + queue->addMaker(new Creatures(forrestCreatures, 30, 50, MonsterAIFactory::wildlifeNonPredator())); + queue->addMaker(new Items(ItemFactory::mushrooms(), SquareType::GRASS, 30, 60)); + return new BorderGuard(queue); +} + +LevelMaker* LevelMaker::goblinTownLevel(CreatureFactory cfactory, vector up, vector down) { + MakerQueue* queue = new MakerQueue(); + map > featureCount { + { SquareType::CHEST, make_pair(4, 8) }, + { SquareType::TORTURE_TABLE, make_pair(4, 8) }}; + queue->addMaker(new Empty(SquareType::ROCK_WALL)); + LevelMaker* cavern = new Blob(SquareType::PATH); + vector vCavern; + vector> sizes; + for (int i : Range(40)) { + sizes.push_back(make_pair(Random.getRandom(5, 10), Random.getRandom(5, 10))); + vCavern.push_back(cavern); + } + queue->addMaker(new RandomLocations(vCavern, sizes, new AlwaysTrue(), false)); + queue->addMaker(new RoomMaker(5, 8, 4, 7, SquareType::ROCK_WALL, Nothing(), + new ShopMaker(ItemFactory::goblinShop(), Tribe::goblin, 10), false)); + queue->addMaker(new Connector({1, 0, 0})); + for (StairKey key : down) + queue->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::FLOOR)); + for (StairKey key : up) + queue->addMaker(new Stairs(StairDirection::UP, key, SquareType::FLOOR)); + queue->addMaker(new DungeonFeatures(SquareType::FLOOR, featureCount)); + queue->addMaker(new Creatures(cfactory, 10, 15, MonsterAIFactory::monster())); + return new BorderGuard(queue, SquareType::BLACK_WALL); +} + +LevelMaker* LevelMaker::mineTownLevel(CreatureFactory cfactory, vector up, vector down) { + MakerQueue* queue = new MakerQueue(); + map > featureCount { + { SquareType::CHEST, make_pair(4, 8) }, + { SquareType::BED, make_pair(4, 8) }}; + queue->addMaker(new Empty(SquareType::ROCK_WALL)); + LevelMaker* cavern = new Blob(SquareType::PATH); + vector vCavern; + vector> sizes; + for (int i : Range(20)) { + sizes.push_back(make_pair(Random.getRandom(5, 25), Random.getRandom(5, 25))); + vCavern.push_back(cavern); + } + queue->addMaker(new RandomLocations(vCavern, sizes, new AlwaysTrue(), false)); + queue->addMaker(new RoomMaker(6, 12, 4, 7, SquareType::ROCK_WALL, Nothing(), + new ShopMaker(ItemFactory::dwarfShop(), Tribe::dwarven, 10), false)); + queue->addMaker(new Connector({1, 0, 0})); + for (StairKey key : down) + queue->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::FLOOR)); + for (StairKey key : up) + queue->addMaker(new Stairs(StairDirection::UP, key, SquareType::FLOOR)); + queue->addMaker(new DungeonFeatures(SquareType::FLOOR, featureCount)); + queue->addMaker(new Creatures(cfactory, 10, 15, MonsterAIFactory::monster())); + return new BorderGuard(queue, SquareType::BLACK_WALL); +} + +LevelMaker* LevelMaker::pyramidLevel(CreatureFactory cfactory, vector up, vector down) { + MakerQueue* queue = new MakerQueue(); + queue->addMaker(new Empty(SquareType::YELLOW_WALL)); + queue->addMaker(new AddAttrib(SquareAttrib::NO_DIG)); + queue->addMaker(new Margin(1, new AddAttrib(SquareAttrib::NO_DIG, true))); + queue->addMaker(new RoomMaker(2, 5, 4, 6, SquareType::YELLOW_WALL, SquareType::YELLOW_WALL, nullptr, true)); + for (StairKey key : down) + queue->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::FLOOR)); + for (StairKey key : up) + queue->addMaker(new Stairs(StairDirection::UP, key, SquareType::FLOOR)); + if (down.size() == 0) + queue->addMaker(new LevelExit(SquareType::PATH)); + else + queue->addMaker(new Creatures(cfactory, 3, 5, MonsterAIFactory::monster())); + queue->addMaker(new Connector({5, 3, 0})); + return queue; +} + +LevelMaker* LevelMaker::collectiveLevel(vector up, vector down) { + MakerQueue* queue = new MakerQueue(); + queue->addMaker(new Empty(SquareType::ROCK_WALL)); + queue->addMaker(underground()); + vector makers; + vector> sizes; + for (StairKey key : up) { + MakerQueue* area = new MakerQueue(); + area->addMaker(new Blob(SquareType::PATH)); + area->addMaker(new Stairs(StairDirection::UP, key, SquareType::PATH)); + makers.push_back(area); + sizes.emplace_back(10, 10); + } + for (StairKey key : down) { + MakerQueue* area = new MakerQueue(); + area->addMaker(new Blob(SquareType::PATH)); + area->addMaker(new Stairs(StairDirection::DOWN, key, SquareType::PATH)); + makers.push_back(area); + sizes.emplace_back(10, 10); + } + MakerQueue* startPos = new MakerQueue(); + startPos->addMaker(new Blob(SquareType::PATH)); + startPos->addMaker(new StartingPos(SquareType::PATH)); + makers.push_back(startPos); + sizes.emplace_back(10, 10); + for (int i : Range(Random.getRandom(4, 7))) { + makers.push_back(new Blob(SquareType::GOLD_ORE)); + sizes.emplace_back(Random.getRandom(5, 10), Random.getRandom(5, 10)); + } + queue->addMaker(new RandomLocations(makers, sizes, new AlwaysTrue())); + return new BorderGuard(queue, SquareType::BLACK_WALL); +} diff --git a/level_maker.h b/level_maker.h new file mode 100644 index 000000000..d7c62ac6e --- /dev/null +++ b/level_maker.h @@ -0,0 +1,32 @@ +#ifndef _LEVEL_MAKER_H +#define _LEVEL_MAKER_H + +#include "util.h" +#include "level.h" +#include "creature_factory.h" + + +class LevelMaker { + public: + virtual void make(Level::Builder* builder, Rectangle area) = 0; + + static LevelMaker* roomLevel(CreatureFactory cfactory, vector up, vector down); + static LevelMaker* cryptLevel(CreatureFactory cfactory, vector up, vector down); + static LevelMaker* topLevel( + CreatureFactory forrest, + CreatureFactory village, + Location* villageLocation, + CreatureFactory cementaryCreatures, + CreatureFactory goblins, + CreatureFactory pyramid); + static LevelMaker* mineTownLevel(CreatureFactory cfactory, vector up, vector down); + static LevelMaker* goblinTownLevel(CreatureFactory cfactory, vector up, vector down); + + static LevelMaker* pyramidLevel(CreatureFactory, vector up, vector down); + static LevelMaker* towerLevel(Optional down, Optional up); + static Vec2 getRandomExit(Rectangle rect); + + static LevelMaker* collectiveLevel(vector up, vector down); +}; + +#endif diff --git a/location.cpp b/location.cpp new file mode 100644 index 000000000..904f6e3e2 --- /dev/null +++ b/location.cpp @@ -0,0 +1,61 @@ +#include "stdafx.h" + +Location::Location(const string& _name, const string& desc) : name(_name), description(desc), bounds(-1, -1, 1, 1) { +} + +Location::Location() : bounds(-1, -1, 1, 1) {} + +string Location::getName() const { + return *name; +} + +string Location::getDescription() const { + return *description; +} + +bool Location::hasName() const { + return name; +} + +Rectangle Location::getBounds() const { + CHECK(bounds.getPX() > -1) << "Location bounds not initialized"; + return bounds; +} + +void Location::setBounds(Rectangle b) { + bounds = b; +} + +void Location::setLevel(const Level* l) { + level = l; +} + +const Level* Location::getLevel() const { + return level; +} + +class TowerTopLocation : public Location { + public: + +/* virtual void onCreature(Creature* cr) override { + if (!cr->isPlayer()) + return; + Player* c = dynamic_cast(cr); + if (!entered.count(c) && !c->isBlind()) { + for (Vec2 v : c->getLevel()->getBounds()) + if ((v - c->getPosition()).lengthD() < 300 && !c->getLevel()->getSquare(v)->isCovered()) + c->remember(v, c->getLevel()->getSquare(v)->getViewObject()); + c->privateMessage("You stand at the top of a very tall stone tower."); + c->privateMessage("You see distant land in all directions."); + entered.insert(c); + } + }*/ + + private: + unordered_set entered; +}; + +Location* Location::towerTopLocation() { + return new TowerTopLocation(); +} + diff --git a/location.h b/location.h new file mode 100644 index 000000000..fea5acb36 --- /dev/null +++ b/location.h @@ -0,0 +1,29 @@ +#ifndef _LOCATION_H +#define _LOCATION_H + +#include "util.h" + +class Location { + public: + Location(const string& name, const string& description); + Location(); + string getName() const; + string getDescription() const; + bool hasName() const; + Rectangle getBounds() const; + void setBounds(Rectangle); + void setLevel(const Level*); + const Level* getLevel() const; + + virtual void onCreature(Creature* c) {} + + static Location* towerTopLocation(); + + private: + Optional name; + Optional description; + Rectangle bounds; + const Level* level; +}; + +#endif diff --git a/logging_view.h b/logging_view.h new file mode 100644 index 000000000..a0235b962 --- /dev/null +++ b/logging_view.h @@ -0,0 +1,66 @@ +#ifndef _LOGGING_VIEW +#define _LOGGING_VIEW + + +template +class LoggingView : public T { + public: + LoggingView(ofstream& of) : output(of) { + } + + virtual void close() override { + output.close(); + T::close(); + } + + virtual Action getAction() override { + Action res = T::getAction(); + output << "getAction " << res << endl; + output.flush(); + return res; + } + + virtual Optional chooseFromList(const string& title, const vector& options, int index) override { + auto res = T::chooseFromList(title, options, index); + output << "chooseFromList "; + if (res) + output << *res << endl; + else + output << "nothing" << endl; + output.flush(); + return res; + } + + virtual Optional chooseDirection(const string& message) override { + auto res = T::chooseDirection(message); + output << "chooseDirection "; + if (res) + output << res->x << "," << res->y << endl; + else + output << "nothing" << endl; + output.flush(); + return res; + } + + virtual bool yesOrNoPrompt(const string& message) override { + auto res = T::yesOrNoPrompt(message); + output << "yesOrNoPrompt " << res << endl; + output.flush(); + return res; + } + + virtual Optional getNumber(const string& title, int max) override { + auto res = T::getNumber(title, max); + output << "getNumber "; + if (res) + output << *res << endl; + else + output << "nothing" << endl; + output.flush(); + return res; + } + private: + ofstream& output; +}; + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 000000000..56cafd132 --- /dev/null +++ b/main.cpp @@ -0,0 +1,71 @@ +#include "stdafx.h" + +using namespace std; + +int main(int argc, char* argv[]) { + View* view; + ifstream input; + ofstream output; + string lognamePref = "log"; + Debug::init(); + int seed = time(0); + bool dwarf = false; + if (argc == 2 && argv[1][0] == 'd') + dwarf = true; + else + if (argc == 2 && argv[1][0] != 'l') { + seed = convertFromString(argv[1]); + argc = 1; + } + if (argc == 1 || dwarf) { + Random.init(seed); + string fname(lognamePref); + fname += convertToString(seed); + output.open(fname); + CHECK(output.is_open()); + Debug() << "Writing to " << fname; + view = View::createLoggingView(output); + } else { + string fname = argv[1]; + Debug() << "Reading from " << fname; + seed = convertFromString(fname.substr(lognamePref.size())); + Random.init(seed); + input.open(fname); + CHECK(input.is_open()); + view = View::createReplayView(input); + } + Quest::initialize(); + Tribe::init(); + Item::identifyEverything(); + NameGenerator::init("first_names.txt", "aztec_names.txt", "creatures.txt", + "artifacts.txt", "world.txt", "town_names.txt", "dwarfs.txt", "gods.txt", "demons.txt", "dogs.txt"); + ItemFactory::init(); + bool modelReady = false; + messageBuffer.initialize(view); + string heroName = NameGenerator::firstNames.getNext(); + view->initialize(); + auto choice = view->chooseFromList("Welcome to KeeperRL.", { "Keeper mode", "Adventure mode"}); + if (!choice) + exit(0); + dwarf = (choice == 1); + Model* model; +#ifndef WINDOWS + thread t = !dwarf ? (thread([&] { model = Model::collectiveModel(view, 5); modelReady = true; })) : + (thread([&] { model = Model::heroModel(view, heroName); modelReady = true; })); + view->displaySplash(modelReady); +#else + modelReady = true; + view->displaySplash(modelReady); + model = (!dwarf ? Model::collectiveModel(view, 5) : Model::heroModel(view, heroName)); +#endif + int var = 0; + view->setTimeMilli(0); + while (1) { + if (model->isTurnBased()) + model->update(var++); + else + model->update(double(view->getTimeMilli()) / 300); + } + return 0; +} + diff --git a/map_layout.cpp b/map_layout.cpp new file mode 100644 index 000000000..6f51fa85e --- /dev/null +++ b/map_layout.cpp @@ -0,0 +1,243 @@ +#include "stdafx.h" +#include "map_layout.h" + +MapLayout::MapLayout( + int screenW, int screenH, + int leftM, int topM, int rightM, int bottomM, + vector l) :screenWidth(screenW), screenHeight(screenH), + leftMargin(leftM), topMargin(topM), rightMargin(rightM), bottomMargin(bottomM), layers(l) {} + +Rectangle MapLayout::getBounds() { + return Rectangle(leftMargin, topMargin, screenWidth - rightMargin, screenHeight - bottomMargin); +} + +void MapLayout::updateScreenSize(int width, int height) { + screenWidth = width; + screenHeight = height; +} + +vector MapLayout::getLayers() const { + return layers; +} + +class GridLayout : public MapLayout { + public: + GridLayout( + int screenW, int screenH, + int sW, int sH, + int leftM, int topM, + int rightM, int bottomM, + int marg, vector layers) : MapLayout(screenW, screenH, leftM, topM, rightM, bottomM, layers) + , squareW(sW), squareH(sH), boxMargin(marg) {} + + virtual double squareHeight(Vec2 mapPos) override { + return squareH; + } + + virtual double squareWidth(Vec2 mapPos) override { + return squareW; + } + + virtual Vec2 projectOnScreen(Vec2 mapPos, double height) override { + return getBounds().middle() + (mapPos).mult(Vec2(squareW, squareH)) - center; + } + + virtual Vec2 projectOnMap(Vec2 screenPos) override { + Vec2 pos = (screenPos + center - getBounds().middle()).div(Vec2(squareW, squareH)); + return pos; + } + + virtual void updatePlayerPos(Vec2 pos) override { + center = pos; + } + + virtual vector getAllTiles() override { + vector ret; + Rectangle grid(getBounds().getW() / squareW, getBounds().getH() / squareH); + for (Vec2 v : grid) + ret.push_back(v + center.div(Vec2(squareW, squareH)) - grid.middle()); + return ret; + } + + private: + Vec2 center; + int squareW; + int squareH; + int boxMargin; +}; + +MapLayout* MapLayout::gridLayout(int screenW, int screenH, + int squareWidth, int squareHeight, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin, + int boxMargin, vector layers) { + return new GridLayout( + screenW, screenH, squareWidth, squareHeight, leftMargin, topMargin, rightMargin, bottomMargin, boxMargin, + layers); +} + +class WorldLayout : public GridLayout { + public: + WorldLayout(int screenW, int screenH, int leftM, int topM, int rightM, int bottomM) + : GridLayout(screenW, screenH, 1, 1, leftM, topM, rightM, bottomM, 1, + {ViewLayer::FLOOR, ViewLayer::CREATURE}) {} + + virtual Vec2 projectOnScreen(Vec2 mapPos, double height) override { + return mapPos; + } +}; + +MapLayout* MapLayout::worldLayout(int screenW, int screenH, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin) { + return new WorldLayout( + screenW, screenH, leftMargin, topMargin, rightMargin, bottomMargin); +} + +double multY = 75; +double multX = 36; +double offsetY = 1750; +double offsetX = 0; +double camOffset = 30; +double camHeight = 15; + +using sf::Keyboard; + +class TppLayout : public MapLayout { + public: + TppLayout(int screenW, int screenH, + int leftM, int topM, int rightM, int bottomM, int sW, int sH) : MapLayout(screenW, screenH, + leftM, topM, rightM, bottomM, allLayers), squareW(sW), squareH(sH) {} + + virtual double squareWidth(Vec2 mapPos) override { + VecD dir = translateToScreen(mapPos - center); + return max(5., min(20., 160. * (atan2(camOffset - dir.y, camHeight) - atan2(camOffset - dir.y - 10, camHeight)))); + } + + virtual double squareHeight(Vec2 mapPos) override { + return squareWidth(mapPos); + } + + virtual Vec2 projectOnMap(Vec2 screenPos) override { + return screenPos; + } + + virtual Vec2 projectOnScreen(Vec2 mapPos, double height) override { + VecD dir = translateToScreen(mapPos - center); + return getBounds().middle() + Vec2(offsetX + multX * atan2(dir.x, camOffset - dir.y) * squareW, + offsetY - multY * atan2(camOffset - dir.y, camHeight - height * 0) * squareH); + } + + virtual vector getAllTiles() override { + int horizon = 50; + vector ret; + for (Vec2 v : Rectangle(center.x - horizon, center.y - horizon, center.x + horizon, center.y + horizon)) + if (projectOnScreen(v, 0).inRectangle(getBounds())) + ret.push_back(v); + return ret; + } + + struct VecD { + VecD(double a, double b) : x(a), y(b) {} + VecD(Vec2 v) : x(v.x), y(v.y) {} + double x, y; + }; + + VecD translateToScreen(Vec2 dir) { + CHECK(contains(Vec2::directions8(), facing)) << facing; + double diagDist = 1 / sqrt(2); + if (facing == Vec2(0, -1)) + return dir; + if (facing == Vec2(0, 1)) + return VecD(-dir.x, -dir.y); + if (facing == Vec2(1, 0)) + return VecD(dir.y, -dir.x); + if (facing == Vec2(-1, 0)) + return VecD(-dir.y, dir.x); + if (facing == Vec2(-1, -1)) + return VecD((-dir.y + dir.x) * diagDist, (dir.y + dir.x) * diagDist); + if (facing == Vec2(1, -1)) + return VecD((dir.y + dir.x) * diagDist, (dir.y - dir.x) * diagDist); + if (facing == Vec2(1, 1)) + return VecD((dir.y - dir.x) * diagDist, (-dir.y - dir.x) * diagDist); + return VecD((-dir.y - dir.x) * diagDist, (-dir.y + dir.x) * diagDist); + } + + void printTrans() { + Debug() << "xMult = " << multX << " yMult = " << multY << " xOffset = " << offsetX << " yOffset = " << offsetY << " camOffset = " << camOffset << " camHeight = " << camHeight; + } + + vector dirs { + Vec2(0, -1), Vec2(1, -1), Vec2(1, 0), Vec2(1, 1), Vec2(0, 1), Vec2(-1, 1), Vec2(-1, 0), Vec2(-1, -1)}; + + Vec2 facingMove(Vec2 f, int dir) { + auto elem = findElement(dirs, f); + CHECK(elem); + return dirs[(*elem + dir + dirs.size()) % dirs.size()]; + } + + + virtual Optional overrideAction(const sf::Event::KeyEvent& key) override { + switch (key.code) { + case Keyboard::Up: + case Keyboard::Numpad8: return Action( + key.control ? ActionId::TRAVEL : key.alt ? ActionId::FIRE : ActionId::MOVE, facing); + case Keyboard::Right: + case Keyboard::Numpad6: facing = facingMove(facing, 1); return Action(ActionId::IDLE); + case Keyboard::Down: + case Keyboard::Numpad2: facing = facingMove(facing, 4); return Action(ActionId::IDLE); + case Keyboard::Left: + case Keyboard::Numpad4: facing = facingMove(facing, -1); return Action(ActionId::IDLE); + case Keyboard::Numpad9: + case Keyboard::Numpad7: + case Keyboard::Numpad3: + case Keyboard::Numpad1: return Action(ActionId::IDLE); + case Keyboard::T: return Action(ActionId::THROW_DIR, facing); + /* case Keyboard::F1: multX *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F2: multX *= 0.9; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F3: multY *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F4: multY *= 0.9; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F5: offsetX *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F6: offsetX *= 0.9; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F7: offsetY *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F8: offsetY *= 0.9; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F9: camOffset *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F10: camOffset *= 0.9; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F11: camHeight *= 1.1; printTrans(); return Action(ActionId::IDLE); + case Keyboard::F12: camHeight *= 0.9; printTrans(); return Action(ActionId::IDLE);*/ + default: return Nothing(); + } +} + + virtual void updatePlayerPos(Vec2 pos) override { + if (contains(Vec2::directions8(), pos - center)) { + Vec2 movement = pos - center; + if (movement == lastMovement) { + ++cnt; + if (cnt > 10) { + facing = movement; + cnt = 0; + } + } else { + lastMovement = movement; + cnt = 0; + } + } + center = pos; + } + + private: + int squareW; + int squareH; + Vec2 center; + Vec2 facing = Vec2(0, -1); + int cnt = 0; + Vec2 lastMovement; +}; + +MapLayout* MapLayout::tppLayout(int screenW, int screenH, + int squareWidth, int squareHeight, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin) { + return new TppLayout(screenW, screenH, leftMargin, topMargin, rightMargin, bottomMargin, squareWidth, squareHeight); +} diff --git a/map_layout.h b/map_layout.h new file mode 100644 index 000000000..8eb4edf23 --- /dev/null +++ b/map_layout.h @@ -0,0 +1,55 @@ +#ifndef _MAP_LAYOUT +#define _MAP_LAYOUT + +#include + +#include "util.h" +#include "enums.h" + +class MapLayout { + public: + MapLayout(int screenWidth, int screenHeight, int leftMargin, int topMargin, int rightMargin, int bottomMargin, + vector layers); + Rectangle getBounds(); + void updateScreenSize(int width, int height); + + vector getLayers() const; + + virtual double squareWidth(Vec2 mapPos) = 0; + virtual double squareHeight(Vec2 mapPos) = 0; + virtual Vec2 projectOnScreen(Vec2 mapPos, double height) = 0; + virtual Vec2 projectOnMap(Vec2 screenPos) = 0; + virtual vector getAllTiles() = 0; + virtual void updatePlayerPos(Vec2) = 0; + virtual Optional overrideAction(const sf::Event::KeyEvent&) { return Nothing(); }; + + static MapLayout* gridLayout( + int screenW, int screenH, + int squareWidth, int squareHeight, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin, + int boxMargin, vector layers); + + static MapLayout* tppLayout( + int screenW, int screenH, + int squareWidth, int squareHeight, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin); + + static MapLayout* worldLayout(int screenW, int screenH, + int leftMargin, int topMargin, + int rightMargin, int bottomMargin); + + protected: + int screenWidth; + int screenHeight; + int leftMargin; + int topMargin; + int rightMargin; + int bottomMargin; + + vector layers; +}; + + +#endif diff --git a/map_memory.cpp b/map_memory.cpp new file mode 100644 index 000000000..50c19cdac --- /dev/null +++ b/map_memory.cpp @@ -0,0 +1,22 @@ +#include "stdafx.h" + +void MapMemory::addObject(Vec2 pos, const ViewObject& obj) { + table[pos].insert(obj); +} + +void MapMemory::clearSquare(Vec2 pos) { + table.erase(pos); +} + +bool MapMemory::hasViewIndex(Vec2 pos) const { + return table.count(pos); +} + +ViewIndex MapMemory::getViewIndex(Vec2 pos) const { + TRY(return table.at(pos), "No view index at " << pos); +} + +const MapMemory& MapMemory::empty() { + static MapMemory mem; + return mem; +} diff --git a/map_memory.h b/map_memory.h new file mode 100644 index 000000000..b276b7d6d --- /dev/null +++ b/map_memory.h @@ -0,0 +1,20 @@ +#ifndef _MEMORY_H +#define _MEMORY_H + +#include "view_object.h" +#include "view_index.h" +#include "util.h" + +class MapMemory { + public: + void addObject(Vec2 pos, const ViewObject& obj); + void clearSquare(Vec2 pos); + bool hasViewIndex(Vec2 pos) const; + ViewIndex getViewIndex(Vec2 pos) const; + static const MapMemory& empty(); + + private: + unordered_map table; +}; + +#endif diff --git a/markov_chain.cpp b/markov_chain.cpp new file mode 100644 index 000000000..3131bfa01 --- /dev/null +++ b/markov_chain.cpp @@ -0,0 +1,30 @@ +#include "stdafx.h" + +template +MarkovChain::MarkovChain(T s, map>> t) : state(s), transitions(t) { + for (auto elem : transitions) { + double sum = 0; + for (auto trans : elem.second) + sum += trans.second; + CHECK(sum <= 1); + if (sum < 1) + transitions[elem.first].emplace_back(elem.first, 1 - sum); + } +} + +template +T MarkovChain::getState() const { + return state; +} + +template +void MarkovChain::setState(T s) { + state = s; +} + +template +void MarkovChain::update() { + state = chooseRandom(transitions.at(state)); +} + +template class MarkovChain; diff --git a/markov_chain.h b/markov_chain.h new file mode 100644 index 000000000..4fc6b815f --- /dev/null +++ b/markov_chain.h @@ -0,0 +1,19 @@ +#ifndef _MARKOV_CHAIN +#define _MARKOV_CHAIN + + +template +class MarkovChain { + public: + MarkovChain(T initialState, map>>); + + T getState() const; + void setState(T); + void update(); + + private: + T state; + map>> transitions; +}; + +#endif diff --git a/message_buffer.cpp b/message_buffer.cpp new file mode 100644 index 000000000..c0f608fa6 --- /dev/null +++ b/message_buffer.cpp @@ -0,0 +1,54 @@ +#include "stdafx.h" + +using namespace std; + +MessageBuffer messageBuffer; + +void correct(string& msg) { + if (islower(msg[0])) + msg[0] = toupper(msg[0]); + if (msg.size() > 1 && msg[0] == '\"' && islower(msg[1])) + msg[1] = toupper(msg[1]); + if (msg.back() != '.' && msg.back() != '?' && msg.back() != '!' && msg.back() != '\"') + msg.append("."); +} + +void MessageBuffer::addMessage(string msg) { + Debug() << "MSG " << msg; + CHECK(view != nullptr) << "Message buffer not initialized."; + if (msg == "") + return; + bool imp = isImportant(msg); + if (imp) + removeImportant(msg); + correct(msg); + if (imp) + view->addImportantMessage(msg); + else { + view->addMessage(msg); + } + messages.push_back(msg); +} + +void MessageBuffer::initialize(View* view) { + this->view = view; +} + +void MessageBuffer::showHistory() { + view->presentList("Messages:", messages, true); +} + +const static string importantPref = "IMPORTANT"; + +string MessageBuffer::important(const string& msg) { + return importantPref + msg; +} + +bool MessageBuffer::isImportant(const string& msg) { + return msg.size() > importantPref.size() && msg.substr(0, importantPref.size()) == importantPref; +} + +void MessageBuffer::removeImportant(string& msg) { + CHECK(isImportant(msg)); + msg = msg.substr(importantPref.size()); +} diff --git a/message_buffer.h b/message_buffer.h new file mode 100644 index 000000000..0a3bc78c8 --- /dev/null +++ b/message_buffer.h @@ -0,0 +1,27 @@ +#ifndef _MESSAGE_BUFFER_H +#define _MESSAGE_BUFFER_H + +#include +#include + +#include "util.h" +#include "view.h" + +class MessageBuffer { + public: + void addMessage(string msg); + void initialize(View* view); + void showHistory(); + + static string important(const string& msg); + + private: + static bool isImportant(const string& msg); + static void removeImportant(string& msg); + vector messages; + View* view; +}; + +extern MessageBuffer messageBuffer; + +#endif diff --git a/minion_equipment.cpp b/minion_equipment.cpp new file mode 100644 index 000000000..b27b2c1e1 --- /dev/null +++ b/minion_equipment.cpp @@ -0,0 +1,46 @@ +#include "stdafx.h" + +Optional MinionEquipment::getEquipmentType(const Item* it) { + if (it->getType() == ItemType::WEAPON) + return MinionEquipment::WEAPON; + if (it->getType() == ItemType::ARMOR) { + if (it->getEquipmentSlot() == EquipmentSlot::BODY_ARMOR) + return MinionEquipment::BODY_ARMOR; + if (it->getEquipmentSlot() == EquipmentSlot::HELMET) + return MinionEquipment::HELMET; + } + if (it->getType() == ItemType::TOOL && it->getEffectType() == EffectType::HEAL) + return MinionEquipment::FIRST_AID_KIT; + return Nothing(); +} + +bool MinionEquipment::isItemUseful(const Item* it) { + return getEquipmentType(it); +} + +bool MinionEquipment::needs(const Creature* c, const Item* it) { + EquipmentType type = *getEquipmentType(it); + return (!equipmentMap.count(make_pair(c, type)) && + (c->canEquip(it) || (type == MinionEquipment::FIRST_AID_KIT && !c->isUndead() && + c->getEquipment().getItems(Item::effectPredicate(EffectType::HEAL)).empty()))); +} + +bool MinionEquipment::needsItem(const Creature* c, const Item* it) { + if (owners.count(it)) { + if (owners.at(it) == c) + return true; + if (owners.at(it)->isDead()) + owners.erase(it); + else + return false; + } + if (!getEquipmentType(it) || !c->isHumanoid()) + return false; + if (needs(c, it)) { + owners[it] = c; + equipmentMap[make_pair(c, *getEquipmentType(it))] = it; + return true; + } else + return false; +} + diff --git a/minion_equipment.h b/minion_equipment.h new file mode 100644 index 000000000..d1aad337a --- /dev/null +++ b/minion_equipment.h @@ -0,0 +1,20 @@ +#ifndef _MINION_EQUIPMENT_H +#define _MINION_EQUIPMENT_H + +class MinionEquipment { + public: + bool needsItem(const Creature*, const Item*); + + bool isItemUseful(const Item*); + + private: + enum EquipmentType { WEAPON, BODY_ARMOR, HELMET, FIRST_AID_KIT }; + + bool needs(const Creature* c, const Item* it); + static Optional getEquipmentType(const Item* it); + + map, const Item*> equipmentMap; + map owners; +}; + +#endif diff --git a/model.cpp b/model.cpp new file mode 100644 index 000000000..49f779b7b --- /dev/null +++ b/model.cpp @@ -0,0 +1,248 @@ +#include "stdafx.h" + +using namespace std; + +bool Model::isTurnBased() { + return !collective || collective->isTurnBased(); +} + +void Model::update(double totalTime) { + if (collective) + collective->render(view); + do { + if (collective) { + // process a few times so events don't stack up when game is paused + for (int i : Range(5)) + collective->processInput(view); + } + Creature* creature = timeQueue.getNextCreature(); + CHECK(creature) << "No more creatures"; + Debug() << creature->getTheName() << " moving now"; + double time = creature->getTime(); + if (time > totalTime) + return; + if (time >= lastTick + 1) { + MEASURE({ + Debug() << "Turn " << time; + for (Creature* c : timeQueue.getAllCreatures()) { + c->tick(time); + } + for (Level* l : levels) + for (Square* square : l->getTickingSquares()) + square->tick(time); + lastTick = time; + if (collective) + collective->tick(); + }, "ticking time"); + } + bool unpossessed = false; + if (!creature->isDead()) { + bool wasPlayer = creature->isPlayer(); + creature->makeMove(); + if (wasPlayer && !creature->isPlayer()) + unpossessed = true; + } + if (collective) + collective->update(creature); + if (!creature->isDead()) { + Level* level = creature->getLevel(); + CHECK(level->getSquare(creature->getPosition())->getCreature() == creature); + } + if (unpossessed) + break; + } while (1); +} + +void Model::addCreature(PCreature c) { + c->setTime(timeQueue.getCurrentTime()); + CHECK(c->getLevel() != nullptr) << "Creature must already be located on a level."; + timeQueue.addCreature(std::move(c)); +} + +void Model::removeCreature(Creature* c) { + deadCreatures.push_back(timeQueue.removeCreature(c)); +} + +Level* Model::buildLevel(Level::Builder&& b, LevelMaker* maker, bool surface) { + Level::Builder builder(std::move(b)); + maker->make(&builder, Rectangle(builder.getWidth(), builder.getHeight())); + levels.push_back(builder.build(this, surface)); + return levels.back(); +} + +Model::Model(View* v) : view(v) { +} + +Level* Model::prepareTopLevel(Location* villageLocation) { + Level* top = buildLevel( + Level::Builder(600, 600, "Wilderness"), + LevelMaker::topLevel( + CreatureFactory::forrest(), + CreatureFactory::humanVillage(), + villageLocation, + CreatureFactory::crypt(), + CreatureFactory::goblinTown(1), + CreatureFactory::pyramid(0)), + true); + Level* c1 = buildLevel( + Level::Builder(30, 20, "Crypt"), + LevelMaker::cryptLevel(CreatureFactory::crypt(),{StairKey::CRYPT}, {})); + Level* p1 = buildLevel( + Level::Builder(13, 13, "Pyramid Level 2"), + LevelMaker::pyramidLevel(CreatureFactory::pyramid(1), {StairKey::PYRAMID}, {StairKey::PYRAMID})); + Level* p2 = buildLevel( + Level::Builder(11, 11, "Pyramid Level 3"), + LevelMaker::pyramidLevel(CreatureFactory::pyramid(2), {}, {StairKey::PYRAMID})); + addLink(StairDirection::DOWN, StairKey::CRYPT, top, c1); + addLink(StairDirection::UP, StairKey::PYRAMID, top, p1); + addLink(StairDirection::UP, StairKey::PYRAMID, p1, p2); + return top; +} + +Model* Model::heroModel(View* view, const string& heroName) { + Model* m = new Model(view); + Level* top = m->prepareTopLevel(new Location("village of " + NameGenerator::townNames.getNext(), "")); + Level* d1 = m->buildLevel( + Level::Builder(60, 35, "Dwarven Halls"), + LevelMaker::mineTownLevel(CreatureFactory::dwarfTown(1), {StairKey::DWARF}, {StairKey::DWARF})); + Level* g1 = m->buildLevel( + Level::Builder(60, 35, "Goblin Den"), + LevelMaker::goblinTownLevel(CreatureFactory::goblinTown(1), {StairKey::DWARF}, {})); + vector gnomish; + int numGnomLevels = 8; + // int towerLinkIndex = Random.getRandom(1, numGnomLevels - 1); + for (int i = 0; i < numGnomLevels; ++i) { + vector upKeys {StairKey::DWARF}; + /* if (i == towerLinkIndex) + upKeys.push_back(StairKey::TOWER);*/ + gnomish.push_back(m->buildLevel( + Level::Builder(60, 35, "Gnomish Mines Level " + convertToString(i + 1)), + LevelMaker::roomLevel(CreatureFactory::level(i + 1), upKeys, {StairKey::DWARF}))); + } + /* vector tower; + int numTowerLevels = 5; + for (int i = 0; i < numTowerLevels; ++i) + tower.push_back(m->buildLevel( + Level::Builder(4, 4, "Stone Tower " + convertToString(i + 2)), + LevelMaker::towerLevel(StairKey::TOWER, StairKey::TOWER))); + + for (int i = 0; i < numTowerLevels - 1; ++i) + m->addLink(StairDirection::DOWN, StairKey::TOWER, tower[i + 1], tower[i]);*/ + + // m->addLink(StairDirection::DOWN, StairKey::TOWER, tower[0], gnomish[towerLinkIndex]); + // m->addLink(StairDirection::DOWN, StairKey::TOWER, top, tower.back()); + + for (int i = 0; i < numGnomLevels - 1; ++i) + m->addLink(StairDirection::DOWN, StairKey::DWARF, gnomish[i], gnomish[i + 1]); + + m->addLink(StairDirection::DOWN, StairKey::DWARF, top, d1); + m->addLink(StairDirection::DOWN, StairKey::DWARF, d1, gnomish[0]); + m->addLink(StairDirection::UP, StairKey::DWARF, g1, gnomish.back()); + map* levelMemory = new map(); + PCreature player = CreatureFactory::addInventory( + PCreature(new Creature(ViewObject(ViewId::PLAYER, ViewLayer::CREATURE, "Player"), Tribe::player, + CATTR( + c.speed = 100; + c.weight = 90; + c.size = CreatureSize::LARGE; + c.strength = 15; + c.dexterity = 15; + c.barehandedDamage = 5; + c.humanoid = true; + c.name = "adventurer"; + c.firstName = heroName; + c.skills.insert(Skill::archery); + c.skills.insert(Skill::twoHandedWeapon);), Player::getFactory(view, levelMemory))), { + ItemId::FIRST_AID_KIT, + ItemId::SWORD, + ItemId::KNIFE, + ItemId::LEATHER_ARMOR, ItemId::LEATHER_HELM}); + for (int i : Range(Random.getRandom(70, 131))) + player->take(ItemFactory::fromId(ItemId::GOLD_PIECE)); + Tribe::goblin->makeSlightEnemy(player.get()); + Level* start = d1; + start->setPlayer(player.get()); + start->landCreature(StairDirection::UP, StairKey::DWARF, std::move(player)); + return m; +} + +Model* Model::collectiveModel(View* view, int numCreatures) { + Model* m = new Model(view); + CreatureFactory factory = CreatureFactory::collectiveStart(); + Location* villageLocation = new Location("village of " + NameGenerator::townNames.getNext(), ""); + Level* top = m->prepareTopLevel(villageLocation); + Level* second = m->buildLevel( + Level::Builder(60, 35, "Dungeons of doom "), + LevelMaker::roomLevel(CreatureFactory::level(3), {StairKey::DWARF}, {StairKey::DWARF})); + Level* l = m->buildLevel(Level::Builder(60, 35, "A Cave"), + LevelMaker::collectiveLevel({StairKey::DWARF}, {StairKey::DWARF})); + Level* dwarf = m->buildLevel( + Level::Builder(60, 35, NameGenerator::townNames.getNext()), + LevelMaker::mineTownLevel(CreatureFactory::dwarfTown(1), {StairKey::DWARF}, {})); + m->addLink(StairDirection::DOWN, StairKey::DWARF, top, l); + m->addLink(StairDirection::DOWN, StairKey::DWARF, l, second); + m->addLink(StairDirection::DOWN, StairKey::DWARF, second, dwarf); + m->collective = new Collective(CreatureFactory::collectiveMinions(), CreatureFactory::collectiveUndead()); + m->collective->setLevel(l); + Tribe::elven->addEnemy(Tribe::player); + Tribe::dwarven->addEnemy(Tribe::player); + Tribe::goblin->addEnemy(Tribe::player); + for (int i : Range(numCreatures)) { + PCreature c = factory.random(MonsterAIFactory::collective(m->collective)); + l->landCreature(StairDirection::UP, StairKey::PLAYER_SPAWN, c.get()); + m->collective->addCreature(c.get()); + m->addCreature(std::move(c)); + } + VillageControl* control = VillageControl::humanVillage(m->collective, villageLocation, + StairDirection::DOWN, StairKey::DWARF); + CreatureFactory firstAttack = CreatureFactory::collectiveEnemies(); + CreatureFactory lastAttack = CreatureFactory::collectiveFinalAttack(); + vector> heroAttackTime { + { 100, Random.getRandom(2, 5) }, + { 200, Random.getRandom(4, 8) }, + { 300, Random.getRandom(12, 18) }}; + for (int i : All(heroAttackTime)) { + CreatureFactory& factory = (i == heroAttackTime.size() - 1 ? lastAttack : firstAttack); + for (int k : Range(heroAttackTime[i].second)) { + PCreature c = factory.random(MonsterAIFactory::villageControl(control, villageLocation)); + control->addCreature(c.get(), heroAttackTime[i].first); + top->landCreature(StairDirection::UP, StairKey::PLAYER_SPAWN, std::move(c)); + } + } + VillageControl* dwarfControl = VillageControl::dwarfVillage(m->collective, l, StairDirection::UP, StairKey::DWARF); + for (int i : All(heroAttackTime)) { + CreatureFactory factory = CreatureFactory::singleType(Tribe::dwarven, CreatureId::DWARF); + for (int k : Range(heroAttackTime[i].second)) { + PCreature c = factory.random(MonsterAIFactory::villageControl(dwarfControl, nullptr)); + dwarfControl->addCreature(c.get(), heroAttackTime[i].first); + dwarf->landCreature(StairDirection::UP, StairKey::DWARF, std::move(c)); + } + } + Tribe::player->addEnemy(Tribe::dwarven); + return m; +} + +void Model::addLink(StairDirection dir, StairKey key, Level* l1, Level* l2) { + levelLinks[make_tuple(dir, key, l1)] = l2; + levelLinks[make_tuple(opposite(dir), key, l2)] = l1; +} + +Vec2 Model::changeLevel(StairDirection dir, StairKey key, Creature* c) { + Level* current = c->getLevel(); + Level* target = levelLinks[make_tuple(dir, key, current)]; + Vec2 newPos = target->landCreature(opposite(dir), key, c); + if (c->isPlayer()) { + current->setPlayer(nullptr); + target->setPlayer(c); + } + return newPos; +} + +void Model::changeLevel(Level* target, Vec2 position, Creature* c) { + Level* current = c->getLevel(); + target->landCreature({position}, c); + if (c->isPlayer()) { + current->setPlayer(nullptr); + target->setPlayer(c); + } +} diff --git a/model.h b/model.h new file mode 100644 index 000000000..16a89a417 --- /dev/null +++ b/model.h @@ -0,0 +1,60 @@ +#ifndef _MODEL_H +#define _MODEL_H + +#include +#include +#include "util.h" +#include "level.h" +#include "action.h" +#include "level_generator.h" +#include "square_factory.h" +#include "monster.h" + +class Collective; + +/** + * Main class that holds all game logic. + */ +class Model { + public: + + /** Generates levels and all game entities for a single player game. */ + static Model* heroModel(View* view, const string& heroName); + + /** Generates levels and all game entities for a collective game. */ + static Model* collectiveModel(View* view, int numCreatures); + + /** Makes an update to the game. This method is repeatedly called to make the game run. + Returns the total logical time elapsed.*/ + void update(double totalTime); + + /** Removes creature from current level and puts into the next, according to direction. */ + Vec2 changeLevel(StairDirection direction, StairKey key, Creature*); + + /** Removes creature from current level and puts into the given level */ + void changeLevel(Level*, Vec2 position, Creature*); + + /** Adds new creature to the queue. Assumes this creature has already been added to a level. */ + void addCreature(PCreature); + + /** Removes creature from the queue. Assumes it has already been removed from its level. */ + void removeCreature(Creature*); + + bool isTurnBased(); + + private: + Model(View* view); + Level* buildLevel(Level::Builder&& b, LevelMaker*, bool surface = false); + void addLink(StairDirection, StairKey, Level*, Level*); + Level* prepareTopLevel(Location*); + + vector levels; + View* view; + TimeQueue timeQueue; + vector deadCreatures; + double lastTick = -1000; + map, Level*> levelLinks; + Collective* collective = nullptr; +}; + +#endif diff --git a/monkey_test.cpp b/monkey_test.cpp new file mode 100644 index 000000000..53ae914e3 --- /dev/null +++ b/monkey_test.cpp @@ -0,0 +1,60 @@ +#include "stdafx.h" + +using namespace std; + +class MonkeyView : public View { + public: + MonkeyView() : View(100, 80) {} + virtual void initialize() { + } + virtual void close() override {} + virtual void addMessage(const string& message) override {} + virtual void addImportantMessage(const string& message) override {} + virtual Action getAction() override { + static int cnt = 0; + Debug() << ++cnt << " moves."; + return (Action)Random.getRandom(24); + } + virtual Optional chooseFromList(const string& title, const vector& options) override { + if (options.size() == 0) + return Nothing(); + int ind; + do { + ind = Random.getRandom(options.size()); + } while (View::hasTitlePrefix(options[ind])); + int titles = 0; + for (int i = 0; i < ind; ++i) + if (View::hasTitlePrefix(options[i])) + ++titles; + return ind - titles; + } + virtual Optional getNumber(const string&, int max) override { + return Random.getRandom(1, max + 1); + } + virtual void presentList(const string& title, const vector& options, bool) override { + } + virtual Optional chooseDirection(const string&) override { + return Vec2::neighbors8()[Random.getRandom(8)]; + } + virtual bool yesOrNoPrompt(const string&) override { + return Random.roll(2); + } + virtual void onRefreshView(const vector >& objects) override {} + virtual void onPlaceObject(Vec2 pos, const ViewObject& object) override {} + virtual void onMoveObject(Vec2 from, Vec2 to, const ViewObject& object) override {} + virtual void onEraseObject(Vec2 pos, const ViewObject& object) override {} +}; + +int main() { + Quest::initialize(); + ItemFactory::init(); + Tribe::init(); + View* view = new MonkeyView(); + view->initialize(); + messageBuffer.initialize(view); + Model model(view); + while (1) { + model.update(); + } + return 0; +} diff --git a/monster.cpp b/monster.cpp new file mode 100644 index 000000000..1e5d01ad3 --- /dev/null +++ b/monster.cpp @@ -0,0 +1,85 @@ +#include "stdafx.h" + +using namespace std; + +Monster::Monster(Creature* c, MonsterAIFactory f) : + creature(c), actor(f.getMonsterAI(c)) {} + +ControllerFactory Monster::getFactory(MonsterAIFactory f) { + return ControllerFactory([=](Creature* c) { return new Monster(c, f);}); +} + +void Monster::makeMove() { + actor->makeMove(); +} + +static string addName(const string& s, const string& n) { + if (n.size() > 0) + return s + " " + n; + else + return s; +} + +bool Monster::isPlayer() const { + return false; +} + +const MapMemory& Monster::getMemory(const Level* l) const { + return MapMemory::empty(); +} + +void Monster::onBump(Creature* c) { + c->attack(creature); +} + +void Monster::you(MsgType type, const string& param) const { + string msg, msgNoSee; + switch (type) { + case MsgType::ARE: msg = creature->getTheName() + " is " + param; break; + case MsgType::YOUR: msg = creature->getTheName() + "'s " + param; break; + case MsgType::FEEL: msg = creature->getTheName() + " looks " + param; break; + case MsgType::FALL_ASLEEP: msg = (creature->getTheName() + " falls asleep") + (param.size() > 0 ? " on the " + param : "."); + msgNoSee = "You hear snorring."; break; + case MsgType::WAKE_UP: msg = creature->getTheName() + " wakes up."; break; + case MsgType::DIE: msg = creature->getTheName() + " is killed!"; break; + case MsgType::TELE_APPEAR: msg = creature->getTheName() + " appears out of nowhere!"; break; + case MsgType::TELE_DISAPPEAR: msg = creature->getTheName() + " suddenly disappears!"; break; + case MsgType::BLEEDING_STOPS: msg = creature->getTheName() + "'s bleeding stops."; break; + case MsgType::DIE_OF_BLEEDING: msg = creature->getTheName() + " dies."; break; + case MsgType::FALL_APART: msg = creature->getTheName() + " falls apart."; break; + case MsgType::MISS_ATTACK: msg = creature->getTheName() + addName(" misses", param); break; + case MsgType::MISS_THROWN_ATTACK: msg = param + " misses " + creature->getTheName(); break; + case MsgType::HIT_THROWN_ITEM: msg = param + " hits " + creature->getTheName(); break; + case MsgType::CRASH_THROWN_ITEM: msg = param + " crashes on " + creature->getTheName() + "'s head"; break; + case MsgType::GET_HIT_NODAMAGE: msg = "The " + param + " is harmless."; break; + case MsgType::COLLAPSE: msg = creature->getTheName() + " collapses."; break; + case MsgType::FALL: msg = creature->getTheName() + " falls on the " + param; break; + case MsgType::TRIGGER_TRAP: msg = creature->getTheName() + " triggers something."; break; + case MsgType::PANIC: msg = creature->getTheName() + " panics."; break; + case MsgType::RAGE: msg = creature->getTheName() + " is enraged."; break; + case MsgType::SWING_WEAPON: msg = creature->getTheName() + " swings his " + param; break; + case MsgType::THRUST_WEAPON: msg = creature->getTheName() + " thrusts his " + param; break; + case MsgType::KICK: msg = creature->getTheName() + addName(" kicks", param); break; + case MsgType::BITE: msg = creature->getTheName() + addName(" bites", param); break; + case MsgType::PUNCH: msg = creature->getTheName() + addName(" punches", param); break; + case MsgType::CRAWL: msg = creature->getTheName() + " is crawling"; break; + case MsgType::STAND_UP: msg = creature->getTheName() + " is back on his feet"; break; + case MsgType::TURN_INVISIBLE: msg = creature->getTheName() + " disappears!"; break; + case MsgType::TURN_VISIBLE: msg = creature->getTheName() + " appears out of nowhere!"; break; + case MsgType::DROP_WEAPON: msg = creature->getTheName() + " drops his " + param; break; + case MsgType::ITEM_CRASHES: msg = param + " crashes on " + creature->getTheName(); break; + case MsgType::ENTER_PORTAL: msg = creature->getTheName() + " disappears in the portal."; break; + case MsgType::HAPPENS_TO: msg = param + " " + creature->getTheName(); break; + case MsgType::BURN: msg = creature->getTheName() + " burns in the " + param; msgNoSee = "You hear a horrible shriek"; break; + case MsgType::DROWN: msg = creature->getTheName() + " drowns in the " + param; msgNoSee = "You hear a loud splash" ;break; + case MsgType::SET_UP_TRAP: msg = "You set up the trap"; break; + case MsgType::KILLED_BY: msg = creature->getTheName() + " is killed by " + param; break; + default: break; + } + if (!msg.empty()) + creature->globalMessage(msg, msgNoSee); +} + +void Monster::you(const string& param) const { + creature->globalMessage(creature->getTheName() + " " + param); +} diff --git a/monster.h b/monster.h new file mode 100644 index 000000000..655b762fd --- /dev/null +++ b/monster.h @@ -0,0 +1,31 @@ +#ifndef _MONSTER_H +#define _MONSTER_H + +#include "creature.h" +#include "shortest_path.h" +#include "enums.h" + +class Monster : public Controller { + public: + Monster(Creature* creature, MonsterAIFactory); + + virtual void you(MsgType type, const string& param) const override; + virtual void you(const string& param) const override; + + virtual void makeMove() override; + virtual bool isPlayer() const; + virtual const MapMemory& getMemory(const Level* l = nullptr) const; + + virtual void onBump(Creature*); + + static ControllerFactory getFactory(MonsterAIFactory); + + protected: + Creature* creature; + + private: + PMonsterAI actor; + vector enemies; +}; + +#endif diff --git a/monster_ai.cpp b/monster_ai.cpp new file mode 100644 index 000000000..4d445fb7b --- /dev/null +++ b/monster_ai.cpp @@ -0,0 +1,776 @@ +#include "stdafx.h" + +using namespace std; + +Behaviour::Behaviour(Creature* c) : creature(c) { +} + +const Creature* Behaviour::getClosestEnemy() { + int dist = 1000000000; + const Creature* result = nullptr; + for (const Creature* other : creature->getVisibleEnemies()) { + if ((other->getPosition() - creature->getPosition()).length8() < dist && other->getTribe() != Tribe::pest) { + result = other; + dist = (other->getPosition() - creature->getPosition()).length8(); + } + } + return result; +} + +Item* Behaviour::getBestWeapon() { + Item* best = nullptr; + int damage = -1; + for (Item* item : creature->getEquipment().getItems(Item::typePredicate(ItemType::WEAPON))) + if (item->getModifier(AttrType::DAMAGE) > damage) { + damage = item->getModifier(AttrType::DAMAGE); + best = item; + } + return best; +} + +MoveInfo Behaviour::tryToApplyItem(EffectType type, double maxTurns) { + auto teleItems = creature->getEquipment().getItems(Item::effectPredicate(type)); + for (Item* item : teleItems) + if (creature->canApplyItem(item) && item->getApplyTime() <= maxTurns) + return { 1, [=]() { + creature->globalMessage(creature->getTheName() + " " + item->getApplyMsgThirdPerson(), + item->getNoSeeApplyMsg()); + creature->applyItem(item); + }}; + return {0, nullptr}; +} + +class Heal : public Behaviour { + public: + Heal(Creature* c) : Behaviour(c) {} + + virtual double itemValue(const Item* item) { + if (item->getEffectType() == EffectType::HEAL) { + return 0.5; + } + else + return 0; + } + + virtual MoveInfo getMove() { + if (!creature->isHumanoid() || creature->getHealth() == 1) + return {0, nullptr}; + for (Vec2 v : Vec2::directions8()) + if (creature->canHeal(v) && creature->isFriend(creature->getConstSquare(v)->getCreature())) { + creature->getConstSquare(v)->getCreature()->privateMessage("\"Let me help you my friend.\""); + creature->heal(v); + } + MoveInfo move = tryToApplyItem(EffectType::HEAL, 1); + if (move.move) + return { min(1.0, 1.5 - creature->getHealth()), move.move }; + move = tryToApplyItem(EffectType::HEAL, 3); + if (move.move) + return { 0.3 * min(1.0, 1.5 - creature->getHealth()), move.move }; + if (creature->getConstSquare()->getApplyType(creature) == SquareApplyType::SLEEP) + return { 0.4 * min(1.0, 1.5 - creature->getHealth()), [this] { + creature->applySquare(); + }}; + Vec2 bedRadius(10, 10); + Level* l = creature->getLevel(); + if (!hasBed || hasBed->level != creature->getLevel()) { + for (Vec2 v : Rectangle(creature->getPosition() - bedRadius, creature->getPosition() + bedRadius)) + if (l->inBounds(v) && l->getSquare(v)->getApplyType(creature) == SquareApplyType::SLEEP) + if (Optional move = creature->getMoveTowards(v)) + return { 0.4 * min(1.0, 1.5 - creature->getHealth()), [=] { + creature->move(*move); + hasBed = {v, creature->getLevel() }; + }}; + } else + if (Optional move = creature->getMoveTowards(hasBed->pos)) + return { 0.4 * min(1.0, 1.5 - creature->getHealth()), [=] { + creature->move(*move); + }}; + + return {0, nullptr}; + } + + private: + struct BedInfo { + Vec2 pos; + const Level* level; + }; + Optional hasBed; +}; + +class Rest : public Behaviour { + public: + Rest(Creature* c) : Behaviour(c) {} + + virtual MoveInfo getMove() { + return {0.1, [=] { creature->wait(); }}; + } +}; + +class MoveRandomly : public Behaviour { + public: + MoveRandomly(Creature* c, int _memSize) + : Behaviour(c), memSize(_memSize) {} + + virtual MoveInfo getMove() { + if (!visited(creature->getPosition())) + updateMem(creature->getPosition()); + Vec2 direction(0, 0); + double val = 0.0001; + Vec2 pos = creature->getPosition(); + for (Vec2 dir : Vec2::directions8(true)) + if (!visited(pos + dir) && creature->canMove(dir)) { + direction = dir; + break; + } + if (direction == Vec2(0, 0)) + for (Vec2 dir : Vec2::directions8(true)) + if (creature->canMove(dir)) { + direction = dir; + break; + } + if (direction == Vec2(0, 0)) + return {val, [this]() { creature->wait(); }}; + else + return {val, [this, direction]() { creature->move(direction); updateMem(creature->getPosition());}}; + } + + void updateMem(Vec2 pos) { + memory.push_back(pos); + if (memory.size() > memSize) + memory.pop_front(); + } + + bool visited(Vec2 pos) { + return contains(memory, pos); + } + + private: + deque memory; + int memSize; +}; + +class AttackPest : public Behaviour { + public: + AttackPest(Creature* c) : Behaviour(c) {} + + virtual MoveInfo getMove() override { + if (creature->getTribe() == Tribe::pest) + return NoMove; + const Creature* other = nullptr; + for (Vec2 v : Vec2::directions8(true)) + if (const Creature* c = creature->getConstSquare(v)->getCreature()) + if (c->getTribe() == Tribe::pest) { + other = c; + break; + } + if (!other) + return NoMove; + if (creature->canAttack(other)) + return {1.0, [this, other]() { + creature->attack(other); + }}; + else + return NoMove; + } +}; + +class BirdFlyAway : public Behaviour { + public: + BirdFlyAway(Creature* c, double _maxDist) : Behaviour(c), maxDist(_maxDist) {} + + virtual MoveInfo getMove() override { + const Creature* enemy = getClosestEnemy(); + bool fly = Random.roll(15) || ( enemy && (enemy->getPosition() - creature->getPosition()).lengthD() < maxDist); + if (creature->canFlyAway() && fly) + return {1.0, [this] () { + creature->flyAway(); + }}; + else + return NoMove; + } + + private: + double maxDist; +}; + +class GoldLust : public Behaviour { + public: + GoldLust(Creature* c) : Behaviour(c) {} + + virtual double itemValue(const Item* item) { + if (item->getType() == ItemType::GOLD) + return 1; + else + return 0; + } +}; + +class Fighter : public Behaviour, public EventListener { + public: + Fighter(Creature* c, double powerR, bool _chase) : Behaviour(c), maxPowerRatio(powerR), chase(_chase) { + EventListener::addListener(this); + courage = c->getCourage(); + } + + virtual ~Fighter() override { + EventListener::removeListener(this); + } + + virtual void onKillEvent(const Creature* victim, const Creature* killer) override { + if (victim != creature && victim->getName() == creature->getName() && creature->canSee(victim)) { + courage -= 0.1; + } + if (lastSeen && victim->getPosition() == lastSeen->pos) + lastSeen = Nothing(); + } + + virtual void onThrowEvent(const Creature* thrower, const Item* item, const vector& trajectory) override { + if (!creature->isHumanoid()) + return; + string name = creature->getName(); + if (contains(trajectory, creature->getPosition()) + && item->getName().size() > name.size() && item->getName().substr(0, name.size()) == name) { + creature->globalMessage(creature->getTheName() + " screams in terror!", "You hear a scream of terror."); + if (Random.roll(2)) + creature->rage(30); + else + courage -= 0.5; + } + } + + virtual const Level* getListenerLevel() const override { + return creature->getLevel(); + } + + virtual MoveInfo getMove() override { + const Creature* other = getClosestEnemy(); + if (other != nullptr) { + double myDamage = creature->getAttr(AttrType::DAMAGE); + Item* weapon = getBestWeapon(); + if (!creature->getWeapon() && weapon) + myDamage += weapon->getModifier(AttrType::DAMAGE); + double powerRatio = courage * myDamage / other->getAttr(AttrType::DAMAGE); + double weight = 1. - creature->getHealth() * 0.9; + if (powerRatio < maxPowerRatio) + weight += 2 - powerRatio * 2; + weight = min(1.0, max(0.0, weight)); + if (creature->isPanicking()) + weight = 1; + if (other->isSleeping() || other->isStationary()) + weight = 0; + Debug() << creature->getName() << " panic weight " << weight; + if (weight >= 0.5) { + if ((creature->getPosition() - other->getPosition()).length8() < 7) { + MoveInfo move = getPanicMove(other, weight); + if (move.isValid()) + return move; + else + return getAttackMove(other); + } + return NoMove; + } else + return getAttackMove(other); + } else + return getAttackMove(nullptr); + } + + MoveInfo getPanicMove(const Creature* other, double weight) { + MoveInfo teleMove = tryToApplyItem(EffectType::TELEPORT, 1); + if (teleMove.move != nullptr) + return {weight, teleMove.move}; + Optional move = creature->getMoveAway(other->getPosition(), chase); + if (move) + return {weight, [this, move] () { + creature->move(*move); + }}; + else + return NoMove; + } + + virtual double itemValue(const Item* item) { + if (item->getEffectType() && + contains({EffectType::INVISIBLE, EffectType::SLOW, EffectType::BLINDNESS, EffectType::SLEEP, + EffectType::TELEPORT}, *item->getEffectType())) + return 1; + if (item->getType() == ItemType::AMMO && creature->hasSkill(Skill::archery)) + return 0.1; + if (item->getType() != ItemType::WEAPON || !creature->hasSkillToUseWeapon(item)) + return 0; + if (item->getModifier(AttrType::THROWN_DAMAGE) > 0) + return (double)item->getModifier(AttrType::THROWN_DAMAGE) / 50; + int damage = item->getModifier(AttrType::DAMAGE); + Item* best = getBestWeapon(); + if (best && best != item && best->getModifier(AttrType::DAMAGE) >= damage) + return 0; + return (double)damage / 50; + } + + bool checkFriendlyFire(Vec2 enemyDir) { + Vec2 dir = enemyDir.shorten(); + for (Vec2 v = dir; v != enemyDir; v += dir) { + const Creature* c = creature->getConstSquare(v)->getCreature(); + if (c && !creature->isEnemy(c)) + return true; + } + return false; + } + + double getThrowValue(Item* it) { + if (it->getEffectType() && + contains({EffectType::SLOW, EffectType::BLINDNESS, EffectType::SLEEP}, *it->getEffectType())) + return 100; + return it->getModifier(AttrType::THROWN_DAMAGE); + } + + MoveInfo getThrowMove(Vec2 enemyDir) { + if (enemyDir.x != 0 && enemyDir.y != 0 && abs(enemyDir.x) != abs(enemyDir.y)) + return {0, nullptr}; + if (checkFriendlyFire(enemyDir)) + return {0, nullptr}; + Vec2 dir = enemyDir.shorten(); + Item* best = nullptr; + int damage = 0; + auto items = creature->getEquipment().getItems([this](Item* item) { + return !creature->getEquipment().isEquiped(item);}); + for (Item* item : items) + if (getThrowValue(item) > damage) { + damage = getThrowValue(item); + best = item; + } + if (best && creature->canThrowItem(best)) + return {1.0, [this, dir, best]() { + creature->globalMessage(creature->getTheName() + " throws " + best->getAName()); + creature->throwItem(best, dir); + }}; + else + return NoMove; + } + + MoveInfo getFireMove(Vec2 dir) { + if (dir.x != 0 && dir.y != 0 && abs(dir.x) != abs(dir.y)) + return {0, nullptr}; + if (checkFriendlyFire(dir)) + return {0, nullptr}; + if (creature->canFire(dir.shorten())) + return {1.0, [this, dir]() { + creature->globalMessage(creature->getTheName() + " fires an arrow "); + creature->fire(dir.shorten()); + }}; + return NoMove; + } + + MoveInfo getAttackMove(const Creature* other) { + int radius = 4; + int distance = 10000; + double lastSeenTimeout = 20; + if (other == nullptr) { + if (!lastSeen) { + return NoMove; + } + if (lastSeen->level != creature->getLevel() || + lastSeen->time < creature->getTime() - lastSeenTimeout || + lastSeen->pos == creature->getPosition()) { + lastSeen = Nothing(); + return NoMove; + } + if (chase) { + Optional move = creature->getMoveTowards(lastSeen->pos); + if (move) + return {0.5, [this, move]() { + Debug() << creature->getTheName() << " moving to last seen " << (lastSeen->pos - creature->getPosition()); + creature->move(*move); + }}; + else + return NoMove; + } + } + if (other->isInvincible()) + return NoMove; + Debug() << creature->getName() << " enemy " << other->getName(); + Vec2 enemyDir = (other->getPosition() - creature->getPosition()); + distance = enemyDir.length8(); + if (creature->isHumanoid() && !creature->getEquipment().getItem(EquipmentSlot::WEAPON)) { + Item* weapon = getBestWeapon(); + if (weapon != nullptr && creature->canEquip(weapon)) + return {3.0 / (2.0 + distance), [this, weapon]() { + creature->globalMessage(creature->getTheName() + " draws " + weapon->getAName()); + creature->equip(weapon); + }}; + } + if (distance <= 3) { + MoveInfo move = tryToApplyItem(EffectType::INVISIBLE, 1); + if (move.isValid()) + return move; + } + if (distance > 1) { + MoveInfo move = getFireMove(enemyDir); + if (move.isValid()) + return move; + move = getThrowMove(enemyDir); + if (move.isValid()) + return move; + if (chase && other->getTribe() != Tribe::wildlife) { + Optional move = creature->getMoveTowards(creature->getPosition() + enemyDir); + lastSeen = Nothing(); + if (move) + return {0.7, [this, enemyDir, move]() { + lastSeen = {creature->getPosition() + enemyDir, creature->getTime(), creature->getLevel()}; + creature->move(*move); + }}; + } + } + if (distance == 1) { + if (creature->canAttack(other)) + return {1.0, [this, other]() { + creature->attack(other); + }}; + } + return NoMove; + } + + private: + double maxPowerRatio; + double courage; + bool chase; + struct LastSeen { + Vec2 pos; + double time; + const Level* level; + }; + Optional lastSeen; +}; + +class GuardTarget : public Behaviour { + public: + GuardTarget(Creature* c, double minD, double maxD) : Behaviour(c), minDist(minD), maxDist(maxD) {} + + protected: + MoveInfo getMoveTowards(Vec2 target) { + double dist = (creature->getPosition() - target).lengthD(); + if (dist <= minDist) + return NoMove; + double exp = 1.5; + double weight = pow((dist - minDist) / (maxDist - minDist), exp); + Optional move = creature->getMoveTowards(target); + if (move) + return {weight, [this, move] () { + creature->move(*move); + }}; + else + return NoMove; + } + + private: + double minDist; + double maxDist; +}; + +class GuardArea : public Behaviour { + public: + GuardArea(Creature* c, const Location* l) : Behaviour(c), location(l), area(l->getBounds()) {} + + virtual MoveInfo getMove() override { + if (creature->getLevel() != location->getLevel()) + return NoMove; + if (!creature->getPosition().inRectangle(area)) { + for (Vec2 v : Vec2::directions8()) + if ((creature->getPosition() + v).inRectangle(area) && creature->canMove(v)) + return {1.0, [this, v] () { + creature->move(v); + }}; + Optional move = creature->getMoveTowards( + Vec2((area.getPX() + area.getKX()) / 2, (area.getPY() + area.getKY()) / 2)); + if (move) + return {1.0, [this, move] () { + creature->move(*move); + }}; + } + return NoMove; + } + + private: + const Location* location; + Rectangle area; +}; + +class GuardSquare : public GuardTarget { + public: + GuardSquare(Creature* c, Vec2 _pos, double minDist, double maxDist) : GuardTarget(c, minDist, maxDist), pos(_pos) {} + + virtual MoveInfo getMove() override { + return getMoveTowards(pos); + } + + private: + Vec2 pos; +}; + +class GuardCreature : public GuardTarget { + public: + GuardCreature(Creature* c, Creature* _target, double minDist, double maxDist) : GuardTarget(c, minDist, maxDist), target(_target) {} + + virtual MoveInfo getMove() override { + return getMoveTowards(target->getPosition()); + } + + private: + Creature* target; +}; + +class Thief : public Behaviour { + public: + Thief(Creature* c) : Behaviour(c) {} + + virtual MoveInfo getMove() override { + if (!creature->hasSkill(Skill::stealing)) + return {0, nullptr}; + for (const Creature* other : robbed) { + if (creature->canSee(other)) { + MoveInfo teleMove = tryToApplyItem(EffectType::TELEPORT, 1); + Debug() << "Gotta get out"; + if (teleMove.move != nullptr) + return teleMove; + Optional move = creature->getMoveAway(other->getPosition()); + if (move) + return {1.0, [this, move] () { + creature->move(*move); + }}; + } + } + for (Vec2 dir : Vec2::directions8(true)) { + const Creature* other = creature->getConstSquare(dir)->getCreature(); + if (other && !contains(robbed, other)) { + vector allGold; + for (Item* it : other->getEquipment().getItems()) + if (it->getType() == ItemType::GOLD) + allGold.push_back(it); + if (allGold.size() > 0) + return {1.0, [=]() { + creature->stealFrom(dir, allGold); + other->privateMessage(creature->getTheName() + " steals all your gold!"); + robbed.push_back(other); + }}; + } + } + return {0, nullptr}; + } + + private: + vector robbed; +}; + +class ByCollective : public Behaviour { + public: + ByCollective(Creature* c, Collective* col) : Behaviour(c), collective(col) {} + + virtual MoveInfo getMove() override { + return collective->getMove(creature); + } + + private: + Collective* collective; +}; + +class ChooseRandom : public Behaviour { + public: + ChooseRandom(Creature* c, vector beh, vector w) : Behaviour(c), behaviours(beh), weights(w) {} + + virtual MoveInfo getMove() override { + return chooseRandom(behaviours, weights)->getMove(); + } + + private: + vector behaviours; + vector weights; +}; + +class GoToHeart : public Behaviour { + public: + GoToHeart(Creature* c, Vec2 _heartPos) : Behaviour(c), heartPos(_heartPos) {} + + virtual MoveInfo getMove() override { + if (Optional move = creature->getMoveTowards(heartPos)) + return {1.0, [this, move] () { + creature->move(*move); + }}; + else + return NoMove; + }; + + private: + Vec2 heartPos; +}; + +class ByVillageControl : public Behaviour { + public: + ByVillageControl(Creature* c, VillageControl* control, Location* l) : + Behaviour(c), villageControl(control) { + if (l) + guardArea.reset(new GuardArea(c, l)); + } + + virtual MoveInfo getMove() override { + if (villageControl->startedAttack(creature)) + return villageControl->getMove(creature); + else if (guardArea) + return guardArea->getMove(); + else + return NoMove; + } + + private: + VillageControl* villageControl; + PBehaviour guardArea; +}; + +MonsterAI::MonsterAI(Creature* c, const vector& beh, const vector& w, bool pick) : + weights(w), creature(c), pickItems(pick) { + CHECK(beh.size() == w.size()); + for (auto b : beh) + behaviours.push_back(PBehaviour(b)); +} + +void MonsterAI::makeMove() { + vector> moves; + for (int i : All(behaviours)) { + MoveInfo move = behaviours[i]->getMove(); + move.value *= weights[i]; + moves.emplace_back(move, weights[i]); + if (pickItems) { + map> stacks = Item::stackItems(creature->getPickUpOptions()); + for (auto elem : stacks) { + Item* item = elem.second[0]; + if (!item->isUnpaid() && creature->canPickUp(elem.second)) { + MoveInfo pickupMove { behaviours[i]->itemValue(item) * weights[i], [=]() { + creature->globalMessage(creature->getTheName() + " picks up " + elem.first, ""); + creature->pickUp(elem.second); + }}; + moves.emplace_back(pickupMove, weights[i]); + } + } + } + } + /*vector inventory = creature->getEquipment().getItems([this](Item* item) { return !creature->getEquipment().isEquiped(item);}); + for (Item* item : inventory) { + bool useless = true; + for (PBehaviour& behaviour : behaviours) + if (behaviour->itemValue(item) > 0) + useless = false; + if (useless) + moves.push_back({ 0.01, [=]() { + creature->globalMessage(creature->getTheName() + " drops " + item->getAName(), ""); + creature->drop({item}); + }}); + }*/ + MoveInfo winner {0, nullptr}; + for (int i : All(moves)) { + MoveInfo& move = moves[i].first; + if (move.value > winner.value) + winner = move; + if (i < moves.size() - 1 && move.value > moves[i + 1].second) + break; + } + CHECK(winner.value > 0); + winner.move(); +} + +PMonsterAI MonsterAIFactory::getMonsterAI(Creature* c) { + return PMonsterAI(maker(c)); +} + +MonsterAIFactory::MonsterAIFactory(MakerFun _maker) : maker(_maker) { +} + +MonsterAIFactory MonsterAIFactory::monster() { + return stayInLocation(nullptr); +} + +MonsterAIFactory MonsterAIFactory::collective(Collective* col) { + return MonsterAIFactory([=](Creature* c) { + return new MonsterAI(c, { + new Heal(c), + new Fighter(c, 0.6, true), + new ByCollective(c, col), + new ChooseRandom(c, {new Rest(c), new MoveRandomly(c, 3)}, {3, 1}), + new AttackPest(c)}, + { 6, 5, 2, 1, 1}, false); + }); +} + +MonsterAIFactory MonsterAIFactory::villageControl(VillageControl* col, Location* l) { + return MonsterAIFactory([=](Creature* c) { + return new MonsterAI(c, { + new Heal(c), + new Fighter(c, 0.6, true), + new GoldLust(c), + new ByVillageControl(c, col, l), + new MoveRandomly(c, 3)}, + { 6, 5, 3, 2, 1 }); + }); +} + +MonsterAIFactory MonsterAIFactory::stayInLocation(Location* l) { + return MonsterAIFactory([l](Creature* c) { + vector actors { + new Heal(c), + new Thief(c), + new Fighter(c, 0.6, true), + new MoveRandomly(c, 3), + new AttackPest(c), + new GoldLust(c)}; + vector weights { 5, 4, 3, 1, 1, 1 }; + if (l != nullptr) { + actors.push_back(new GuardArea(c, l)); + weights.push_back(1); + } + return new MonsterAI(c, actors, weights); + }); +} + +MonsterAIFactory MonsterAIFactory::wildlifeNonPredator() { + return MonsterAIFactory([](Creature* c) { + return new MonsterAI(c, { + new Fighter(c, 1.2, false), + new MoveRandomly(c, 3)}, + {5, 1}); + }); +} + +MonsterAIFactory MonsterAIFactory::moveRandomly() { + return MonsterAIFactory([](Creature* c) { + return new MonsterAI(c, { + new MoveRandomly(c, 3)}, + {1}); + }); +} + +MonsterAIFactory MonsterAIFactory::idle() { + return MonsterAIFactory([](Creature* c) { + return new MonsterAI(c, { + new Rest(c)}, + {1}); + }); +} + +MonsterAIFactory MonsterAIFactory::scavengerBird(Vec2 corpsePos) { + return MonsterAIFactory([=](Creature* c) { + return new MonsterAI(c, { + new BirdFlyAway(c, 3), + new MoveRandomly(c, 3), + new GuardSquare(c, corpsePos, 1, 2)}, + {1, 1, 2}); + }); +} + +MonsterAIFactory MonsterAIFactory::follower(Creature* leader, int radius) { + return MonsterAIFactory([=](Creature* c) { + return new MonsterAI(c, { + new Heal(c), + new Fighter(c, 0.6, true), + new GuardCreature(c, leader, radius, 3 * radius), + new MoveRandomly(c, 3), + new GoldLust(c)}, + { 4, 3, 2, 1, 1 }); + }); +} + diff --git a/monster_ai.h b/monster_ai.h new file mode 100644 index 000000000..fcaf615ed --- /dev/null +++ b/monster_ai.h @@ -0,0 +1,77 @@ +#ifndef _ACTOR_H +#define _ACTOR_H + +#include "creature.h" +#include "location.h" + +struct MoveInfo { + double value; + function move; + + bool isValid() const { + return move != nullptr; + } +}; + +const MoveInfo NoMove = {0.0, nullptr}; + +enum MonsterAIType { + MONSTER, + WILDLIFE_NON_PREDATOR, + BIRD_FLY_AWAY, + FOLLOWER, +}; + +class Behaviour { + public: + Behaviour(Creature*); + virtual MoveInfo getMove() { return {0, nullptr}; } + virtual void onAttacked(const Creature* attacker) {} + virtual double itemValue(const Item*) { return 0; } + Item* getBestWeapon(); + const Creature* getClosestEnemy(); + MoveInfo tryToApplyItem(EffectType, double maxTurns); + + virtual ~Behaviour() {} + + protected: + Creature* creature; +}; + +class MonsterAI { + public: + void makeMove(); + + private: + friend class MonsterAIFactory; + MonsterAI(Creature*, const vector& behaviours, const vector& weights, bool pickItems = true); + vector behaviours; + vector weights; + Creature* creature; + bool pickItems; +}; + +class Collective; +class VillageControl; + +class MonsterAIFactory { + public: + PMonsterAI getMonsterAI(Creature* c); + + static MonsterAIFactory collective(Collective*); + static MonsterAIFactory villageControl(VillageControl*, Location*); + static MonsterAIFactory monster(); + static MonsterAIFactory stayInLocation(Location*); + static MonsterAIFactory wildlifeNonPredator(); + static MonsterAIFactory scavengerBird(Vec2 corpsePos); + static MonsterAIFactory follower(Creature*, int radius); + static MonsterAIFactory moveRandomly(); + static MonsterAIFactory idle(); + + private: + typedef function MakerFun; + MonsterAIFactory(MakerFun); + MakerFun maker; +}; + +#endif diff --git a/name_generator.cpp b/name_generator.cpp new file mode 100644 index 000000000..92f59da2f --- /dev/null +++ b/name_generator.cpp @@ -0,0 +1,85 @@ +#include "stdafx.h" + +NameGenerator NameGenerator::scrolls; +NameGenerator NameGenerator::firstNames; +NameGenerator NameGenerator::aztecNames; +NameGenerator NameGenerator::creatureNames; +NameGenerator NameGenerator::weaponNames; +NameGenerator NameGenerator::worldNames; +NameGenerator NameGenerator::townNames; +NameGenerator NameGenerator::dwarfNames; +NameGenerator NameGenerator::deityNames; +NameGenerator NameGenerator::demonNames; +NameGenerator NameGenerator::dogNames; + +string getSyllable() { + string vowels = "aeyuio"; + string consonants = "qwrtplkjhgfdszxcvbnm"; + string ret; + if (Random.roll(3)) + ret += consonants[Random.getRandom(consonants.size())]; + ret += vowels[Random.getRandom(vowels.size())]; + if (Random.roll(3)) + ret += consonants[Random.getRandom(consonants.size())]; + return ret; +} + +string getWord() { + int syllables = chooseRandom({1, 2, 3, 4}, {1, 4, 3, 1}); + string ret; + for (int i : Range(syllables)) + ret += getSyllable(); + return ret; +} + +vector readLines(const string& path) { + vector input; + ifstream in(path); + CHECK(in.is_open()) << "Unable to open " << path; + char buf[100]; + while (in.getline(buf, 100)) + input.push_back(buf); + return input; +} + +void NameGenerator::init(const string& firstNamesPath, const string& aztecNamesPath, + const string& creatureNamesPath, const string& weaponNamesPath, const string& worldsPath, + const string& townsPath, const string& dwarfsPath, const string& deitiesPath, const string& demonsPath, + const string& dogsPath) { + vector input; + for (int i : Range(1000)) { + string ret; + int parts = chooseRandom({1, 2}, {3, 1}); + for (int k : Range(parts)) + ret += getWord() + " "; + trim(ret); + input.push_back(ret); + } + scrolls = NameGenerator(input); + + firstNames = NameGenerator(readLines(firstNamesPath)); + aztecNames = NameGenerator(readLines(aztecNamesPath)); + creatureNames = NameGenerator(readLines(creatureNamesPath)); + weaponNames = NameGenerator(readLines(weaponNamesPath)); + worldNames = NameGenerator(readLines(worldsPath)); + townNames = NameGenerator(readLines(townsPath)); + deityNames = NameGenerator(readLines(deitiesPath)); + dwarfNames = NameGenerator(readLines(dwarfsPath)); + demonNames = NameGenerator(readLines(demonsPath)); + dogNames = NameGenerator(readLines(dogsPath)); +} + + +string NameGenerator::getNext() { + CHECK(!names.empty()) << "Out of names!"; + string ret = names.front(); + names.pop(); + return ret; +} + + +NameGenerator::NameGenerator(vector list) { + random_shuffle(list.begin(), list.end(),[](int a) { return Random.getRandom(a);}); + for (string name : list) + names.push(name); +} diff --git a/name_generator.h b/name_generator.h new file mode 100644 index 000000000..0c0ee733b --- /dev/null +++ b/name_generator.h @@ -0,0 +1,37 @@ +#ifndef _NAME_GENERATOR +#define _NAME_GENERATOR + +class NameGenerator { + public: + NameGenerator() = default; + string getNext(); + + static NameGenerator firstNames; + static NameGenerator scrolls; + static NameGenerator aztecNames; + static NameGenerator creatureNames; + static NameGenerator weaponNames; + static NameGenerator worldNames; + static NameGenerator townNames; + static NameGenerator dwarfNames; + static NameGenerator deityNames; + static NameGenerator demonNames; + static NameGenerator dogNames; + + static void init( + const string& firstNamesPath, + const string& aztecNamesPath, + const string& specialCreaturesPath, + const string& specialWeaponsPath, + const string& worldsPath, + const string& townsPath, + const string& dwarfPath, + const string& deitiesPath, + const string& demonsPath, + const string& dogsPath); + private: + NameGenerator(vector names); + queue names; +}; + +#endif diff --git a/pantheon.cpp b/pantheon.cpp new file mode 100644 index 000000000..3b0df4f2c --- /dev/null +++ b/pantheon.cpp @@ -0,0 +1,269 @@ +#include "stdafx.h" + +static map> epithetsMap { + { DeityHabitat::FIRE, + { Epithet::WAR, Epithet::DEATH, Epithet::DESTRUCTION, Epithet::WEALTH, + Epithet::FEAR, Epithet::CRAFTS, Epithet::LIGHT, Epithet::DARKNESS }}, + { DeityHabitat::EARTH, + { Epithet::HEALTH, Epithet::NATURE, Epithet::WINTER, Epithet::LOVE, + Epithet::WEALTH, Epithet::MIND, Epithet::CHANGE, Epithet::DEFENSE, Epithet::DARKNESS }}, + { DeityHabitat::TREES, + { Epithet::HEALTH, Epithet::NATURE, Epithet::LOVE, Epithet::LIGHT, + Epithet::CHANGE, Epithet::CRAFTS, Epithet::HUNTING, Epithet::FORTUNE, Epithet::SECRETS, }}, + { DeityHabitat::STONE, + { Epithet::WISDOM, Epithet::WEALTH, Epithet::DEFENSE, Epithet::SECRETS, Epithet::DEATH, + Epithet::WINTER, }}, + { DeityHabitat::WATER, + { Epithet::NATURE, Epithet::WISDOM, Epithet::WEALTH, Epithet::MIND, Epithet::DESTRUCTION, + Epithet::CHANGE, Epithet::DEFENSE, Epithet::FEAR, Epithet::COURAGE, Epithet::HEALTH, }}, + { DeityHabitat::AIR, + { Epithet::MIND, Epithet::LIGHTNING, Epithet::LIGHT, Epithet::CHANGE, + Epithet::FORTUNE, Epithet::FEAR, Epithet::COURAGE, }}, + { DeityHabitat::STARS, + { Epithet::DEATH, Epithet::WAR, Epithet::WINTER, Epithet::WISDOM, Epithet::LOVE, + Epithet::DARKNESS, Epithet::FORTUNE, Epithet::SECRETS, }} + +}; + +/* + CHANGE earth, water, trees, earth + COURAGE air, water + CRAFTS trees, fire + DARKNESS stars, earth, fire + DEATH stars, fire, stone + DEFENSE water, stone, earth + DESTRUCTION water, fire + FEAR air, water, fire + FORTUNE stars, air, trees + HEALTH water, trees, earth + HUNTING trees + LIGHT air, trees, fire + LIGHTNING air + LOVE stars, air, trees + MIND air, water, earth + NATURE water, trees, earth + SECRETS stars, stone, trees + WAR stars, fire + WEALTH water, stone, earth, fire + WINTER stars, stone, earth + WISDOM stars, water, stone +*/ + +static string getEpithetString(Epithet epithet) { + switch (epithet) { + case Epithet::CHANGE: return "change"; + case Epithet::COURAGE: return "courage"; // ? + case Epithet::CRAFTS: return "crafts"; + case Epithet::DARKNESS: return "darkness"; + case Epithet::DEATH: return "death"; + case Epithet::DEFENSE: return "defense"; + case Epithet::DESTRUCTION: return "destruction"; + case Epithet::FEAR: return "fear"; + case Epithet::FORTUNE: return "fortune"; // ? + case Epithet::HEALTH: return "health"; + case Epithet::HUNTING: return "hunting"; // cos do polowania + case Epithet::LIGHT: return "light"; // ? + case Epithet::LIGHTNING: return "lightning"; + case Epithet::LOVE: return "love"; // potion of love + case Epithet::MIND: return "mind"; + case Epithet::NATURE: return "nature"; + case Epithet::SECRETS: return "secrets"; + case Epithet::WAR: return "war"; + case Epithet::WEALTH: return "wealth"; + case Epithet::WINTER: return "winter"; // zamarza level + case Epithet::WISDOM: return "wisdom"; + } + return ""; +} + +string Deity::getName() const { + return name; +} + +Gender Deity::getGender() const { + return gender; +} + +string Deity::getEpithets() const { + vector v; + for (Epithet e : epithets) + v.push_back(getEpithetString(e)); + return combine(v); +} + +static string toString(DeityHabitat h) { + switch (h) { + case DeityHabitat::EARTH: return "the earth"; + case DeityHabitat::STONE: return "stone"; + case DeityHabitat::FIRE: return "fire"; + case DeityHabitat::AIR: return "the air"; + case DeityHabitat::TREES: return "the trees"; + case DeityHabitat::WATER: return "water"; + case DeityHabitat::STARS: return "the stars"; + } + return ""; +} + +DeityHabitat Deity::getHabitat() const { + return habitat; +} + +string Deity::getHabitatString() const { + return toString(habitat); +} + +static void grantGift(Creature* c, ItemId id, string deity, int num = 1) { + c->privateMessage(deity + " grants you a gift."); + Effect::giveItemEffect(id, num)->applyToCreature(c, EffectStrength::NORMAL); +} + +static void applyEffect(Creature* c, EffectType effect, string msg) { + c->privateMessage(msg); + Effect::getEffect(effect)->applyToCreature(c, EffectStrength::STRONG); +} + +void Deity::onPrayer(Creature* c) { + bool prayerAnswered = false; + for (Epithet epithet : randomPermutation(epithets)) { + if (contains(usedEpithets, epithet)) + continue; + bool noEffect = false; + switch (epithet) { + case Epithet::DEATH: { + PCreature death = CreatureFactory::fromId(CreatureId::DEATH, Tribe::killEveryone); + for (Vec2 v : c->getPosition().neighbors8(true)) + if (c->getLevel()->inBounds(v) && c->getLevel()->getSquare(v)->canEnter(death.get())) { + c->privateMessage("Death appears before you."); + c->getLevel()->addCreature(v, std::move(death)); + break; + } + if (death) + noEffect = true; + break; } + case Epithet::WAR: + grantGift(c, chooseRandom( + {ItemId::SPECIAL_SWORD, ItemId::SPECIAL_BATTLE_AXE, ItemId::SPECIAL_WAR_HAMMER}), name); break; + case Epithet::WISDOM: grantGift(c, + chooseRandom({ItemId::MUSHROOM_BOOK, ItemId::POTION_BOOK, ItemId::AMULET_BOOK}), name); break; + case Epithet::DESTRUCTION: applyEffect(c, EffectType::DESTROY_EQUIPMENT, ""); break; + case Epithet::SECRETS: grantGift(c, ItemId::INVISIBLE_POTION, name); break; + case Epithet::LIGHTNING: + c->bleed(0.9); + c->you(MsgType::ARE, "struck by a lightning bolt!"); + break; + case Epithet::FEAR: applyEffect(c, EffectType::PANIC, name + " puts fear in your heart"); break; + case Epithet::MIND: + if (Random.roll(2)) + applyEffect(c, EffectType::RAGE, name + " fills your head with anger"); + else + applyEffect(c, EffectType::HALLU, ""); + break; + case Epithet::CHANGE: + if (Random.roll(2) && c->getEquipment().getItem(EquipmentSlot::WEAPON)) { + PCreature snake = CreatureFactory::fromId(CreatureId::SNAKE, Tribe::pest); + for (Vec2 v : c->getPosition().neighbors8(true)) + if (c->getLevel()->inBounds(v) && c->getLevel()->getSquare(v)->canEnter(snake.get())) { + c->getLevel()->addCreature(v, std::move(snake)); + c->steal({c->getEquipment().getItem(EquipmentSlot::WEAPON)}); + c->privateMessage("Ouch!"); + c->you(MsgType::YOUR, "weapon turns into a snake!"); + break; + } + if (!snake) + break; + } + for (Item* it : randomPermutation(c->getEquipment().getItems())) { + if (it->getType() == ItemType::POTION) { + c->privateMessage("Your " + it->getName() + " changes color!"); + c->steal({it}); + c->take(ItemFactory::potions().random()); + break; + } + if (it->getType() == ItemType::SCROLL) { + c->privateMessage("Your " + it->getName() + " changes label!"); + c->steal({it}); + c->take(ItemFactory::scrolls().random()); + break; + } + if (it->getType() == ItemType::AMULET) { + c->privateMessage("Your " + it->getName() + " changes shape!"); + c->steal({it}); + c->take(ItemFactory::amulets().random()); + break; + } + } + break; + case Epithet::HEALTH: + if (c->getHealth() < 1 || c->lostLimbs()) + applyEffect(c, EffectType::HEAL, "You feel a healing power overcoming you"); + else { + if (Random.roll(4)) + grantGift(c, ItemId::HEALING_AMULET, name); + else + grantGift(c, ItemId::HEALING_POTION, name, Random.getRandom(1, 4)); + } + break; + case Epithet::NATURE: grantGift(c, ItemId::FRIENDLY_ANIMALS_AMULET, name); break; +// case Epithet::LOVE: grantGift(c, ItemId::PANIC_MUSHROOM, name); break; + case Epithet::WEALTH: grantGift(c, ItemId::GOLD_PIECE, name, Random.getRandom(100, 200)); break; + case Epithet::DEFENSE: grantGift(c, ItemId::DEFENSE_AMULET, name); break; + case Epithet::DARKNESS: applyEffect(c, EffectType::BLINDNESS, ""); break; + case Epithet::CRAFTS: applyEffect(c, + chooseRandom({EffectType::ENHANCE_ARMOR, EffectType::ENHANCE_WEAPON}), ""); break; +// case Epithet::HUNTING: grantGift(c, ItemId::PANIC_MUSHROOM, name); break; + default: noEffect = true; + } + usedEpithets.push_back(epithet); + if (!noEffect) { + prayerAnswered = true; + break; + } + } + if (!prayerAnswered) + c->privateMessage("Your prayer is not answered."); +} + +vector generateDeities() { + set used; + vector ret; + for (auto elem : epithetsMap) { + string deity = NameGenerator::deityNames.getNext(); + Gender gend = Gender::MALE; + if ((deity.back() == 'a' || deity.back() == 'i') && !Random.roll(4)) + gend = Gender::FEMALE; + vector ep; + for (int i : Range(Random.getRandom(1, 4))) { + Epithet epithet; + int cnt = 100; + do { + epithet = chooseRandom(elem.second); + } while (used.count(epithet) && --cnt > 0); + if (cnt == 0) + break; + used.insert(epithet); + ep.push_back(epithet); + } + if (ep.empty()) + return generateDeities(); + ret.push_back(new Deity(deity, gend, ep, elem.first)); + } + return ret; +} + +vector Deity::getDeities() { + static vector deities = generateDeities(); + return deities; +} + +Deity* Deity::getDeity(DeityHabitat h) { + for (Deity* d : getDeities()) + if (d->getHabitat() == h) + return d; + Debug(FATAL) << "Didn't find deity for habitat " << (int)h; + return getDeities()[0]; +} + +Deity::Deity(const string& n, Gender g, const vector& e, DeityHabitat h) : + name(n), gender(g), epithets(e), habitat(h) { +} + diff --git a/pantheon.h b/pantheon.h new file mode 100644 index 000000000..46f69a2d4 --- /dev/null +++ b/pantheon.h @@ -0,0 +1,61 @@ +#ifndef _PANTHEON_H +#define _PANTHEON_H + +#include "util.h" + +enum class DeityHabitat { + EARTH, + STONE, + FIRE, + AIR, + TREES, + WATER, + STARS }; + +enum class Epithet { + CHANGE, + COURAGE, + CRAFTS, + DARKNESS, + DEATH, + DEFENSE, + DESTRUCTION, + FEAR, + FORTUNE, + HEALTH, + HUNTING, + LIGHT, + LIGHTNING, + LOVE, + MIND, + NATURE, + SECRETS, + WAR, + WEALTH, + WINTER, + WISDOM, +}; + +class Deity { + public: + Deity(const string& name, Gender gender, const vector& epithets, DeityHabitat habitat); + string getName() const; + Gender getGender() const; + string getEpithets() const; + string getHabitatString() const; + DeityHabitat getHabitat() const; + void onPrayer(Creature* c); + + static vector getDeities(); + static Deity* getDeity(DeityHabitat); + + private: + Deity(Deity&) {} + string name; + Gender gender; + vector epithets; + vector usedEpithets; + DeityHabitat habitat; +}; + +#endif diff --git a/player.cpp b/player.cpp new file mode 100644 index 000000000..1839718f3 --- /dev/null +++ b/player.cpp @@ -0,0 +1,563 @@ +#include "stdafx.h" + +using namespace std; + +Player::Player(Creature* c, View* v, bool greet, map* memory) : + creature(c), view(v), displayGreeting(greet), levelMemory(memory) { + EventListener::addListener(this); +} + +Player::~Player() { + EventListener::removeListener(this); +} + +const Level* Player::getListenerLevel() const { + return creature->getLevel(); +} + +void Player::onThrowEvent(const Creature* thrower, const Item* item, const vector& trajectory) { + for (Vec2 v : trajectory) + if (creature->canSee(v)) { + view->animateObject(trajectory, item->getViewObject()); + return; + } +} +ControllerFactory Player::getFactory(View* f, map* levelMemory) { + return ControllerFactory([=](Creature* c) { return new Player(c, f, true, levelMemory);}); +} + +map slotSuffixes = { + {EquipmentSlot::WEAPON, "(weapon ready)"}, + {EquipmentSlot::RANGED_WEAPON, "(ranged weapon ready)"}, + {EquipmentSlot::BODY_ARMOR, "(being worn)"}, + {EquipmentSlot::HELMET, "(being worn)"}, + {EquipmentSlot::AMULET, "(being worn)"}}; + +map slotTitles = { + {EquipmentSlot::WEAPON, "Weapon"}, + {EquipmentSlot::RANGED_WEAPON, "Ranged weapon"}, + {EquipmentSlot::BODY_ARMOR, "Body armor"}, + {EquipmentSlot::HELMET, "Helmet"}, + {EquipmentSlot::AMULET, "Amulet"}}; + +View* Player::getView() { + MEASURE( + view->refreshView(creature), + "level render time"); + return view; +} + +void Player::onBump(Creature*) { + Debug(FATAL) << "Shouldn't call onBump on a player"; +} + +void Player::getItemNames(vector items, vector& names, vector >& groups) { + map > ret = groupBy(items, + [this] (Item* const& item) { + if (creature->getEquipment().isEquiped(item)) + return item->getNameAndModifiers(false, creature->isBlind()) + " " + + slotSuffixes[creature->getEquipment().getSlot(item)]; + else + return item->getNameAndModifiers(false, creature->isBlind());}); + for (auto elem : ret) { + if (elem.second.size() == 1) + names.push_back(elem.first); + else + names.push_back(convertToString(elem.second.size()) + " " + + elem.second[0]->getNameAndModifiers(true, creature->isBlind())); + groups.push_back(elem.second); + } +} + +string Player::getPluralName(Item* item, int num) { + if (num == 1) + return item->getTheName(false, creature->isBlind()); + else + return convertToString(num) + " " + item->getTheName(true, creature->isBlind()); +} + +static string getSquareQuestion(SquareApplyType type, string name) { + switch (type) { + case SquareApplyType::DESCEND: return "Descend the " + name; + case SquareApplyType::ASCEND: return "Ascend the " + name; + case SquareApplyType::USE_CHEST: return "Open the " + name; + case SquareApplyType::DRINK: return "Drink from the " + name; + case SquareApplyType::PRAY: return "Pray at the " + name; + case SquareApplyType::SLEEP: return "Go to sleep on the " + name; + default: Debug(FATAL) << "Unhandled"; + } + return ""; +} + +static bool canUseSquare(SquareApplyType type) { + return !contains({SquareApplyType::TRAIN, SquareApplyType::WORKSHOP}, type); +} + +void Player::pickUpAction(bool extended) { + auto items = creature->getPickUpOptions(); + const Square* square = creature->getConstSquare(); + if (square->getApplyType(creature) && canUseSquare(*square->getApplyType(creature)) && + (items.empty() || + view->yesOrNoPrompt(getSquareQuestion(*square->getApplyType(creature), square->getName())))) { + creature->applySquare(); + return; + } + vector names; + vector > groups; + getItemNames(creature->getPickUpOptions(), names, groups); + if (names.empty()) + return; + int index = 0; + if (names.size() > 1) { + Optional res = view->chooseFromList("Choose an item to pick up:", names); + if (!res) + return; + else + index = *res; + } + int num = groups[index].size(); + if (num < 1) + return; + if (extended && num > 1) { + Optional res = view->getNumber("Pick up how many " + groups[index][0]->getName(true) + "?", num); + if (!res) + return; + num = *res; + } + vector pickUpItems = getPrefix(groups[index], 0, num); + if (creature->canPickUp(pickUpItems)) { + messageBuffer.addMessage("You pick up " + getPluralName(groups[index][0], num)); + creature->pickUp(pickUpItems); + } +} + +void Player::itemsMessage() { + vector names; + vector > groups; + getItemNames(creature->getPickUpOptions(), names, groups); + if (names.size() > 1) + privateMessage(creature->isBlind() ? "You feel here some items" : "You see here some items."); + else if (names.size() == 1) + privateMessage((creature->isBlind() ? string("You feel here ") : ("You see here ")) + + (groups[0].size() == 1 ? "a " + groups[0][0]->getNameAndModifiers(false, creature->isBlind()) : names[0])); +} + +ItemType typeDisplayOrder[] { ItemType::WEAPON, ItemType::RANGED_WEAPON, ItemType::AMMO, ItemType::ARMOR, ItemType::POTION, ItemType::SCROLL, ItemType::FOOD, ItemType::BOOK, ItemType::AMULET, ItemType::TOOL, ItemType::CORPSE, ItemType::OTHER, ItemType::GOLD }; + +vector Player::chooseItem(const string& text, function predicate, bool onlyDisplay, Optional otherOption) { + map > typeGroups = groupBy( + creature->getEquipment().getItems(predicate), [](Item* const& item) { return item->getType();}); + vector names; + vector > groups; + for (auto elem : typeDisplayOrder) + if (typeGroups[elem].size() > 0) { + names.push_back(View::getTitlePrefix(Item::getText(elem))); + getItemNames(typeGroups[elem], names, groups); + } + if (onlyDisplay) { + view->presentList(text, names); + return vector(); + } + Optional index = view->chooseFromList(text, names); + if (index) + return groups[*index]; + return vector(); +} + +void Player::dropAction(bool extended) { + vector items = chooseItem("Choose an item to drop:", [this](Item* item) { + return !creature->getEquipment().isEquiped(item);}); + int num = items.size(); + if (num < 1) + return; + if (extended && num > 1) { + Optional res = view->getNumber("Drop how many " + items[0]->getName(true, creature->isBlind()) + "?", num); + if (!res) + return; + num = *res; + } + messageBuffer.addMessage("You drop " + getPluralName(items[0], num)); + creature->drop(getPrefix(items, 0, num)); +} + +void Player::onItemsAppeared(vector items) { + vector names; + vector > groups; + getItemNames(items, names, groups); + CHECK(!names.empty()); + Optional index = view->chooseFromList("Do you want to take it?", names); + if (!index) { + return; + } + int num = groups[*index].size(); //groups[index].size() == 1 ? 1 : howMany(view, groups[index].size()); + if (num < 1) + return; + messageBuffer.addMessage("You take " + getPluralName(groups[*index][0], num)); + creature->pickUp(getPrefix(groups[*index], 0, num)); +} + +void Player::applyAction() { + vector items = chooseItem("Choose an item to apply:", [this](Item* item) { + return ( + item->getType() == ItemType::TOOL || + item->getType() == ItemType::POTION || + item->getType() == ItemType::FOOD || + item->getType() == ItemType::BOOK || + item->getType() == ItemType::SCROLL) + && creature->canApplyItem(item);}); + if (items.size() == 0) + return; + if (creature->isBlind() && contains({ItemType::SCROLL, ItemType::BOOK}, items[0]->getType())) { + privateMessage("You can't read while blind!"); + return; + } + if (items[0]->getApplyTime() > 1) { + for (const Creature* c : creature->getVisibleEnemies()) + if ((c->getPosition() - creature->getPosition()).length8() < 3 && + !view->yesOrNoPrompt("Applying " + items[0]->getAName() + " takes " + + convertToString(items[0]->getApplyTime()) + " turns. Are you sure you want to continue?")) + return; + } + if (creature->canApplyItem(items[0])) { + privateMessage("You " + items[0]->getApplyMsgFirstPerson()); + creature->applyItem(items[0]); + } +} + +void Player::throwAction(Optional dir) { + vector items = chooseItem("Choose an item to throw:", [this](Item* item) { + return !creature->getEquipment().isEquiped(item);}); + if (items.size() == 0) + return; + if (!dir) { + auto cDir = view->chooseDirection("Which direction do you want to throw?"); + if (!cDir) + return; + dir = *cDir; + } + if (creature->canThrowItem(items[0])) { + messageBuffer.addMessage("You throw " + items[0]->getAName(false, creature->isBlind())); + creature->throwItem(items[0], *dir); + } +} + +void Player::equipmentAction() { + vector slots; + for (auto slot : slotTitles) + slots.push_back(slot.first); + int index = 0; + creature->startEquipChain(); + while (1) { + vector list; + for (auto slot : slots) { + list.push_back(View::getTitlePrefix(slotTitles.at(slot))); + Item* item = creature->getEquipment().getItem(slot); + if (item) + list.push_back(item->getNameAndModifiers()); + else + list.push_back("[Nothing]"); + } + Optional newIndex = view->chooseFromList("Equipment", list, index); + if (!newIndex) { + creature->finishEquipChain(); + return; + } + index = *newIndex; + EquipmentSlot slot = slots[index]; + if (Item* item = creature->getEquipment().getItem(slot)) { + if (creature->canUnequip(item)) + creature->unequip(item); + } else { + vector items = chooseItem("Choose an item to equip:", [=](Item* item) { + return item->canEquip() + && !creature->getEquipment().isEquiped(item) + && item->getEquipmentSlot() == slot;}); + if (items.size() == 0) { + continue; + // finishEquipChain(); + // return; + } + // messageBuffer.addMessage("You equip " + items[0]->getTheName()); + if (creature->canEquip(items[0])) { + creature->equip(items[0]); + } + } + } +} + +void Player::grantIdentify(int numItems) { + auto unidentFun = [this](Item* item) { return item->canIdentify() && !item->isIdentified();}; + vector unIded = creature->getEquipment().getItems(unidentFun); + if (unIded.empty()) { + privateMessage("All your posessions are already identified"); + return; + } + if (numItems > unIded.size()) { + privateMessage("You identify all your posessions"); + for (Item* it : unIded) + it->identify(); + } else + for (int i : Range(numItems)) { + vector items = chooseItem("Choose an item to identify:", unidentFun); + if (items.size() == 0) + return; + items[0]->identify(); + privateMessage("You identify " + items[0]->getTheName()); + } +} + +void Player::displayInventory() { + chooseItem("Inventory:", [](Item* item) { return true; }, true); +} + +void Player::hideAction() { + if (creature->canHide()) { + privateMessage("You hide behind the " + creature->getConstSquare()->getName()); + creature->hide(); + } else { + if (!creature->hasSkill(Skill::ambush)) + privateMessage("You don't have this skill."); + else + privateMessage("You can't hide here."); + } +} + +void Player::travelAction() { + if (!creature->canMove(travelDir) || view->travelInterrupt()) { + travelling = false; + return; + } + creature->move(travelDir); + itemsMessage(); + vector enemies = creature->getVisibleEnemies(); + vector ignoreCreatures { "a boar" ,"a deer", "a fox", "a vulture", "a rat", "a jackal", "a boulder" }; + if (enemies.size() > 0) { + for (const Creature* c : enemies) + if (!contains(ignoreCreatures, c->getAName())) { + view->refreshView(creature); + privateMessage("You notice " + c->getAName()); + travelling = false; + return; + } + } + const Location* currentLocation = creature->getLevel()->getLocation(creature->getPosition()); + if (lastLocation != currentLocation && currentLocation != nullptr && currentLocation->hasName()) { + privateMessage("You arrive at " + addAParticle(currentLocation->getName())); + travelling = false; + return; + } + vector squareDirs = creature->getConstSquare()->getTravelDir(); + if (squareDirs.size() != 2) { + travelling = false; + Debug() << "Stopped by multiple routes"; + return; + } + Optional myIndex = findElement(squareDirs, -travelDir); + CHECK(myIndex) << "Bad travel data in square"; + travelDir = squareDirs[(*myIndex + 1) % 2]; +} + +void Player::payDebtAction() { + for (Vec2 v : Vec2::directions8()) + if (const Creature* c = creature->getConstSquare(v)->getCreature()) { + if (int debt = c->getDebt(creature)) { + vector gold = creature->getGold(debt); + if (gold.size() < debt) { + privateMessage("You don't have enough gold to pay."); + } else if (view->yesOrNoPrompt("Buy items for " + convertToString(debt) + " zorkmids?")) { + privateMessage("You pay " + c->getName() + " " + convertToString(debt) + " zorkmids."); + creature->give(c, gold); + } + } else { + Debug() << "No debt " << c->getName(); + } + } +} + +void Player::chatAction() { + vector creatures; + for (Vec2 v : Vec2::directions8()) + if (const Creature* c = creature->getConstSquare(v)->getCreature()) + creatures.push_back(c); + if (creatures.size() == 1) { + privateMessage("You chat with " + creatures[0]->getTheName()); + creature->chatTo(creatures[0]->getPosition() - creature->getPosition()); + } else + if (creatures.size() > 1) { + Optional dir = view->chooseDirection("Which direction?"); + if (!dir) + return; + if (const Creature* c = creature->getConstSquare(*dir)->getCreature()) { + privateMessage("You chat with " + c->getTheName()); + creature->chatTo(*dir); + } + } +} + +void Player::fireAction(Vec2 dir) { + if (creature->canFire(dir)) + creature->fire(dir); +} + +const MapMemory& Player::getMemory(const Level* l) const { + if (l == nullptr) + l = creature->getLevel(); + return (*levelMemory)[l]; +} + +void Player::remember(Vec2 pos, const ViewObject& object) { + (*levelMemory)[creature->getLevel()].addObject(pos, object); +} + + +void Player::makeMove() { + vector squareDirs = creature->getConstSquare()->getTravelDir(); + const vector& creatures = creature->getLevel()->getAllCreatures(); + if (creature->isHallucinating()) + ViewObject::setHallu(true); + else + ViewObject::setHallu(false); + MEASURE( + view->refreshView(creature), + "level render time"); + static bool greeting = false; + if (displayGreeting) { + CHECK(creature->getFirstName()); + view->presentText("", "Dear " + *creature->getFirstName() + ",\n \n \tIf you are reading this letter, then you have arrived in the valley of " + NameGenerator::worldNames.getNext() + ". There is a band of dwarves dwelling in caves under a mountain. Find them, talk to them, they will help you. Let your sword guide you.\n \n \nYours, " + NameGenerator::firstNames.getNext() + "\n \nPS.: Beware the goblins!"); + displayGreeting = false; + } + for (const Creature* c : creature->getVisibleEnemies()) { + if (c->isSpecialMonster() && !contains(specialCreatures, c)) { + privateMessage(MessageBuffer::important(c->getDescription())); + specialCreatures.push_back(c); + } + } + if (travelling) + travelAction(); + else { + Action action = view->getAction(); + Vec2 direction(-100, -100); + bool travel = false; + switch (action.getId()) { + case ActionId::FIRE: fireAction(action.getDirection()); break; + case ActionId::TRAVEL: travel = true; + case ActionId::MOVE: direction = action.getDirection(); break; + case ActionId::SHOW_INVENTORY: displayInventory(); break; + case ActionId::PICK_UP: pickUpAction(false); break; + case ActionId::EXT_PICK_UP: pickUpAction(true); break; + case ActionId::DROP: dropAction(false); break; + case ActionId::EXT_DROP: dropAction(true); break; + case ActionId::WAIT: creature->wait(); break; + case ActionId::APPLY_ITEM: applyAction(); break; + case ActionId::THROW: throwAction(); break; + case ActionId::THROW_DIR: throwAction(action.getDirection()); break; + case ActionId::EQUIPMENT: equipmentAction(); break; + case ActionId::HIDE: hideAction(); break; + case ActionId::PAY_DEBT: payDebtAction(); break; + case ActionId::CHAT: chatAction(); break; + case ActionId::SHOW_HISTORY: messageBuffer.showHistory(); break; + case ActionId::UNPOSSESS: creature->popController(); return; break; + case ActionId::IDLE: break; + } + if (creature->isSleeping() && creature->canPopController()) { + creature->popController(); + return; + } + if (direction.x > -100) { + if (travel) { + vector squareDirs = creature->getConstSquare()->getTravelDir(); + if (findElement(squareDirs, direction)) { + travelDir = direction; + lastLocation = creature->getLevel()->getLocation(creature->getPosition()); + travelling = true; + travelAction(); + } + } else + if (creature->canMove(direction)) { + creature->move(direction); + itemsMessage(); + } else { + const Creature *c = creature->getConstSquare(direction)->getCreature(); + if (creature->canBumpInto(direction) && creature->isEnemy(c)) + creature->bumpInto(direction); + } + } + } + for (Vec2 pos : creature->getLevel()->getVisibleTiles(creature)) { + ViewIndex index = creature->getLevel()->getSquare(pos)->getViewIndex(creature); + (*levelMemory)[creature->getLevel()].clearSquare(pos); + for (ViewLayer l : { ViewLayer::ITEM, ViewLayer::FLOOR, ViewLayer::LARGE_ITEM}) + if (index.hasObject(l)) + remember(pos, index.getObject(l)); + } +} + +bool Player::isPlayer() const { + return true; +} + +void Player::privateMessage(const string& message) const { + messageBuffer.addMessage(message); +} + +void Player::you(const string& param) const { + privateMessage("You " + param); +} + +void Player::you(MsgType type, const string& param) const { + string msg; + switch (type) { + case MsgType::ARE: msg = "You are " + param; break; + case MsgType::YOUR: msg = "Your " + param; break; + case MsgType::FEEL: msg = "You feel " + param; break; + case MsgType::FALL_ASLEEP: msg = "You fall asleep" + (param.size() > 0 ? " on the " + param : "."); break; + case MsgType::WAKE_UP: msg = "You wake up."; break; + case MsgType::FALL_APART: msg = "You fall apart."; break; + case MsgType::FALL: msg = "You fall on the " + param; break; + case MsgType::DIE: messageBuffer.addMessage("You die!!"); break; + case MsgType::TELE_DISAPPEAR: msg = "You are standing somewhere else!"; break; + case MsgType::BLEEDING_STOPS: msg = "Your bleeding stops."; break; + case MsgType::DIE_OF_BLEEDING: msg = "You die of wounds."; break; + case MsgType::MISS_ATTACK: msg = "You miss " + param; break; + case MsgType::MISS_THROWN_ATTACK: msg = param + " misses you"; break; + case MsgType::HIT_THROWN_ITEM: msg = param + " hits you"; break; + case MsgType::CRASH_THROWN_ITEM: msg = param + " crashes on your head"; break; + case MsgType::GET_HIT_NODAMAGE: msg = "The " + param + " is harmless."; break; + case MsgType::COLLAPSE: msg = "You collapse."; break; + case MsgType::TRIGGER_TRAP: msg = "You trigger something."; break; + case MsgType::SWING_WEAPON: msg = "You swing your " + param; break; + case MsgType::THRUST_WEAPON: msg = "You thrust your " + param; break; + case MsgType::ATTACK_SURPRISE: msg = "You sneak attack " + param; break; + case MsgType::KICK: msg = "You kick " + param; break; + case MsgType::BITE: msg = "You bite " + param; break; + case MsgType::PUNCH: msg = "You punch " + param; break; + case MsgType::PANIC: + msg = !creature->isHallucinating() ? "You are suddenly very afraid" : "You freak out completely"; break; + case MsgType::RAGE: + msg = !creature->isHallucinating() ?"You are suddenly very angry" : "This will be a very long trip."; break; + case MsgType::CRAWL: msg = "You are crawling"; break; + case MsgType::STAND_UP: msg = "You are back on your feet"; break; + case MsgType::CAN_SEE_HIDING: msg = param + " can see you hiding"; break; + case MsgType::TURN_INVISIBLE: msg = "You can see through yourself!"; break; + case MsgType::TURN_VISIBLE: msg = "You are no longer invisible"; break; + case MsgType::DROP_WEAPON: msg = "You drop your " + param; break; + case MsgType::ITEM_CRASHES: msg = param + " crashes on you."; break; + case MsgType::ENTER_PORTAL: msg = "You enter the portal"; break; + case MsgType::HAPPENS_TO: msg = param + " you."; break; + case MsgType::BURN: msg = "You burn in the " + param; break; + case MsgType::DROWN: msg = "You drown in the " + param; break; + case MsgType::SET_UP_TRAP: msg = "You set up the trap"; break; + case MsgType::KILLED_BY: msg = "You are killed by " + param; break; + default: break; + } + messageBuffer.addMessage(msg); +} + + +void Player::onKilled(const Creature* attacker) { + messageBuffer.showHistory(); + if (!creature->canPopController()) { + exit(0); + } else + creature->popController(); +} diff --git a/player.h b/player.h new file mode 100644 index 000000000..cdac4f048 --- /dev/null +++ b/player.h @@ -0,0 +1,65 @@ +#ifndef _PLAYER_H +#define _PLAYER_H + +#include "creature.h" + +class View; + +class Player : public Controller, public EventListener { + public: + Player(Creature*, View*, bool displayGreeting, map* levelMemory); + virtual ~Player(); + virtual void grantIdentify(int numItems) override; + + virtual bool isPlayer() const override; + + virtual void you(MsgType type, const string& param) const override; + virtual void you(const string& param) const override; + virtual void privateMessage(const string& message) const override; + + virtual void onKilled(const Creature* attacker) override; + virtual void onItemsAppeared(vector items) override; + + View* getView(); + + const MapMemory& getMemory(const Level* l = nullptr) const; + virtual void makeMove() override; + + virtual void onBump(Creature*); + + static ControllerFactory getFactory(View*, map* levelMemory); + + virtual const Level* getListenerLevel() const override; + virtual void onThrowEvent(const Creature* thrower, const Item* item, const vector& trajectory) override; + + + private: + void remember(Vec2 pos, const ViewObject& object); + void pickUpAction(bool extended); + void itemsMessage(); + void dropAction(bool extended); + void equipmentAction(); + void applyAction(); + void throwAction(Optional dir = Nothing()); + void takeOffAction(); + void hideAction(); + void displayInventory(); + void travelAction(); + void payDebtAction(); + void chatAction(); + void fireAction(Vec2 dir); + vector chooseItem(const string& text, function predicate, bool displayOnly = false, + Optional otherOption = Nothing()); + void getItemNames(vector items, vector& names, vector >& groups); + string getPluralName(Item* item, int num); + Creature* creature; + View* view = nullptr; + bool travelling = false; + Vec2 travelDir; + const Location* lastLocation; + vector specialCreatures; + bool displayGreeting; + map* levelMemory; +}; + +#endif diff --git a/poison_gas.cpp b/poison_gas.cpp new file mode 100644 index 000000000..8d0d46410 --- /dev/null +++ b/poison_gas.cpp @@ -0,0 +1,32 @@ +#include "stdafx.h" + +void PoisonGas::addAmount(double a) { + CHECK(a > 0); + amount = min(3., a + amount); +} + +const double decrease = 0.002; +const double spread = 0.15; + +void PoisonGas::tick(Level* level, Vec2 pos) { + if (amount < 0.00001) { + amount = 0; + return; + } + for (Vec2 v : Vec2::directions8(true)) { + Square* square = level->getSquare(pos + v); + if (square->canSeeThru() && amount > 0 && square->getPoisonGasAmount() < amount) { + double transfer = v.isCardinal4() ? spread : spread / 2; + transfer = min(amount, transfer); + transfer = min((amount - square->getPoisonGasAmount()) / 2, transfer); + amount -= transfer; + level->getSquare(v + pos)->addPoisonGas(transfer); + } + } + amount = max(0.0, amount - decrease); +} + +double PoisonGas::getAmount() const { + return amount; +} + diff --git a/poison_gas.h b/poison_gas.h new file mode 100644 index 000000000..633210261 --- /dev/null +++ b/poison_gas.h @@ -0,0 +1,17 @@ +#ifndef _POISON_GAS_H +#define _POISON_GAS_H + +#include "util.h" + +class PoisonGas { + public: + void addAmount(double amount); + void tick(Level*, Vec2 pos); + double getAmount() const; + + private: + double amount = 0; +}; + +#endif + diff --git a/quest.cpp b/quest.cpp new file mode 100644 index 000000000..454defd93 --- /dev/null +++ b/quest.cpp @@ -0,0 +1,36 @@ +#include "stdafx.h" + +using namespace std; + +Quest::Quest() : state(State::CLOSED) {} + +bool Quest::isClosed() const { + return state == State::CLOSED; +} + +bool Quest::isOpen() const { + return state == State::OPEN; +} + +bool Quest::isFinished() const { + return state == State::FINISHED; +} + +void Quest::open() { + state = State::OPEN; +} + +void Quest::finish() { + Debug() << "Quest finished"; + state = State::FINISHED; +} + +void Quest::initialize() { + quests.insert(make_pair(QuestName::GOLD, Quest())); +} + +Quest& Quest::get(QuestName name) { + TRY(return quests.at(name), "Unknown quest."); +} + +map Quest::quests; diff --git a/quest.h b/quest.h new file mode 100644 index 000000000..da2d4b55d --- /dev/null +++ b/quest.h @@ -0,0 +1,27 @@ +#ifndef _QUEST_H +#define _QUEST_H + +#include +#include + +#include "util.h" + +enum class QuestName { GOLD }; + +class Quest { + public: + Quest(); + void open(); + void finish(); + bool isOpen() const; + bool isFinished() const; + bool isClosed() const; + static void initialize(); + static Quest& get(QuestName); + + private: + enum class State { CLOSED, OPEN, FINISHED } state; + static map quests; +}; + +#endif diff --git a/ranged_weapon.cpp b/ranged_weapon.cpp new file mode 100644 index 000000000..3d7e7b943 --- /dev/null +++ b/ranged_weapon.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" + + +RangedWeapon::RangedWeapon(ViewObject o, const ItemAttributes& attr) : Item(o, attr) {} + +void RangedWeapon::fire(Creature* c, Level* l, PItem ammo, Vec2 dir) { + int toHitVariance = 10; + int attackVariance = 15; + int toHit = Random.getRandom(-toHitVariance, toHitVariance) + + c->getAttr(AttrType::THROWN_TO_HIT) + + ammo->getModifier(AttrType::THROWN_TO_HIT) + + getAccuracy(); + int damage = Random.getRandom(-attackVariance, attackVariance) + + c->getAttr(AttrType::THROWN_DAMAGE) + + ammo->getModifier(AttrType::THROWN_DAMAGE); + l->throwItem(std::move(ammo), Attack(c, chooseRandom({AttackLevel::LOW, AttackLevel::MIDDLE, AttackLevel::HIGH}), + AttackType::SHOOT, toHit, damage), + 20, c->getPosition(), dir); +} + diff --git a/ranged_weapon.h b/ranged_weapon.h new file mode 100644 index 000000000..97a4b799e --- /dev/null +++ b/ranged_weapon.h @@ -0,0 +1,13 @@ +#ifndef _RANGED_WEAPON +#define _RANGED_WEAPON + +#include "item.h" + +class RangedWeapon : public Item { + public: + RangedWeapon(ViewObject, const ItemAttributes&); + + virtual void fire(Creature*, Level*, PItem ammo, Vec2 dir); +}; + +#endif diff --git a/replay_view.h b/replay_view.h new file mode 100644 index 000000000..910bc332b --- /dev/null +++ b/replay_view.h @@ -0,0 +1,84 @@ +#ifndef _REPLAY_VIEW +#define _REPLAY_VIEW + + +template +class ReplayView : public T { + public: + ReplayView(ifstream& iff) : input(iff) { + } + + virtual void close() override { + input.close(); + T::close(); + } + + virtual void addMessage(const string&) override { + } + + virtual void presentList(const string& title, const vector& options, bool scrollDown) override { + } + + virtual void addImportantMessage(const string&) override { + } + + virtual Action getAction() override { + T::getAction(); + // usleep(300000); + string method; + Action action; + input >> method >> action; + if (method == "exit") + exit(0); + CHECKEQ(method, "getAction"); + return action; + } + + virtual Optional chooseFromList(const string& title, const vector& options, int index) override { + string method; + string action; + input >> method >> action; + CHECKEQ(method, "chooseFromList"); + if (action == "nothing") + return Nothing(); + else + return convertFromString(action); + } + + virtual Optional chooseDirection(const string& message) override { + string method; + string action; + input >> method >> action; + CHECKEQ(method, "chooseDirection"); + if (action == "nothing") + return Nothing(); + else { + vector s = split(action, ','); + CHECKEQ((int)s.size(), 2); + return Vec2(convertFromString(s[0]), convertFromString(s[1])); + } + } + + virtual bool yesOrNoPrompt(const string& message) override { + string method; + bool action; + input >> method >> action; + CHECKEQ(method, "yesOrNoPrompt"); + return action; + } + + virtual Optional getNumber(const string& title, int max) { + string method; + string action; + input >> method >> action; + CHECKEQ(method, "getNumber"); + if (action == "nothing") + return Nothing(); + else + return convertFromString(action); + } + private: + ifstream& input; +}; + +#endif diff --git a/shortest_path.cpp b/shortest_path.cpp new file mode 100644 index 000000000..a3bdf6c02 --- /dev/null +++ b/shortest_path.cpp @@ -0,0 +1,167 @@ +#include "stdafx.h" + +using namespace std; + +const double ShortestPath::infinity = 1000000000; + +const int revShortestLimit = 15; + +static Table initDistTable(Vec2 to, Vec2 from, int levelWidth, int levelHeight) { + int margin = 20; + Vec2 p(max(0, min(to.x, from.x) - margin), + max(0, min(to.y, from.y) - margin)); + Vec2 k(min(levelWidth - p.x, abs(to.x - from.x) + 2 * margin), + min(levelHeight - p.y, abs(to.y - from.y) + 2 * margin)); + return Table(Rectangle(p, p + k), ShortestPath::infinity); +} + +ShortestPath::ShortestPath(const Level* level, const Creature* creature, Vec2 to, Vec2 from, double mult) : + distance(initDistTable(to, from, level->getWidth(), level->getHeight())), + target(to), + directions(Vec2::directions8()) { + auto entryFun = [level, creature](Vec2 pos) { + if (level->getSquare(pos)->canEnter(creature) || creature->getPosition() == pos) + return 1.0; + if (level->getSquare(pos)->canEnterEmpty(creature) || level->getSquare(pos)->canDestroy()) + return 5.0; + return infinity;}; + auto lengthFun = [](Vec2 v) { return v.length8(); }; + if (mult == 0) + init(entryFun, lengthFun, target, from); + else { + init(entryFun, lengthFun, target, Nothing(), revShortestLimit); + setDistance(target, infinity); + reverse(entryFun, lengthFun, mult, from, revShortestLimit); + } +} + + +ShortestPath::ShortestPath(Rectangle a, function entryFun, function lengthFun, vector dir, Vec2 to, Optional from, double mult) : + distance(a.getPX(), a.getPY(), a.getW(), a.getH(), infinity), + target(to), directions(dir) { + if (mult == 0) + init(entryFun, lengthFun, target, from); + else { + init(entryFun, lengthFun, target, Nothing(), revShortestLimit); + setDistance(target, infinity); + reverse(entryFun, lengthFun, mult, from, revShortestLimit); + } +} + +double ShortestPath::getDistance(Vec2 v) const { + return distance[v]; +} +void ShortestPath::setDistance(Vec2 v, double d) { + distance[v] = d; +} + +void ShortestPath::init(function entryFun, function lengthFun, Vec2 target, Optional from, Optional limit) { + reversed = false; + function comparator; + if (from) + comparator = [=](Vec2 pos1, Vec2 pos2) { return this->getDistance(pos1) + lengthFun(*from - pos1) > this->getDistance(pos2) + lengthFun(*from - pos2); }; + else + comparator = [this](Vec2 pos1, Vec2 pos2) { return this->getDistance(pos1) > this->getDistance(pos2); }; + priority_queue, decltype(comparator)> q(comparator) ; + setDistance(target, 0); + q.push(target); + int numPopped = 0; + while (!q.empty()) { + ++numPopped; + Vec2 pos = q.top(); + // Debug() << "Popping " << pos << " " << distance[pos] << " " << (from ? (*from - pos).length4() : 0); + if (from == pos || (limit && getDistance(pos) >= *limit)) { + Debug() << "Shortest path from " << (from ? *from : Vec2(-1,-1)) << " to " << target << " " << numPopped << " visited"; + return; + } + q.pop(); + for (Vec2 dir : directions) { + Vec2 next = pos + dir; + if (next.inRectangle(distance.getBounds())) { + double cdist = getDistance(pos); + double ndist = getDistance(next); + if (cdist < ndist) { + double dist = cdist + entryFun(next); + CHECK(dist > cdist) << "Entry fun non positive " << dist - cdist; + if (dist < ndist) { + setDistance(next, dist); + q.push(next); + } + } + } + } + } +// if (from) + Debug() << "Shortest path exhausted, " << numPopped << " visited"; +} + +void ShortestPath::reverse(function entryFun, function lengthFun, double mult, Optional from, int limit) { + reversed = true; + function comparator; + if (from) + comparator = [=](Vec2 pos1, Vec2 pos2) { return this->getDistance(pos1) + lengthFun(*from - pos1) > this->getDistance(pos2) + lengthFun(*from - pos2); }; + else + comparator = [this](Vec2 pos1, Vec2 pos2) { return this->getDistance(pos1) > this->getDistance(pos2); }; + + priority_queue, decltype(comparator)> q(comparator) ; + for (Vec2 v : distance.getBounds()) { + double dist = distance[v]; + if (dist <= limit) { + setDistance(v, mult * dist); + q.push(v); + } + } + int numPopped = 0; + while (!q.empty()) { + ++numPopped; + Vec2 pos = q.top(); + if (from == pos) { + Debug() << "Rev shortest path from " << " from " << target << " " << numPopped << " visited"; + return; + } + q.pop(); + for (Vec2 dir : directions) + if ((pos + dir).inRectangle(distance.getBounds())) { + if (getDistance(pos + dir) > distance[pos] + entryFun(pos + dir) && distance[pos + dir] < 0) { + distance[pos + dir] = distance[pos] + entryFun(pos + dir); + q.push(pos + dir); + } + } + } + Debug() << "Rev shortest path from " << " from " << target << " " << numPopped << " visited"; +} + +bool ShortestPath::isReversed() const { + return reversed; +} + +bool ShortestPath::isReachable(Vec2 pos) const { + if (!pos.inRectangle(distance.getBounds())) + return false; + if (getDistance(pos) == infinity) + return false; + for (Vec2 dir : directions) + if ((pos + dir).inRectangle(distance.getBounds()) && getDistance(pos + dir) < getDistance(pos)) + return true; + return false; +} + +Vec2 ShortestPath::getNextMove(Vec2 pos) const { + CHECK(isReachable(pos)); + CHECK(pos != target) << "Already arrived at target."; + double lowest = getDistance(pos); + Vec2 next; + for (Vec2 dir : directions) { + double dist; + if ((pos + dir).inRectangle(distance.getBounds()) && (dist = getDistance(pos + dir)) < lowest) { + lowest = dist; + next = pos + dir; + } + } + CHECK(lowest < getDistance(pos)) << "Unable to continue shortest path."; + return next; +} + +Vec2 ShortestPath::getTarget() const { + return target; +} diff --git a/shortest_path.h b/shortest_path.h new file mode 100644 index 000000000..a25b8912a --- /dev/null +++ b/shortest_path.h @@ -0,0 +1,42 @@ +#ifndef _SHORTEST_PATH_H +#define _SHORTEST_PATH_H + +#include + +#include "util.h" + +class Creature; +class Level; + +class ShortestPath { + public: + ShortestPath(const Level* level, const Creature* creature, Vec2 target, Vec2 from, double mult = 0); + ShortestPath( + Rectangle area, + function entryFun, + function lengthFun, + vector directions, + Vec2 target, + Optional from = Nothing(), + double mult = 0); + ShortestPath(ShortestPath&&) = default; + ShortestPath& operator=(ShortestPath&&) = default; + bool isReachable(Vec2 pos) const; + double getDistance(Vec2 pos) const; + Vec2 getNextMove(Vec2 pos) const; + Vec2 getTarget() const; + bool isReversed() const; + + static const double infinity; + + private: + void init(function entryFun, function lengthFun, Vec2 target, Optional from, Optional limit = Nothing()); + void reverse(function entryFun, function lengthFun, double mult, Optional from, int limit); + void setDistance(Vec2, double); + Table distance; + Vec2 target; + vector directions; + bool reversed; +}; + +#endif diff --git a/skill.cpp b/skill.cpp new file mode 100644 index 000000000..f8f4d3f05 --- /dev/null +++ b/skill.cpp @@ -0,0 +1,46 @@ +#include "stdafx.h" + +string Skill::getName() const { + return name; +} + +string Skill::getHelpText() const { + return helpText; +} + +class IdentifySkill : public Skill { + public: + IdentifySkill(string name, string helpText, ItemFactory f) : Skill(name, helpText), factory(f) {} + + virtual void onTeach(Creature* c) override { + string message; + for (PItem& it : factory.getAll()) { + Item::identify(it->getName()); + message.append(it->getName() + ": " + it->getDescription() + "\n"); + } + // c->privateMessage(MessageBuffer::important(message)); + } + + private: + ItemFactory factory; +}; + +Skill* const Skill::ambush = new Skill("skill of ambush" ,"Press \'h\' to hide and attack unsuspecting enemies."); +Skill* const Skill::twoHandedWeapon = new Skill("skill of fighting with two-handed weapons", + "You can now fight with warhammers and battle axes."); +Skill* const Skill::knifeThrowing = new Skill("skill of knife throwing", + "You can now throw knives with deadly precision."); +Skill* const Skill::stealing = new Skill("skill of stealing", "Not available"); +Skill* const Skill::mushrooms = new IdentifySkill("knowledge of mushrooms", + "You now know the types of mushrooms and their use.", ItemFactory::mushrooms()); +Skill* const Skill::potions = new IdentifySkill("knowledge of potions", + "You now know the types of potions and their use.", ItemFactory::potions()); +Skill* const Skill::amulets = new IdentifySkill("knowledge of amulets", + "You now know the types of amulets and their use.", ItemFactory::amulets()); +Skill* const Skill::swimming = new Skill("skill of swimming", ""); +Skill* const Skill::archery = new Skill("skill of archery", + "You can now equip a bow and use \'alt + arrow\' to fire"); +Skill* const Skill::construction = new Skill("skill of construction", ""); + +Skill::Skill(string _name, string _helpText) : name(_name), helpText(_helpText) {} + diff --git a/skill.h b/skill.h new file mode 100644 index 000000000..98cef6980 --- /dev/null +++ b/skill.h @@ -0,0 +1,33 @@ +#ifndef _SKILL_H +#define _SKILL_H + +#include + +class Skill { + public: + string getName() const; + string getHelpText() const; + + virtual void onTeach(Creature* c) {} + + static Skill* const ambush; + static Skill* const twoHandedWeapon; + static Skill* const knifeThrowing; + static Skill* const stealing; + static Skill* const mushrooms; + static Skill* const potions; + static Skill* const amulets; + static Skill* const swimming; + static Skill* const archery; + static Skill* const construction; + + protected: + Skill(string name, string helpText); + + private: + string name; + string helpText; +}; + + +#endif diff --git a/square.cpp b/square.cpp new file mode 100644 index 000000000..545064a5a --- /dev/null +++ b/square.cpp @@ -0,0 +1,339 @@ +#include "stdafx.h" + +using namespace std; + +Square::Square(const ViewObject& vo, const string& n, bool see, bool canHide, int s, double f, + map construct, bool tick) + : name(n), viewObject(vo), seeThru(see), hide(canHide), strength(s), fire(strength, f), + constructions(construct), ticking(tick) { +} + +void Square::putCreature(Creature* c) { + CHECK(canEnter(c)); + creature = c; + onEnter(c); +} + +void Square::setPosition(Vec2 v) { + position = v; +} + +Vec2 Square::getPosition() const { + return position; +} + +string Square::getName() const { + return name; +} + +void Square::setName(const string& s) { + name = s; +} + +void Square::setLandingLink(StairDirection direction, StairKey key) { + landingLink = make_pair(direction, key); +} + +bool Square::isLandingSquare(StairDirection direction, StairKey key) { + return landingLink == make_pair(direction, key); +} + +Optional> Square::getLandingLink() const { + return landingLink; +} + +void Square::setCovered(bool c) { + covered = c; +} + +bool Square::isCovered() const { + return covered; +} + +void Square::setHeight(double h) { + viewObject.setHeight(h); + height = h; +} + +void Square::addTravelDir(Vec2 dir) { + if (!findElement(travelDir, dir)) + travelDir.push_back(dir); +} + +bool Square::canConstruct(SquareType type) const { + return constructions.count(type); +} + +void Square::construct(SquareType type) { + CHECK(canConstruct(type)); + if (--constructions[type] == 0) { + PSquare newSquare = PSquare(SquareFactory::get(type)); + onConstructNewSquare(newSquare.get()); + level->replaceSquare(position, std::move(newSquare)); + } +} + +const vector& Square::getTravelDir() const { + return travelDir; +} + +void Square::putCreatureSilently(Creature* c) { + CHECK(canEnter(c)); + creature = c; +} + +void Square::setLevel(Level* l) { + level = l; + if (ticking || !inventory.isEmpty()) + level->addTickingSquare(position); +} + +const Level* Square::getConstLevel() const { + return level; +} + +Level* Square::getLevel() { + return level; +} + +void Square::setViewObject(const ViewObject& o) { + viewObject = o; +} + +void Square::tick(double time) { + if (!inventory.isEmpty()) + for (Item* item : inventory.getItems()) { + item->tick(time, level, position); + if (item->isDiscarded()) + inventory.removeItem(item); + } + poisonGas.tick(level, position); + if (creature && poisonGas.getAmount() > 0.2) { + creature->poisonWithGas(min(1.0, poisonGas.getAmount())); + } + if (fire.isBurning()) { + viewObject.setBurning(fire.getSize()); + Debug() << getName() << " burning " << fire.getSize(); + for (Vec2 v : position.neighbors8(true)) + if (fire.getSize() > Random.getDouble() * 60) + level->getSquare(v)->setOnFire(fire.getSize() / 20); + fire.tick(level, position); + if (fire.isBurntOut()) { + level->globalMessage(position, "The " + getName() + " burns out"); + burnOut(); + } + if (creature) + creature->setOnFire(fire.getSize()); + for (Item* it : getItems()) + it->setOnFire(fire.getSize(), level, position); + } + for (PTrigger& t : triggers) + t->tick(time); + tickSpecial(time); +} + +bool Square::itemLands(Item* item, Attack attack) { + if (creature) { + if (!creature->dodgeAttack(attack)) + return true; + else { + creature->you(MsgType::MISS_THROWN_ATTACK, item->getTheName()); + return false; + } + } + for (PTrigger& t : triggers) + if (t->interceptsFlyingItem(item)) + return true; + return false; +} + +bool Square::itemBounces(Item* item) const { + return !canEnterEmpty(Creature::getDefault()); +} + +void Square::onItemLands(PItem item, Attack attack, int remainingDist, Vec2 dir) { + if (creature) { + item->onHitCreature(creature, attack); + if (!item->isDiscarded()) + dropItem(std::move(item)); + return; + } + for (PTrigger& t : triggers) + if (t->interceptsFlyingItem(item.get())) { + t->onInterceptFlyingItem(std::move(item), attack, remainingDist, dir); + return; + } + + item->onHitSquare(position, this); + if (!item->isDiscarded()) + dropItem(std::move(item)); +} + +bool Square::canEnter(const Creature* c) const { + return creature == nullptr && canEnterSpecial(c); +} + +bool Square::canEnterEmpty(const Creature* c) const { + return canEnterSpecial(c); +} + +bool Square::canEnterSpecial(const Creature* c) const { + return c->canWalk(); +} + +void Square::setOnFire(double amount) { + bool burning = fire.isBurning(); + fire.set(amount); + if (!burning && fire.isBurning()) { + level->addTickingSquare(position); + level->globalMessage(position, "The " + getName() + " catches fire."); + viewObject.setBurning(fire.getSize()); + } + if (creature) + creature->setOnFire(amount); + for (Item* it : getItems()) + it->setOnFire(amount, level, position); +} + +void Square::addPoisonGas(double amount) { + if (canSeeThru()) { + poisonGas.addAmount(amount); + level->addTickingSquare(position); + } +} + +double Square::getPoisonGasAmount() const { + return poisonGas.getAmount(); +} + +bool Square::isBurnt() const { + return fire.isBurntOut(); +} + +ViewObject Square::getViewObject() const { + return viewObject; +} + +static ViewObject addFire(const ViewObject& obj, double fire) { + ViewObject r(obj); + r.setBurning(fire); + return r; +} + +ViewIndex Square::getViewIndex(const CreatureView* c) const { + double fireSize = 0; + for (Item* it : inventory.getItems()) + fireSize = max(fireSize, it->getFireSize()); + fireSize = max(fireSize, fire.getSize()); + ViewIndex ret; + if (creature && (c->canSee(creature) || creature->isPlayer())) { + ret.insert(addFire(creature->getViewObject(), fireSize)); + } + else if (creature && contains(c->getUnknownAttacker(), creature)) + ret.insert(addFire(ViewObject::unknownMonster(), fireSize)); + + if (c->canSee(position)) { + ret.insert(getViewObject()); + for (const PTrigger& t : triggers) + if (auto obj = t->getViewObject()) + ret.insert(addFire(*obj, fireSize)); + if (Item* it = getTopItem()) + ret.insert(addFire(it->getViewObject(), fireSize)); + } + if (poisonGas.getAmount() > 0) + ret.setHighlight(HighlightType::POISON_GAS, min(1.0, poisonGas.getAmount())); + return ret; +} + +void Square::onEnter(Creature* c) { + for (PTrigger& t : triggers) + t->onCreatureEnter(c); + onEnterSpecial(c); +} + +void Square::dropItem(PItem item) { + if (level) // if level == null, then it's being constructed, square will be added later + level->addTickingSquare(getPosition()); + inventory.addItem(std::move(item)); +} + +void Square::dropItems(vector items) { + for (PItem& it : items) + dropItem(std::move(it)); +} + +bool Square::hasItem(Item* it) const { + return inventory.hasItem(it); +} + +Creature* Square::getCreature() { + return creature; +} + +void Square::addTrigger(PTrigger t) { + level->addTickingSquare(position); + triggers.push_back(std::move(t)); +} + +PTrigger Square::removeTrigger(Trigger* trigger) { + PTrigger ret; + for (PTrigger& t : triggers) + if (t.get() == trigger) { + ret = std::move(t); + removeElement(triggers, t); + } + return ret; +} + +const Creature* Square::getCreature() const { + return creature; +} + +void Square::removeCreature() { + CHECK(creature); + creature = 0; +} + +bool SolidSquare::canEnterSpecial(const Creature*) const { + return false; +} + +bool Square::canSeeThru() const { + return seeThru; +} + +void Square::setCanSeeThru(bool f) { + seeThru = f; +} + +bool Square::canHide() const { + return hide; +} + +int Square::getStrength() const { + return strength; +} + +Item* Square::getTopItem() const { + Item* last = nullptr; + if (!inventory.isEmpty()) + for (Item* it : inventory.getItems()) { + last = it; + if (it->getViewObject().layer() == ViewLayer::LARGE_ITEM) + return it; + } + return last; +} + +vector Square::getItems(function predicate) { + return inventory.getItems(predicate); +} + +PItem Square::removeItem(Item* it) { + return inventory.removeItem(it); +} + +vector Square::removeItems(vector it) { + return inventory.removeItems(it); +} + diff --git a/square.h b/square.h new file mode 100644 index 000000000..425323629 --- /dev/null +++ b/square.h @@ -0,0 +1,214 @@ +#ifndef _SQUARE_H +#define _SQUARE_H + +#include +#include +#include "util.h" +#include "item.h" +#include "creature.h" +#include "debug.h" +#include "inventory.h" +#include "trigger.h" +#include "view_index.h" +#include "poison_gas.h" + +class Level; + +class Square { + public: + /** Constructs a square objects. + * \param obstructing true if the square doesn't obstruct view + * \param canHide true if the player can hide at this square + * \param strength square resistance to demolition + */ + Square(const ViewObject& vo, const string& name, bool obstructing, bool canHide = false, + int strength = 0, double flamability = 0, map constructions = {}, bool ticking = false); + + /** Returns the square name. */ + string getName() const; + + /** Sets the square name.*/ + void setName(const string&); + + /** Links this square as point of entry from another level. + * \param direction direction where the creature is coming from + * \param key id specific to a dungeon branch*/ + void setLandingLink(StairDirection direction, StairKey key); + + /** Checks if this square is a point of entry from another level. See setLandingLink().*/ + bool isLandingSquare(StairDirection, StairKey); + + /** Returns the entry point details. Returns nothing if square is not entry point. See setLandingLink().*/ + Optional> getLandingLink() const; + + /** Marks this square as covered, i.e. having a roof.*/ + void setCovered(bool); + + /** Checks if this square is covered, i.e. having a roof.*/ + bool isCovered() const; + + /** Sets the height of the square.*/ + void setHeight(double height); + + /** Adds direction for auto-traveling. \paramname{dir} must point to next square on path.*/ + void addTravelDir(Vec2 dir); + + /** Returns auto-travel directions pointing to the next squares on the path.*/ + const vector& getTravelDir() const; + + /** Sets the square's position on the level.*/ + void setPosition(Vec2 v); + + /** Returns the square's position on the level.*/ + Vec2 getPosition() const; + + /** Checks if this creature can enter the square at the moment. Takes account other creatures on the square.*/ + bool canEnter(const Creature*) const; + + /** Checks if this square is can be entered by the creature. Doesn't take into account other + * creatures on the square.*/ + bool canEnterEmpty(const Creature*) const; + + /** Checks if this square obstructs view.*/ + bool canSeeThru() const; + + /** Sets if this square obstructs view.*/ + void setCanSeeThru(bool); + + /** Checks if the player can hide behind this square.*/ + bool canHide() const; + + /** Returns the strength, i.e. resistance to demolition.*/ + int getStrength() const; + + /** Checks if this square can be destroyed.*/ + virtual bool canDestroy() const { return false; } + + /** Called when something destroyed this square.*/ + virtual void destroy(int strength) { Debug(FATAL) << "Can't destroy " << name; } + + /** Called when this square is burned completely.*/ + virtual void burnOut() {} + + /** Exposes the square and objects on it to fire.*/ + void setOnFire(double amount); + + bool isBurnt() const; + + /** Adds some poison gas to the square.*/ + void addPoisonGas(double amount); + + /** Returns the amount of poison gas on this square.*/ + double getPoisonGasAmount() const; + + /** Sets the level this square is on.*/ + void setLevel(Level*); + + /** Puts a creature on the square.*/ + void putCreature(Creature*); + + /** Puts a creature on the square without triggering any mechanisms that happen when a creature enters.*/ + void putCreatureSilently(Creature*); + + /** Removes the creature from the square.*/ + void removeCreature(); + + //@{ + /** Returns the creature from the square.*/ + Creature* getCreature(); + const Creature* getCreature() const; + //@} + + /** Adds a trigger to the square.*/ + void addTrigger(PTrigger); + + /** Removes the trigger from the square.*/ + PTrigger removeTrigger(Trigger*); + + //@{ + /** Drops item or items on the square. The square assumes ownership.*/ + virtual void dropItem(PItem); + void dropItems(vector); + //@} + + /** Checks if a given item is present on the square.*/ + bool hasItem(Item*) const; + + /** Checks if another square can be constructed from this one.*/ + bool canConstruct(SquareType) const; + + /** Constructs another square. The construction might finish after several attempts.*/ + void construct(SquareType); + + /** Called just before swapping the old square for the new constructed one.*/ + virtual void onConstructNewSquare(Square* newSquare) {} + + /** Triggers all time-dependent processes like burning. Calls tick() for creature and items if present. + For this method to be called, the square coordinates must be added with Level::addTickingSquare().*/ + void tick(double time); + + ViewObject getViewObject() const; + ViewIndex getViewIndex(const CreatureView* c) const; + + bool itemLands(Item* item, Attack attack); + virtual bool itemBounces(Item* item) const; + void onItemLands(PItem item, Attack attack, int remainingDist, Vec2 dir); + vector getItems(function predicate = alwaysTrue()); + PItem removeItem(Item*); + vector removeItems(vector); + + virtual Optional getApplyType(const Creature*) const { return Nothing(); } + virtual void onApply(Creature* c) { Debug(FATAL) << "Bad square applied"; } + + const Level* getConstLevel() const; + + virtual ~Square() {}; + + protected: + void onEnter(Creature*); + virtual bool canEnterSpecial(const Creature*) const; + virtual void onEnterSpecial(Creature*) {} + virtual void tickSpecial(double time) {} + Level* getLevel(); + void setViewObject(const ViewObject&); + vector face; + Inventory inventory; + string name; + + private: + Item* getTopItem() const; + + Level* level = nullptr; + Vec2 position; + Creature* creature = nullptr; + vector triggers; + ViewObject viewObject; + bool seeThru; + bool hide; + int strength; + double height; + vector travelDir; + bool covered = false; + Optional> landingLink; + Fire fire; + PoisonGas poisonGas; + map constructions; + bool ticking; +}; + +class SolidSquare : public Square { + public: + SolidSquare(const ViewObject& vo, const string& name, bool canSee, map constructions = {}, + bool alwaysVisible = false) : + Square(vo, name, canSee, false, 0, 0, constructions) { + } + + protected: + virtual bool canEnterSpecial(const Creature*) const; + + virtual void onEnterSpecial(Creature* c) { + Debug(FATAL) << "Creature entered solid square"; + } +}; + +#endif diff --git a/square_factory.cpp b/square_factory.cpp new file mode 100644 index 000000000..1edf25fc3 --- /dev/null +++ b/square_factory.cpp @@ -0,0 +1,653 @@ +#include "stdafx.h" + +using namespace std; + +class MessageSquare : public Square { + public: + MessageSquare(const ViewObject& obj, const string& name, bool see, bool once, bool hide, const string& msg) : Square(obj, name, see, hide), message(msg) { + state = once ? State::ONCE : State::ALWAYS; + } + + protected: + virtual void onEnterSpecial(Creature* c) override { + if (c->isPlayer() && state != State::NEVER) { + messageBuffer.addMessage(message); + if (state == State::ONCE) + state = State::NEVER; + } + } + + private: + enum class State { ALWAYS, ONCE, NEVER } state; + string message; +}; + +class Staircase : public Square { + public: + Staircase(const ViewObject& obj, const string& name, StairDirection dir, StairKey key) + : Square(obj, name, true, true, 10000) { + setLandingLink(dir, key); + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage("There are " + getName() + " here."); + } + + virtual Optional getApplyType(const Creature*) const override { + switch (getLandingLink()->first) { + case StairDirection::DOWN: return SquareApplyType::DESCEND; + case StairDirection::UP: return SquareApplyType::ASCEND; + } + return Nothing(); + } + + virtual void onApply(Creature* c) override { + auto link = getLandingLink(); + getLevel()->changeLevel(link->first, link->second, c); + } +}; + +class SecretPassage : public Square { + public: + SecretPassage(const ViewObject& obj, const ViewObject& sec) : Square(obj, "secret door", false), secondary(sec), uncovered(false) { + } + + void uncover(Vec2 pos) { + uncovered = true; + setName("floor"); + setViewObject(secondary); + face.clear(); + setCanSeeThru(true); + getLevel()->updateVisibility(pos); + } + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (uncovered) + return; + if (getLevel()->playerCanSee(getPosition())) { + messageBuffer.addMessage("A secret passage is destroyed!"); + uncover(getPosition()); + } + } + + virtual void onEnterSpecial(Creature* c) override { + if (uncovered) + return; + if (c->isPlayer()) { + messageBuffer.addMessage("You found a secret passage!"); + uncover(c->getPosition()); + } else + if (getLevel()->playerCanSee(c->getPosition())) { + messageBuffer.addMessage(c->getTheName() + " uncovers a secret passage!"); + uncover(c->getPosition()); + } + } + + private: + ViewObject secondary; + bool uncovered; +}; + +class Water : public Square { + public: + Water(const ViewObject& object, const string& name, const string& itemMsg, const string& noSee, + MsgType _msgType, bool swimming = true) + : Square(object, name, true, false, 0, 0, {{SquareType::BRIDGE, 20}}), itemMessage(itemMsg), noSeeMsg(noSee), + msgType(_msgType), swimmingAllowed(swimming) {} + + virtual bool canEnterSpecial(const Creature* c) const override { + return (c->canSwim() && swimmingAllowed) || c->canFly() || c->isBlind() || c->isHeld(); + } + + virtual void onEnterSpecial(Creature* c) override { + if (!c->canFly() && (!c->canSwim() || !swimmingAllowed)) { + c->you(msgType, getName()); + c->die(nullptr, false); + } + } + + virtual void dropItem(PItem item) override { + getLevel()->globalMessage(getPosition(), item->getTheName() + " " + itemMessage, noSeeMsg); + } + + virtual bool itemBounces(Item*) const override { + return false; + } + + private: + string itemMessage; + string noSeeMsg; + MsgType msgType; + bool swimmingAllowed; +}; + +class Chest : public Square { + public: + Chest(const ViewObject& object, const string& name, CreatureId id, int minC, int maxC, + const string& _msgItem, const string& _msgMonster, const string& _msgGold, + ItemFactory _itemFactory) : + Square(object, name, true, true, 30, 0.5), creatureId(id), minCreatures(minC), maxCreatures(maxC), msgItem(_msgItem), msgMonster(_msgMonster), msgGold(_msgGold), itemFactory(_itemFactory) {} + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage("There is a " + string(isBurnt() ? "burnt " : destroyed ? "destroyed " : opened ? " opened " : "") + getName() + " here"); + } + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + destroyed = true; + setViewObject(ViewObject(ViewId::DESTROYED_FURNITURE, ViewLayer::FLOOR, string("Destroyed ") + getName())); + getLevel()->globalMessage(getPosition(), "The " + getName() + " is destroyed."); + if (opened) + return; + vector item; + if (!Random.roll(10)) + item.push_back(itemFactory.random()); + else { + for (int i : Range(Random.getRandom(minCreatures, maxCreatures))) + item.push_back(ItemFactory::corpse(creatureId)); + } + dropItems(std::move(item)); + } + + virtual void burnOut() override { + setViewObject(ViewObject(ViewId::BURNT_FURNITURE, ViewLayer::FLOOR, string("Burnt ") + getName())); + destroyed = true; + if (opened) + return; + vector item; + if (!Random.roll(10)) + item.push_back(itemFactory.random()); + else { + for (int i : Range(Random.getRandom(minCreatures, maxCreatures))) + item.push_back(ItemFactory::corpse(creatureId)); + } + dropItems(std::move(item)); + } + + virtual Optional getApplyType(const Creature*) const override { + if (opened || destroyed) + return Nothing(); + else + return SquareApplyType::USE_CHEST; + } + + virtual void onApply(Creature* c) override { + CHECK(!opened); + c->privateMessage("You open the " + getName()); + opened = true; + int r = Random.getRandom(10); + if (r < 5) { + c->privateMessage(msgItem); + PItem item = itemFactory.random(); + Item* it = item.get(); + dropItem(std::move(item)); + EventListener::addItemsAppeared(getLevel(), getPosition(), {it}); + c->onItemsAppeared({it}); + } else if (r < 9) { + c->privateMessage(msgGold); + vector items = ItemFactory::fromId(ItemId::GOLD_PIECE, Random.getRandom(5, 16)); + vector it; + for (PItem& e : items) + it.push_back(e.get()); + dropItems(std::move(items)); + EventListener::addItemsAppeared(getLevel(),getPosition(), it); + c->onItemsAppeared(it); + } else{ + c->privateMessage(msgMonster); + int numR = Random.getRandom(minCreatures, maxCreatures); + for (Vec2 v : getPosition().neighbors8(true)) { + PCreature rat = CreatureFactory::fromId(creatureId, Tribe::pest); + if (getLevel()->getSquare(v)->canEnter(rat.get())) { + getLevel()->addCreature(v, std::move(rat)); + if (--numR == 0) + break; + } + } + } + } + private: + CreatureId creatureId; + int minCreatures, maxCreatures; + string msgItem, msgMonster, msgGold; + bool opened = false; + bool destroyed = false; + ItemFactory itemFactory; +}; + +class Fountain : public Square { + public: + Fountain(const ViewObject& object) : Square(object, "fountain", true, true, 100) {} + + virtual Optional getApplyType(const Creature*) const override { + return SquareApplyType::DRINK; + } + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + getLevel()->globalMessage(getPosition(), "The fountain is destroyed."); + destroyed = true; + setViewObject(ViewObject(ViewId::FLOOR, ViewLayer::FLOOR, "Floor")); + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage("There is a " + string(destroyed ? "destroyed founain" : "fountain") + " here"); + } + + virtual void onApply(Creature* c) override { + c->privateMessage("You drink from the fountain."); + PItem potion = ItemFactory::potions().random(seed); + potion->apply(c, getLevel()); + } + + private: + int seed = Random.getRandom(123456); + bool destroyed = false; +}; + +class Tree : public Square { + public: + Tree(const ViewObject& object) : Square(object, "tree", false, true, 100, 0.5) {} + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + getLevel()->globalMessage(getPosition(), "The tree falls."); + destroyed = true; + setCanSeeThru(true); + getLevel()->updateVisibility(getPosition()); + setViewObject(ViewObject(ViewId::FALLEN_TREE, ViewLayer::FLOOR, "Fallen tree")); + } + + virtual void burnOut() override { + setCanSeeThru(true); + getLevel()->updateVisibility(getPosition()); + setViewObject(ViewObject(ViewId::BURNT_TREE, ViewLayer::FLOOR, "Burnt tree")); + } + + virtual bool itemBounces(Item* item) const { + return true; + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage(isBurnt() ? "There is a burnt tree here." : + destroyed ? "There is fallen tree here." : "You pass beneath a tree"); + } + + private: + bool destroyed = false; +}; + +class TrapSquare : public Square { + public: + TrapSquare(const ViewObject& object, PEffect e) : Square(object, "floor", true), effect(std::move(e)) { + } + + virtual void onEnterSpecial(Creature* c) override { + if (effect) { + c->you(MsgType::TRIGGER_TRAP, ""); + effect->applyToCreature(c, EffectStrength::NORMAL); + effect = nullptr; + } + } + + private: + PEffect effect; + Tribe* tribe; +}; + +class Door : public Square { + public: + Door(const ViewObject& object) : Square(object, "door", false, true, 100, 1) {} + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + getLevel()->globalMessage(getPosition(), "The door is destroyed."); + destroyed = true; + setCanSeeThru(true); + getLevel()->updateVisibility(getPosition()); + setViewObject(ViewObject(ViewId::DESTROYED_FURNITURE, ViewLayer::FLOOR, "Destroyed door")); + } + + virtual void burnOut() override { + setCanSeeThru(true); + getLevel()->updateVisibility(getPosition()); + setViewObject(ViewObject(ViewId::BURNT_FURNITURE, ViewLayer::FLOOR, "Burnt door")); + destroyed = true; + } + + virtual bool canEnterSpecial(const Creature* c) const override { + return true; + return c->isHumanoid() || destroyed; + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage(isBurnt() ? "There is a burnt door here." : + destroyed ? "There is a destroyed door here." : "You open the door."); + } + + protected: + bool destroyed = false; +}; + +class TribeDoor : public Door { + public: + TribeDoor(const ViewObject& object, int destStrength) : Door(object), destructionStrength(destStrength) {} + + virtual void destroy(int strength) override { + destructionStrength -= strength; + if (destructionStrength <= 0) { + EventListener::addSquareReplacedEvent(getLevel(), getPosition()); + getLevel()->replaceSquare(getPosition(), PSquare(SquareFactory::get(SquareType::FLOOR))); + } + } + + virtual bool canEnterSpecial(const Creature* c) const override { + return (c->canWalk() && c->getTribe() == Tribe::player) || destroyed; + } + + private: + int destructionStrength; +}; + +class Furniture : public Square { + public: + Furniture(const ViewObject& object, const string& name, double flamability) + : Square(object, name, true , true, 100, flamability) {} + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + getLevel()->globalMessage(getPosition(), "The " + name + " is destroyed."); + destroyed = true; + setName("destroyed " + getName()); + setViewObject(ViewObject(ViewId::DESTROYED_FURNITURE, ViewLayer::FLOOR, getName())); + } + + virtual void burnOut() override { + setName("burnt " + getName()); + setViewObject(ViewObject(ViewId::BURNT_FURNITURE, ViewLayer::FLOOR, getName())); + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage("There is a " + getName() + " here."); + } + + private: + bool destroyed = false; +}; + +class Bed : public Furniture { + public: + Bed(const ViewObject& object, const string& name) : Furniture(object, name, 1) {} + + virtual Optional getApplyType(const Creature*) const override { + return SquareApplyType::SLEEP; + } + + virtual void onApply(Creature* c) override { + Effect::getEffect(EffectType::SLEEP)->applyToCreature(c, EffectStrength::STRONG); + getLevel()->addTickingSquare(getPosition()); + } + + virtual void tickSpecial(double time) override { + if (getCreature() && getCreature()->isSleeping()) + getCreature()->heal(0.005); + } +}; + +class Grave : public Bed { + public: + Grave(const ViewObject& object, const string& name) : Bed(object, name) {} + + virtual Optional getApplyType(const Creature* c) const override { + if (c->isUndead()) + return SquareApplyType::SLEEP; + else + return Nothing(); + } + + virtual void onApply(Creature* c) override { + if (c->getName() != "vampire") + return; + Bed::onApply(c); + } +}; + +class Altar : public Square { + public: + Altar(const ViewObject& object, Deity* d) + : Square(object, "shrine to " + d->getName(), true , true, 100, 0), deity(d) { + } + + virtual bool canDestroy() const override { + return true; + } + + virtual void destroy(int strength) override { + if (destroyed) + return; + getLevel()->globalMessage(getPosition(), "The shrine is destroyed."); + destroyed = true; + setName("destroyed shrine"); + setViewObject(ViewObject(ViewId::DESTROYED_FURNITURE, ViewLayer::FLOOR, getName())); + } + + virtual void onEnterSpecial(Creature* c) override { + c->privateMessage("This is a shrine to " + deity->getName()); + c->privateMessage((deity->getGender() == Gender::MALE ? "He lives in " : "She lives in ") + + deity->getHabitatString()); + c->privateMessage((deity->getGender() == Gender::MALE ? "He is the god of " : "She is the goddess of ") + + deity->getEpithets()); + } + + virtual Optional getApplyType(const Creature*) const override { + return SquareApplyType::PRAY; + } + + virtual void onApply(Creature* c) override { + c->privateMessage("You pray to " + deity->getName()); + deity->onPrayer(c); + } + + private: + Deity* deity; + bool destroyed = false; +}; + +class ConstructionDropItems : public SolidSquare { + public: + ConstructionDropItems(const ViewObject& object, const string& name, + map constructions, vector _items) + : SolidSquare(object, name, false, constructions), items(std::move(_items)) {} + + virtual void onConstructNewSquare(Square* s) override { + s->dropItems(std::move(items)); + } + + private: + vector items; +}; + +class TrainingDummy : public Furniture { + public: + TrainingDummy(const ViewObject& object, const string& name) : Furniture(object, name, 1) {} + + virtual Optional getApplyType(const Creature*) const override { + return SquareApplyType::TRAIN; + } + + virtual void onApply(Creature* c) override { + if (Random.roll(50)) + c->increaseExpLevel(); + } +}; + +class Workshop : public Furniture { + public: + Workshop(const ViewObject& object, const string& name, ItemFactory f) : Furniture(object, name, 1), factory(f) {} + + virtual Optional getApplyType(const Creature*) const override { + return SquareApplyType::WORKSHOP; + } + + virtual void onApply(Creature* c) override { + if (Random.roll(40)) + dropItem(factory.random()); + } + + private: + ItemFactory factory; +}; + +class Hatchery : public Square { + public: + Hatchery(const ViewObject& object, const string& name) : Square(object, name, true, false, 0, 0, {}, true) {} + + virtual void tickSpecial(double time) override { + if (getCreature() || !Random.roll(10)) + return; + for (Vec2 v : getPosition().neighbors8()) + if (Creature* c = getLevel()->getSquare(v)->getCreature()) + if (c->getName() == "chicken") + return; + getLevel()->addCreature(getPosition(), CreatureFactory::fromId(CreatureId::CHICKEN, Tribe::player, + MonsterAIFactory::moveRandomly())); + } + + virtual bool canEnterSpecial(const Creature* c) const override { + return c->canWalk() || c->getName() == "chicken"; + } + +}; + +Square* SquareFactory::getAltar(Deity* deity) { + return new Altar(ViewObject(ViewId::ALTAR, ViewLayer::FLOOR, "Shrine"), deity); +} + +Square* SquareFactory::get(SquareType s) { + switch (s) { + case SquareType::PATH: + case SquareType::FLOOR: + return new Square(ViewObject(ViewId::PATH, ViewLayer::FLOOR, "Floor"), "floor", true, false, 0, 0, + {{SquareType::TREASURE_CHEST, 10}, {SquareType::BED, 10}, {SquareType::TRIBE_DOOR, 10}, + {SquareType::ROCK_WALL, 20}, {SquareType::TRAINING_DUMMY, 10}, {SquareType::HATCHERY, 3}, + {SquareType::GRAVE, 10}, {SquareType::ROLLING_BOULDER, 10}, {SquareType::WORKSHOP, 10}}); + case SquareType::BRIDGE: + return new Square(ViewObject(ViewId::BRIDGE, ViewLayer::FLOOR, "Rope bridge"), "rope bridge", true); + case SquareType::GRASS: + return new Square(ViewObject(ViewId::GRASS, ViewLayer::FLOOR, "Grass"), "grass", true); + case SquareType::ROCK_WALL: + return new SolidSquare(ViewObject(ViewId::WALL, ViewLayer::FLOOR, "Wall"), "wall", false, + {{SquareType::FLOOR, Random.getRandom(3, 8)}}); + case SquareType::GOLD_ORE: + return new ConstructionDropItems(ViewObject(ViewId::GOLD_ORE, ViewLayer::FLOOR, "Gold ore"), "gold ore", + {{SquareType::FLOOR, Random.getRandom(30, 80)}}, + ItemFactory::fromId(ItemId::GOLD_PIECE, Random.getRandom(30, 60))); + case SquareType::LOW_ROCK_WALL: + return new SolidSquare(ViewObject(ViewId::WALL, ViewLayer::FLOOR, "Wall"), "wall", true); + case SquareType::WOOD_WALL: + return new SolidSquare(ViewObject(ViewId::WOOD_WALL, ViewLayer::FLOOR, "Wooden wall"), "wall", false); + case SquareType::BLACK_WALL: + return new SolidSquare(ViewObject(ViewId::BLACK_WALL, ViewLayer::FLOOR, "Wall"), "wall", false); + case SquareType::YELLOW_WALL: + return new SolidSquare(ViewObject(ViewId::YELLOW_WALL, ViewLayer::FLOOR, "Wall"), "wall", false); + case SquareType::MOUNTAIN: + return new SolidSquare(ViewObject(ViewId::MOUNTAIN, ViewLayer::FLOOR, "Mountain"), "mountain", true); + case SquareType::GLACIER: + return new SolidSquare(ViewObject(ViewId::SNOW, ViewLayer::FLOOR, "Mountain"), "mountain", true); + case SquareType::HILL: + return new Square(ViewObject(ViewId::HILL, ViewLayer::FLOOR, "Hill"), "hill", true); + case SquareType::SECRET_PASS: + return new SecretPassage(ViewObject(ViewId::SECRETPASS, ViewLayer::FLOOR, "Wall"), + ViewObject(ViewId::FLOOR, ViewLayer::FLOOR, "Floor")); + case SquareType::WATER: + return new Water(ViewObject(ViewId::WATER, ViewLayer::FLOOR, "Water"), "water", + "sinks in the water", "You hear a splash", MsgType::DROWN); + case SquareType::MAGMA: + return new Water(ViewObject(ViewId::MAGMA, ViewLayer::FLOOR, "Magma"), + "magma", "burns in the magma", "", MsgType::BURN, false); + case SquareType::ABYSS: + Debug(FATAL) << "Unimplemented"; + case SquareType::SAND: return new Square(ViewObject(ViewId::SAND, ViewLayer::FLOOR, "Sand"), "sand", true); + case SquareType::CANIF_TREE: return new Tree(ViewObject(ViewId::CANIF_TREE, ViewLayer::FLOOR, "Tree")); + case SquareType::DECID_TREE: return new Tree(ViewObject(ViewId::DECID_TREE, ViewLayer::FLOOR, "Tree")); + case SquareType::BUSH: return new Furniture(ViewObject(ViewId::BUSH, ViewLayer::FLOOR, "Bush"), "bush", 1); + case SquareType::BED: return new Bed(ViewObject(ViewId::BED, ViewLayer::FLOOR, "Bed"), "bed"); + case SquareType::TORTURE_TABLE: + return new Furniture(ViewObject(ViewId::TORTURE_TABLE, ViewLayer::FLOOR, "Torture table"), + "torture table", 0.3); + case SquareType::TRAINING_DUMMY: + return new TrainingDummy(ViewObject(ViewId::TRAINING_DUMMY, ViewLayer::FLOOR, "Training post"), + "training post"); + case SquareType::WORKSHOP: + return new Workshop(ViewObject(ViewId::WORKSHOP, ViewLayer::FLOOR, "Workshop stand"), + "workshop stand", ItemFactory::workshop()); + case SquareType::HATCHERY: + return new Hatchery(ViewObject(ViewId::GRASS, ViewLayer::FLOOR, "Hatchery"), "hatchery"); + case SquareType::DUNGEON_HEART: + return new SolidSquare(ViewObject(ViewId::DUNGEON_HEART, ViewLayer::FLOOR, "Dungeon heart"), + "dungeon heart", true); + case SquareType::ALTAR: + Debug(FATAL) << "Altars are not handled by this method."; + case SquareType::ROLLING_BOULDER: return new TrapSquare(ViewObject(ViewId::FLOOR, ViewLayer::FLOOR, "floor"), + Effect::getEffect(EffectType::ROLLING_BOULDER)); + case SquareType::FOUNTAIN: + return new Fountain(ViewObject(ViewId::FOUNTAIN, ViewLayer::FLOOR, "Fountain")); + case SquareType::CHEST: + return new Chest(ViewObject(ViewId::CHEST, ViewLayer::FLOOR, "Chest"), "chest", CreatureId::RAT, 3, 6, "There is an item inside", "It's full of rats!", "There is gold inside", ItemFactory::dungeon()); + case SquareType::TREASURE_CHEST: + return new Furniture(ViewObject(ViewId::CHEST, ViewLayer::FLOOR, "Chest"), "chest", 1); + case SquareType::COFFIN: + return new Chest(ViewObject(ViewId::COFFIN, ViewLayer::FLOOR, "Coffin"), "coffin", CreatureId::VAMPIRE, 1, 2, "There is a rotting corpse inside. You find an item.", "There is a rotting corpse inside. The corpse is alive!", "There is a rotting corpse inside. You find some gold.", ItemFactory::dungeon()); + case SquareType::GRAVE: + return new Grave(ViewObject(ViewId::GRAVE, ViewLayer::FLOOR, "Grave"), "grave"); + case SquareType::IRON_BARS: + Debug(FATAL) << "Unimplemented"; + case SquareType::DOOR: return new Door(ViewObject(ViewId::DOOR, ViewLayer::FLOOR, "Door")); + case SquareType::TRIBE_DOOR: return new TribeDoor(ViewObject(ViewId::DOOR, ViewLayer::LARGE_ITEM, "Door"), 100); + case SquareType::BORDER_GUARD: + return new SolidSquare(ViewObject(ViewId::BORDER_GUARD, ViewLayer::FLOOR, "Wall"), "wall", false); + case SquareType::DOWN_STAIRS: + case SquareType::UP_STAIRS: Debug(FATAL) << "Stairs are not handled by this method."; + } + return 0; +} + +Square* SquareFactory::getStairs(StairDirection direction, StairKey key) { + switch(direction) { + case StairDirection::UP: + return new Staircase(ViewObject(ViewId::UP_STAIRCASE, ViewLayer::FLOOR, "Stairs leading up"), + "stairs leading up", direction, key); + case StairDirection::DOWN: + return new Staircase(ViewObject(ViewId::DOWN_STAIRCASE, ViewLayer::FLOOR, "Stairs leading down"), + "stairs leading down", direction, key); + } + return nullptr; +} diff --git a/square_factory.h b/square_factory.h new file mode 100644 index 000000000..ad1457cf7 --- /dev/null +++ b/square_factory.h @@ -0,0 +1,26 @@ +#ifndef _SQUARE_FACTORY +#define _SQUARE_FACTORY + +#include "pantheon.h" + +class Square; + + +inline StairDirection opposite(StairDirection d) { + switch (d) { + case StairDirection::DOWN: return StairDirection::UP; + case StairDirection::UP: return StairDirection::DOWN; + } + return StairDirection::DOWN; +} + +class SquareFactory { + public: + static Square* get(SquareType); + static Square* getStairs(StairDirection, StairKey); + static Square* getAltar(Deity*); + + private: +}; + +#endif diff --git a/stdafx.h b/stdafx.h new file mode 100644 index 000000000..1b3071a0d --- /dev/null +++ b/stdafx.h @@ -0,0 +1,110 @@ +#ifndef _STDAFX_H +#define _STDAFX_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ctype.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::string; +using std::vector; +using std::map; +using std::queue; +using std::unique_ptr; +using std::default_random_engine; +using std::function; +using std::initializer_list; +using std::unordered_set; +using std::pair; +using std::tuple; +using std::set; +using std::out_of_range; +using std::unordered_map; +using std::min; +using std::max; +using std::ofstream; +using std::ifstream; +using std::endl; +using std::priority_queue; +using std::make_pair; +using std::stack; + +#include "window_view.h" +#include "action.h" +#include "attack.h" +#include "collective.h" +#include "collective_action.h" +#include "controller.h" +#include "creature_attributes.h" +#include "creature_factory.h" +#include "creature_view.h" +#include "creature.h" +#include "debug.h" +#include "effect.h" +#include "enemy_check.h" +#include "enums.h" +#include "equipment.h" +#include "event.h" +#include "field_of_view.h" +#include "fire.h" +#include "inventory.h" +#include "item_attributes.h" +#include "item_factory.h" +#include "item.h" +#include "level_generator.h" +#include "level.h" +#include "level_maker.h" +#include "location.h" +#include "logging_view.h" +#include "map_layout.h" +#include "map_memory.h" +#include "markov_chain.h" +#include "message_buffer.h" +#include "minion_equipment.h" +#include "model.h" +#include "monster.h" +#include "monster_ai.h" +#include "name_generator.h" +#include "pantheon.h" +#include "player.h" +#include "poison_gas.h" +#include "quest.h" +#include "ranged_weapon.h" +#include "replay_view.h" +#include "shortest_path.h" +#include "skill.h" +#include "square_factory.h" +#include "square.h" +#include "task.h" +#include "time_queue.h" +#include "timer_var.h" +#include "tribe.h" +#include "trigger.h" +#include "util.h" +#include "view.h" +#include "view_index.h" +#include "view_object.h" +#include "village_control.h" + +#endif diff --git a/task.cpp b/task.cpp new file mode 100644 index 000000000..df5df24f0 --- /dev/null +++ b/task.cpp @@ -0,0 +1,340 @@ +#include "stdafx.h" + + +Task::Task(Collective* col, Vec2 pos) : position(pos), collective(col) {} + +Vec2 Task::getPosition() { + return position; +} + +Collective* Task::getCollective() { + return collective; +} + +bool Task::isDone() { + return done; +} + +void Task::setDone() { + done = true; +} + +void Task::setPosition(Vec2 pos) { + position = pos; +} + +class Construction : public Task { + public: + Construction(Collective* col, Vec2 position, SquareType _type) : Task(col, position), type(_type) {} + + virtual bool isImpossible(const Level* level) { + return !level->getSquare(getPosition())->canConstruct(type); + } + + virtual MoveInfo getMove(Creature* c) override { + if (!c->hasSkill(Skill::construction)) + return NoMove; + Vec2 dir = getPosition() - c->getPosition(); + if (dir.length8() == 1) { + if (c->canConstruct(dir, type)) + return {1.0, [this, c, dir] { + c->construct(dir, type); + if (!c->canConstruct(dir, type)) { + setDone(); + getCollective()->onConstructed(getPosition(), type); + } + }}; + else { + setDone(); + return NoMove; + } + } else + return getMoveToPosition(c); + } + + private: + SquareType type; +}; + + +PTask Task::construction(Collective* col, Vec2 target, SquareType type) { + return PTask(new Construction(col, target, type)); +} + +MoveInfo Task::getMoveToPosition(Creature *c) { + Optional move = c->getMoveTowards(position); + if (move) + return {1.0, [=] { + c->move(*move); + }}; + else + return NoMove; +} + +class PickItem : public Task { + public: + PickItem(Collective* col, Vec2 position, vector _items) + : Task(col, position), items(_items.begin(), _items.end()) { + } + + virtual void onPickedUp() { + setDone(); + } + + virtual bool canTransfer() override { + return false; + } + + virtual MoveInfo getMove(Creature* c) override { + CHECK(!pickedUp); + if (c->getPosition() == getPosition()) { + vector hereItems; + for (Item* it : c->getPickUpOptions()) + if (items.count(it)) + hereItems.push_back(it); + if (hereItems.empty()) { + setDone(); + return NoMove; + } + items = set(hereItems.begin(), hereItems.end()); + if (c->canPickUp(hereItems)) + return {1.0, [=] { + c->pickUp(hereItems); + pickedUp = true; + onPickedUp(); + getCollective()->onPickedUp(getPosition(), hereItems); + }}; + else + return NoMove; + } + else + return getMoveToPosition(c); + } + + protected: + set items; + bool pickedUp = false; +}; + +PTask Task::pickItem(Collective* col, Vec2 position, vector items) { + return PTask(new PickItem(col, position, items)); +} + +class EquipItem : public PickItem { + public: + EquipItem(Collective* col, Vec2 position, vector _items) : PickItem(col, position, _items) { + } + + virtual void onPickedUp() override { + } + + virtual MoveInfo getMove(Creature* c) override { + if (!pickedUp) + return PickItem::getMove(c); + CHECK(items.size() <= 1); + for (Item* it : items) + if (c->canEquip(it)) + return {1.0, [=] { + c->equip(it); + setDone(); + }}; + return NoMove; + } +}; + +PTask Task::equipItem(Collective* col, Vec2 position, Item* items) { + return PTask(new EquipItem(col, position, {items})); +} + +class BringItem : public PickItem { + public: + BringItem(Collective* col, Vec2 position, vector items, Vec2 _target) + : PickItem(col, position, items), target(_target) {} + + virtual void onBroughtItem(Creature* c, vector it) { + //getCollective()->onBrought(c->getPosition(), it); + c->drop(it); + } + + virtual void onPickedUp() override { + } + + virtual MoveInfo getMove(Creature* c) override { + if (!pickedUp) + return PickItem::getMove(c); + setPosition(target); + if (c->getPosition() == target) { + vector myItems(items.begin(), items.end()); + return {1.0, [=] { + onBroughtItem(c, myItems); + setDone(); + }}; + } else { + if (c->getPosition().dist8(target) == 1) + if (Creature* other = c->getLevel()->getSquare(target)->getCreature()) + if (other->isSleeping()) + other->wakeUp(); + return getMoveToPosition(c); + } + } + + virtual bool canTransfer() override { + return !pickedUp; + } + + protected: + Vec2 target; + +}; + +PTask Task::bringItem(Collective* col, Vec2 position, vector items, Vec2 target) { + return PTask(new BringItem(col, position, items, target)); +} + +class ApplyItem : public BringItem { + public: + ApplyItem(Collective* col, Vec2 position, vector items, Vec2 _target) + : BringItem(col, position, items, _target) {} + + virtual void cancel() override { + getCollective()->onAppliedItemCancel(target); + } + + virtual void onBroughtItem(Creature* c, vector it) override { + CHECK(it.size() == 1); + getCollective()->onAppliedItem(c->getPosition(), it[0]); + c->applyItem(it[0]); + } +}; + +PTask Task::applyItem(Collective* col, Vec2 position, Item* item, Vec2 target) { + return PTask(new ApplyItem(col, position, {item}, target)); +} + +class ApplySquare : public Task { + public: + ApplySquare(Collective* col, set pos) : Task(col, Vec2(-1, 1)), positions(pos) {} + + virtual bool canTransfer() override { + return false; + } + + virtual MoveInfo getMove(Creature* c) override { + Vec2 pos = getPosition(); + if (pos.x == -1) { + pos = Vec2(-10000, -10000); + for (Vec2 v : randomPermutation(positions)) + if (!rejectedPosition.count(v) && + (pos - c->getPosition()).length8() > (v - c->getPosition()).length8() && + !c->getLevel()->getSquare(v)->getCreature()) + pos = v; + if (pos.x == -10000) { + setDone(); + return NoMove; + } + setPosition(pos); + } + if (c->getPosition() == getPosition()) { + CHECK(c->getSquare()->getApplyType(c)); + return {1.0, [this, c] { + c->applySquare(); + setDone(); + }}; + } else { + MoveInfo move = getMoveToPosition(c); + if (!move.isValid() || ((getPosition() - c->getPosition()).length8() == 1 && c->getLevel()->getSquare(getPosition())->getCreature())) { + rejectedPosition.insert(getPosition()); + setPosition(Vec2(-1, -1)); + if (--invalidCount == 0) { + setDone(); + } + return NoMove; + } + else + return move; + } + } + + private: + set positions; + set rejectedPosition; + int invalidCount = 5; +}; + +PTask Task::applySquare(Collective* col, set position) { + CHECK(position.size() > 0); + return PTask(new ApplySquare(col, position)); +} + +class Eat : public Task { + public: + Eat(Collective* col, set pos) : Task(col, Vec2(-1, 1)), positions(pos) {} + + virtual bool canTransfer() override { + return false; + } + + Item* getDeadChicken(Square* s) { + vector chickens = s->getItems(Item::typePredicate(ItemType::FOOD)); + if (chickens.empty()) + return nullptr; + else + return chickens[0]; + } + + virtual MoveInfo getMove(Creature* c) override { + Vec2 pos = getPosition(); + if (pos.x == -1) { + pos = Vec2(-10000, -10000); + for (Vec2 v : positions) + if (!rejectedPosition.count(v) && (pos - c->getPosition()).length8() > (v - c->getPosition()).length8()) + pos = v; + if (pos.x == -10000) { + setDone(); + return NoMove; + } + setPosition(pos); + } + Item* chicken = getDeadChicken(c->getSquare()); + if (chicken) { + return {1.0, [this, c, chicken] { + c->globalMessage(c->getTheName() + " eats " + chicken->getAName()); + c->eat(chicken); + setDone(); + }}; + } + for (Vec2 v : Vec2::directions8(true)) { + Item* chicken = getDeadChicken(c->getSquare(v)); + if (chicken && c->canMove(v)) + return {1.0, [this, c, v] { + c->move(v); + }}; + if (Creature* ch = c->getSquare(v)->getCreature()) + if (ch->getName() == "chicken" && c->canAttack(ch)) { + return {1.0, [this, c, ch] { + c->attack(ch); + }}; + } + } + MoveInfo move = getMoveToPosition(c); + if (!move.isValid() || ((getPosition() - c->getPosition()).length8() == 1 && c->getLevel()->getSquare(getPosition())->getCreature())) { + rejectedPosition.insert(getPosition()); + setPosition(Vec2(-1, -1)); + if (--invalidCount == 0) { + setDone(); + } + return NoMove; + } + else + return move; + } + + private: + set positions; + set rejectedPosition; + int invalidCount = 5; +}; + +PTask Task::eat(Collective* col, set hatcherySquares) { + return PTask(new Eat(col, hatcherySquares)); +} diff --git a/task.h b/task.h new file mode 100644 index 000000000..db6922464 --- /dev/null +++ b/task.h @@ -0,0 +1,38 @@ +#ifndef _TASK_H +#define _TASK_H + +#include "monster_ai.h" + +class Task { + public: + Task(Collective*, Vec2 position); + + virtual MoveInfo getMove(Creature*) = 0; + virtual bool isImpossible(const Level*) { return false; } + virtual bool canTransfer() { return true; } + virtual void cancel() {} + bool isDone(); + + Vec2 getPosition(); + + static PTask construction(Collective*, Vec2 target, SquareType); + static PTask bringItem(Collective*, Vec2 position, vector, Vec2 target); + static PTask applyItem(Collective* col, Vec2 position, Item* item, Vec2 target); + static PTask applySquare(Collective*, set squares); + static PTask eat(Collective*, set hatcherySquares); + static PTask equipItem(Collective* col, Vec2 position, Item* item); + static PTask pickItem(Collective* col, Vec2 position, vector items); + + protected: + void setDone(); + void setPosition(Vec2); + MoveInfo getMoveToPosition(Creature*); + Collective* getCollective(); + + private: + Vec2 position; + bool done = false; + Collective* collective; +}; + +#endif diff --git a/test.cpp b/test.cpp new file mode 100644 index 000000000..fc14068d6 --- /dev/null +++ b/test.cpp @@ -0,0 +1,356 @@ +#include "stdafx.h" + +using namespace std; + +void testStringConvertion() { + CHECK(convertToString(1234) == "1234"); + CHECK(convertFromString("1234") == 1234); +} + +void testTimeQueue() { + /* CreatureAttributes attr = CATTR(c.name = ""; c.speed = 5; c.size = CreatureSize::SMALL; c.strength = 1; c.dexterity = 3; c.humanoid = false; c.weight = 0.1;); + PCreature b(new Creature(ViewObject(ViewId::JACKAL, ViewLayer::CREATURE, ""), nullptr, attr)); + PCreature a(new Creature(ViewObject(ViewId::PLAYER, ViewLayer::CREATURE, ""), nullptr, attr)); + PCreature c(new Creature(ViewObject(ViewId::JACKAL, ViewLayer::CREATURE, ""), nullptr, attr)); + Creature* rb = b.get(), *ra = a.get(), *rc = c.get(); + a->setTime(1); + b->setTime(1.33); + c->setTime(1.66); + TimeQueue q; + q.addCreature(move(a)); + q.addCreature(move(b)); + q.addCreature(move(c)); + CHECK(q.getNextCreature() == ra); + ra->setTime(2); + CHECK(q.getNextCreature() == rb); + rb->setTime(2); + CHECK(q.getNextCreature() == rc); + rc->setTime(3); + CHECK(q.getNextCreature() == rb); + rb->setTime(3); + CHECK(q.getNextCreature() == ra);*/ +} + +void testRectangleIterator() { + vector v1, v2; + for (Vec2 v : Rectangle(10, 10)) { + v1.push_back(v); + } + for (int i = 0; i < 10; ++i) + for (int j = 0; j < 10; ++j) + v2.push_back(Vec2(i, j)); + CHECK(v1 == v2); + v1.clear(); v2.clear(); + for (Vec2 v : Rectangle(10, 15, 20, 25)) { + v1.push_back(v); + } + for (int i = 10; i < 20; ++i) + for (int j = 15; j < 25; ++j) + v2.push_back(Vec2(i, j)); + CHECK(v1 == v2); +} + +void testRectangle() { + CHECK(Rectangle(2, 2, 4, 4).intersects(Rectangle(3, 3, 5, 5))); + CHECK(!Rectangle(2, 2, 4, 4).intersects(Rectangle(3, 4, 5, 5))); + CHECK(!Rectangle(2, 2, 4, 4).intersects(Rectangle(4, 3, 5, 5))); +} + +void testValueCheck() { + CHECK(3 == CHECKEQ(1 + 2, 3)); + string* s = new string("wefpok"); + CHECK(s == NOTNULL(s)); +} + +void testSplit() { + vector v = { "pok", "pokpok", "pok" }; + vector w = { "po", ";po", "po", ";;po", ";" }; + CHECK(split("pok;pokpok;;pok;", ';') == v) << split("pok;pokpok;;pok;", ';'); + CHECK(split("pok;pokpok;;pok;", 'k') == w) << split("pok;pokpok;;pok;", 'k'); +} + +void testShortestPath() { + vector > table { { 2, 1, 2, 18, 1}, { 1, 1, 18, 1, 2}, {2, 6, 10, 1,1}, {1, 2, 1, 8, 1}, {5, 3, 1, 1, 2}}; + ShortestPath path(Rectangle(5, 5), + [table](Vec2 pos) { return table[pos.y][pos.x];}, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(), Vec2(4, 0)); + vector res {Vec2(1, 0)}; + while (res.back() != Vec2(4, 0)) { + res.push_back(path.getNextMove(res.back())); + } + vector expected {Vec2(1, 0), Vec2(1, 1), Vec2(0, 1), Vec2(0, 2), Vec2(0, 3), Vec2(1, 3), Vec2(2, 3), Vec2(2, 4), Vec2(3, 4), Vec2(4, 4), Vec2(4, 3), Vec2(4, 2), Vec2(4, 1), Vec2(4, 0)}; + CHECK(res == expected); +} + +void testAStar() { + vector > table { { 1, 1, 6, 1, 1}, { 1, 1, 6, 1, 1}, {1, 1, 1, 1,1}, {1, 1, 6, 1, 1}, {1, 1, 6, 1, 1}}; + ShortestPath path(Rectangle(5, 5), + [table](Vec2 pos) { return table[pos.y][pos.x];}, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(), Vec2(4, 0), Vec2(0, 0)); +} + +void testShortestPath2() { + vector > table { { 2, 1, 2, ShortestPath::infinity, 1}, { 1, 1, 18, 1, ShortestPath::infinity}, {2, 6, 10, 1,1}, {1, 2, 1, 8, 1}, {5, 3, 1, 1, 2}}; + ShortestPath path(Rectangle(5, 5), + [table](Vec2 pos) { return table[pos.y][pos.x];}, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(), Vec2(4, 0)); + CHECK(!path.isReachable(Vec2(1, 0))); +} + +void testShortestPathReverse() { + vector > table { { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, { 1, 1, 1, ShortestPath::infinity, ShortestPath::infinity, 1, 1, 1, 1, 1, 1}, { 1, 1, 1, ShortestPath::infinity, ShortestPath::infinity, 1, 1, 1, 1, 1, 1}}; + ShortestPath path(Rectangle(11, 3), + [table](Vec2 pos) { return table[pos.y][pos.x];}, + [] (Vec2 v) { return v.length4(); }, + Vec2::directions4(), Vec2(1, 1), Nothing(), -1.3); +/* vector res {Vec2(1, 0)}; + while (res.back() != Vec2(4, 0)) { + res.push_back(path.getNextMove(res.back())); + } + vector expected {Vec2(1, 0), Vec2(1, 1), Vec2(0, 1), Vec2(0, 2), Vec2(0, 3), Vec2(1, 3), Vec2(2, 3), Vec2(2, 4), Vec2(3, 4), Vec2(4, 4), Vec2(4, 3), Vec2(4, 2), Vec2(4, 1), Vec2(4, 0)}; + CHECK(res == expected);*/ +} + +void testRandom() { + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 1) == "pokpok"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 2) == "kwakwa"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 3) == "kwakwa"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 4) == "pikpik"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 5) == "pikpik"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 2, 3}, 6) == "pikpik"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 0, 3}, 1) == "pokpok"); + CHECK(chooseRandom({"pokpok", "kwakwa", "pikpik"}, { 1, 0, 3}, 2) == "pikpik"); +} + +void testViewTitlePrefix() { + string a = "pokpok"; + CHECK(!View::hasTitlePrefix(a)); + CHECK(View::hasTitlePrefix(View::getTitlePrefix(a))); + CHECK(View::removeTitlePrefix(View::getTitlePrefix(a)) == a); +} + +void testRange() { + vector a; + vector b {0,1,2,3,4,5,6}; + for (int x : Range(7)) + a.push_back(x); + CHECKEQ(a, b); + a.clear(); + for (int x : Range(2, 6)) + a.push_back(x); + CHECKEQ(getPrefix(b, 2, 4), a); + a.clear(); + for (int x : All(b)) + a.push_back(x); + CHECKEQ(a, b); +} + +void testContains() { + CHECK(contains({0, 1, 2, 3}, 2)); + CHECK(!contains({0, 1, 2, 3}, 4)); + CHECK(contains({Vec2(0, 0), Vec2(0, 1)}, Vec2(0, 0))); + CHECK(!contains({Vec2(1, 0), Vec2(0, 1)}, Vec2(0, 0))); + CHECK(contains(string("pokpokpokpikpik"), string("kpok"))); + CHECK(contains(string("pokpokpokpikpik"), string("kpik"))); + CHECK(!contains(string("pokpokpokpikpik"), string("pikpok"))); + CHECK(contains(string("pokpokpokpikpik"), string("pokpokpokpikpik"))); +} + +void testPredicates() { + function pred1 = [](int x) { return x % 2 == 0; }; + function pred2 = [](int x) { return x % 3 == 0; }; + for (int i : Range(20)) { + bool x = andFun(pred1, pred2)(i); + CHECK(x == (i % 6 == 0)); + } +} + +Optional getInt(bool yes) { + if (yes) + return 1; + else + return Nothing(); +} + +void testOptional() { + Optional r = getInt(true); + CHECK(r); + CHECK(r == 1); + CHECK(!getInt(false)); +} + +void testMustInitialize() { + MustInitialize x; + x = 5; + int y = *x; + MustInitialize z(x); + int v = *z; +} + +template +void checkEqual(pair p1, pair p2) { + CHECK(p1 == p2 || (p1.first == p2.second && p1.second == p2.first)) + << p1.first << "," << p1.second << " " << p2.first << "," << p2.second; +} + +void testVec2() { + CHECK(Vec2(5, 0).shorten() == Vec2(1, 0)); + CHECK(Vec2(-7, 0).shorten() == Vec2(-1, 0)); + CHECK(Vec2(0, 4).shorten() == Vec2(0, 1)); + CHECK(Vec2(0, -3).shorten() == Vec2(0, -1)); + CHECK(Vec2(5, 5).shorten() == Vec2(1, 1)); + CHECK(Vec2(-7, -7).shorten() == Vec2(-1, -1)); + CHECK(Vec2(-3, 3).shorten() == Vec2(-1, 1)); + CHECK(Vec2(4, -4).shorten() == Vec2(1, -1)); + + checkEqual(Vec2(3, 0).approxL1(), make_pair(Vec2(1, 0), Vec2(1, 0))); + checkEqual(Vec2(3, -1).approxL1(), make_pair(Vec2(1, 0), Vec2(1, -1))); + checkEqual(Vec2(3, -3).approxL1(), make_pair(Vec2(1, -1), Vec2(1, -1))); + checkEqual(Vec2(1, -3).approxL1(), make_pair(Vec2(0, -1), Vec2(1, -1))); + checkEqual(Vec2(0, -3).approxL1(), make_pair(Vec2(0, -1), Vec2(0, -1))); + checkEqual(Vec2(-1, -3).approxL1(), make_pair(Vec2(0, -1), Vec2(-1, -1))); + checkEqual(Vec2(-3, -3).approxL1(), make_pair(Vec2(-1, -1), Vec2(-1, -1))); + checkEqual(Vec2(-3, -1).approxL1(), make_pair(Vec2(-1, 0), Vec2(-1, -1))); + checkEqual(Vec2(-3, 0).approxL1(), make_pair(Vec2(-1, 0), Vec2(-1, 0))); + checkEqual(Vec2(-3, 1).approxL1(), make_pair(Vec2(-1, 0), Vec2(-1, 1))); + checkEqual(Vec2(-3, 3).approxL1(), make_pair(Vec2(-1, 1), Vec2(-1, 1))); + checkEqual(Vec2(-1, 3).approxL1(), make_pair(Vec2(0, 1), Vec2(-1, 1))); + checkEqual(Vec2(0, 3).approxL1(), make_pair(Vec2(0, 1), Vec2(0, 1))); + checkEqual(Vec2(1, 3).approxL1(), make_pair(Vec2(0, 1), Vec2(1, 1))); + checkEqual(Vec2(3, 3).approxL1(), make_pair(Vec2(1, 1), Vec2(1, 1))); + checkEqual(Vec2(3, 1).approxL1(), make_pair(Vec2(1, 1), Vec2(1, 0))); + + CHECKEQ(Vec2(1, 0).getBearing(), "east"); + CHECKEQ(Vec2(3, 1).getBearing(), "east"); + CHECKEQ(Vec2(1, 1).getBearing(), "south-east"); + CHECKEQ(Vec2(2, 1).getBearing(), "south-east"); + CHECKEQ(Vec2(0, 1).getBearing(), "south"); + CHECKEQ(Vec2(-1, 3).getBearing(), "south"); + CHECKEQ(Vec2(-1, 1).getBearing(), "south-west"); + CHECKEQ(Vec2(-1, 0).getBearing(), "west"); + CHECKEQ(Vec2(-1, -1).getBearing(), "north-west"); + CHECKEQ(Vec2(0, -1).getBearing(), "north"); + CHECKEQ(Vec2(1, -1).getBearing(), "north-east"); +} + +void testConcat() { + vector v = concat({{1, 2}, {3,4,5}, {}, {7,8,9}}); + vector r = {1,2,3,4,5,7,8,9}; + CHECK(v == r) << v << " != " < t(10, 10); + for (Vec2 v : Rectangle(10, 10)) + t[v] = v.x * v.y; + CHECK(t[9][9] = 81); + Table t2(15, 20, 25, 30); + for (int i = 15; i < 40; ++i) + for (int j = 20; j < 50; ++j) + t2[i][j] = i * j; + CHECK(t2[39][49] == 39 * 49); +} + +void testProjection() { +/* Vec2 proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(6, 0)); + CHECKEQ(proj, Vec2(4, 1)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(4, -2)); + CHECKEQ(proj, Vec2(3, 0)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(0, -2)); + CHECKEQ(proj, Vec2(1, 0)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(-2, 0)); + CHECKEQ(proj, Vec2(0, 1)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(-2, 4)); + CHECKEQ(proj, Vec2(0, 3)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(0, 6)); + CHECKEQ(proj, Vec2(1, 4)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(4, 6)); + CHECKEQ(proj, Vec2(3, 4)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(6, 4)); + CHECKEQ(proj, Vec2(4, 3)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(-2, 2)); + CHECKEQ(proj, Vec2(0, 2)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(6, 2)); + CHECKEQ(proj, Vec2(4, 2)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(2, -2)); + CHECKEQ(proj, Vec2(2, 0)); + proj = AllegroView::projectOnBorders(Rectangle(5, 5), Vec2(2, 6)); + CHECKEQ(proj, Vec2(2, 4));*/ +} + +void testVec2Box0() { + vector v = Vec2(-2, -3).box(0, false); + vector res { Vec2(-2, -3) }; + CHECKEQ(v, res); +} + +void testVec2Box1() { + vector v = Vec2(-2, -3).box(1, false); + vector res { Vec2(-3, -4), Vec2(-2, -4), Vec2(-1, -4), + Vec2(-1, -3), Vec2(-1, -2), Vec2(-2, -2), + Vec2(-3, -2), Vec2(-3, -3) }; + CHECKEQ(v, res); +} + +void testVec2Box2() { + vector v = Vec2(-2, -3).box(2, false); + vector res { Vec2(-4, -5), + Vec2(-3, -5), Vec2(-2, -5), Vec2(-1, -5), + Vec2(0, -5), Vec2(0, -4), Vec2(0, -3), + Vec2(0, -2), Vec2(0, -1), Vec2(-1, -1), + Vec2(-2, -1), Vec2(-3, -1), Vec2(-4, -1), + Vec2(-4, -2), Vec2(-4, -3), Vec2(-4, -4)}; + CHECKEQ(v, res); +} + +void testRandomExit() { + Rectangle r(5, 10, 15, 20); + for (int i : Range(1000)) { + Vec2 v = LevelMaker::getRandomExit(r); + CHECK((v.x == 5) ^ (v.x == 14) ^ (v.y == 10) ^ (v.y == 19)); + } +} + +void testCombine() { + vector words { "pok", "pak", "pik", "puk" }; + CHECKEQ(combine(words), "pok, pak, pik and puk"); + words = { "pok" }; + CHECKEQ(combine(words), "pok"); + words = { "pok", "pik" }; + CHECKEQ(combine(words), "pok and pik"); +} + +int main() { + Debug::init(); + testStringConvertion(); + testTimeQueue(); + testRectangleIterator(); + testValueCheck(); + testSplit(); + testShortestPath(); + testAStar(); + testShortestPath2(); + testShortestPathReverse(); + testRandom(); + testViewTitlePrefix(); + testRange(); + testContains(); + testPredicates(); + testOptional(); + testMustInitialize(); + testVec2(); + testConcat(); + testTable(); + testVec2(); + testRectangle(); + testProjection(); + testRandomExit(); + testCombine(); + testVec2Box0(); + testVec2Box1(); + testVec2Box2(); + Debug() << "-----===== OK =====-----"; +} diff --git a/time_queue.cpp b/time_queue.cpp new file mode 100644 index 000000000..2ef80ce90 --- /dev/null +++ b/time_queue.cpp @@ -0,0 +1,69 @@ +#include "stdafx.h" + +using namespace std; + +TimeQueue::TimeQueue() : queue([](QElem e1, QElem e2) { + return e1.time > e2.time || (e1.time == e2.time && e1.creature->getUniqueId() > e2.creature->getUniqueId()); +}) {} + +void TimeQueue::addCreature(PCreature c) { + queue.push({c.get(), c->getTime()}); + creatures.push_back(std::move(c)); +} + +PCreature TimeQueue::removeCreature(Creature* cRef) { + int ind = -1; + for (int i : All(creatures)) + if (creatures[i].get() == cRef) { + ind = i; + break; + } + CHECK(ind > -1) << "Creature not found"; + PCreature ret = std::move(creatures[ind]); + creatures.erase(creatures.begin() + ind); + dead.insert(ret.get()); + return ret; +} + +vector TimeQueue::getAllCreatures() const { + vector ret; + for (const PCreature& c : creatures) + ret.push_back(c.get()); + return ret; +} + +void TimeQueue::removeDead() { + while (!queue.empty() && dead.count(queue.top().creature)) + queue.pop(); +} + +Creature* TimeQueue::getMinCreature() { + CHECK(creatures.size() > 0); + removeDead(); + QElem elem = queue.top(); + if (elem.time == elem.creature->getTime()) + return elem.creature; + else { + queue.pop(); + removeDead(); + queue.push({elem.creature, elem.creature->getTime()}); + return queue.top().creature; + } + /*for (PCreature& c : creatures) + if (c->getTime() < min || (c->getTime() == min && c->getUniqueId() < res->getUniqueId())) { + res = c.get(); + min = c->getTime(); + }*/ +} + +Creature* TimeQueue::getNextCreature() { + Creature* c = getMinCreature(); + return c; +} + +double TimeQueue::getCurrentTime() { + if (creatures.size() > 0) + return getMinCreature()->getTime(); + else + return 0; +} diff --git a/time_queue.h b/time_queue.h new file mode 100644 index 000000000..5a2e58a01 --- /dev/null +++ b/time_queue.h @@ -0,0 +1,29 @@ +#ifndef _TIME_QUEUE_H +#define _TIME_QUEUE_H + +#include +#include "util.h" +#include "creature.h" + +class TimeQueue { + public: + TimeQueue(); + Creature* getNextCreature(); + vector getAllCreatures() const; + void addCreature(PCreature c); + PCreature removeCreature(Creature* c); + double getCurrentTime(); + + private: + vector creatures; + struct QElem { + Creature* creature; + double time; + }; + priority_queue, function> queue; + unordered_set dead; + void removeDead(); + Creature* getMinCreature(); +}; + +#endif diff --git a/timer_var.h b/timer_var.h new file mode 100644 index 000000000..f2a0f38ef --- /dev/null +++ b/timer_var.h @@ -0,0 +1,31 @@ +#ifndef _TIMER_H +#define _TIMER_H + + +class TimerVar { + public: + void set(double t) { + timeout = t; + } + + void unset() { + timeout = -1; + } + + bool isFinished(double currentTime) { + if (timeout >= 0 && currentTime >= timeout) { + timeout = -1; + return true; + } + return false; + } + + operator bool() const { + return timeout >= 0; + } + + private: + double timeout = -1; +}; + +#endif diff --git a/town_names.txt b/town_names.txt new file mode 100644 index 000000000..c50b196eb --- /dev/null +++ b/town_names.txt @@ -0,0 +1,5629 @@ +Abymin +Abymina lui +Abyminarë +Abymine +Abyminești +Abyminge +Abyminis +Abyminja +Abyminnë +Abyminë +Adjud Odorj +Adjud Saing +Adjungavnik +Adjunge +Adjungjin +Adjungs +Adjungue +Adjura +Adjurad +Adjurahor +Adjurahovda +Adjuraine +Adjurance +Adjuravec +Adjuraș +Adjusford +Adjusforos +Adjusforra +Adjusfors +Adjusna +Adjusne +Adjusniçan +Adjutällmo +Adjutë +Adjutów +Adjuviken +Adjuvița +Aignan +Aignance +Aignandre +Aignandë +Aignantines +Aigne +Aignolan +Aignon +Aigny-son +Aigny-sunda +Aisna +Aisniçan +Aisnysa +Aisnärnon +Aisnärp +Aisnäs +Ajac +Ajacce +Ajaccie +Ajaccineux +Ajace +Ajacka +Ajackamëz +Ajackekil +Ajacken +Ajacker +Ajackås +Ajacków +Ajacov +Ajacova +Ajacovalș +Ajacoven +Ajacovence +Ajacovensht +Ajacovikka +Ajacovës +Ajacz +Ajaczów +Ajaczówek +Alek +Alliczyn +Allin +Allishë +Alpe +Alpenicë +Alper +Alpers +Alpersek +Alperski +Alpes +Alpes-Cova +Alpes-Côte +Alpes-Roșu +Alsa +Alunioas +Amar +Amara +Amarance +Amarbergiu +Amarbeș +Amarby +Amarca +Amardy +Amare +Amaredzice +Amargorz +Amariana +Amarianne +Amaritoupes +Amark +Amarka +Amarmorbyn +Amarnan +Amarne +Amarne-Sais +Amarnes +Amaron +Amarp +Amarrenja +Amarsrumiei +Amarwo +Amarzeźnov +Amarzycea +Amarzyn +Amarzyń +Amarë +Amarön +Amaröpi +Amarörne +Anin +Anin Tult +Anin-Jiu +Anin-Vallin +Anine +Aningebro +Aninte +Anintea +Aninë +Anka +Ankalba +Ankalberz +Ankamë +Ankarsek +Ankarstorp +Ankarö +Anne +Annes +Aqui +Aqui Marne +Aqui Săș +Aquianceava +Aquianski +Aquianța +Aquies +Aquil +Aquil-lain +Aquillace +Aquille +Aquillshö +Aquimperski +Aquire +Aquirea +Aquires +Aquirevik +Aquireși +Aquis +Aquita +Aquita Mur +Aquitad +Aquitance +Aquitanța +Ardu-Rouais +Arduș +Arge +Arger +Arges +Arken +Arker +Arle +Arle-d'Ois +Arlen +Arrandë +Arrazhd +Arrännées +Arrénées +Arrë +Arród +Arrój +Arrö +Ascq +Asni +Asni Aramsh +Asnia +Asnic +Asnicz +Asnicë +Asniec +Asnieine +Asnik +Asninge +Asninov +Asninoît +Asnion +Asnioni +Asnionne +Asnionsta +Asnis +Asnistrance +Asnièrel +Atla +Atla Hunion +Atla-Boire +Atlabrazd +Atlac +Atladź +Atlais +Atlaise +Atlaisht +Atlaispoja +Atlaisti +Atlaisłyn +Atlaj +Atlakopojan +Atlakovence +Atlakovik +Atlan +Atland +Atlande +Atlanë +Atlanö +Atlań +Atlața +Aulnay +Aulnaye +Aulnays +Aulnda +Aulndal +Aulnik +Aulniken +Aulno +Aulny +Aurie +Aurik +Aurille +Aurin +Auritillvik +Auritos +Auritte +Auvec +Auveliczewo +Auvelier +Auvelik +Auveline +Auveliq +Auvelishë +Auvence +Auvenice +Auvenis +Auvenstany +Auverges +Auveries +Avig Sil +Avigna +Avignan +Avignau +Avigno +Avigny +Avigue +Avigues +Avigueux +Aviguevie +Avri +Avri Ilos +Avri Iult +Avria +Avrie +Avriebo +Avrieby +Avrignan +Avrik +Avrikum +Avrin +Avrinne +Avrisille +Avrisis +Avristandy +Avristrance +Avrittana +Azur +Azur-Marp +Azur-Merine +Azur-Ori +Azur-Saine +Azur-Seine +Azura +Azure +Azurea +Azureș +Azurești +Azurfatlan +Azurg-Ocna +Azurge +Azurgeby +Azurläge +Azurlëz +Azurlöv +Azuromire +Azuromłyn +Azurorbican +Azurormark +Azurrabë +Azurrance +Azurranchem +Azurras +Azurres +Azurs +Azurt +Bacă +Bagnance +Bagne +Ballbelsta +Balle +Ballenhów +Ballvik +Bally +Banka +Banków +Barce +Barcur +Bark +Barkel +Barkence +Barraszë +Barraszów +Barre +Barred +Barrednicë +Barredniei +Barreni +Barrölnik +Bas-Côte +Bas-dec +Bas-du-Seil +Bech +Becha Mare +Bechedoarp +Bechefów +Beches +Bechilis +Bechire +Bechirona +Bechni +Bechty +Bechów +Bechówka +Beno +Benon +Benov +Benoy +Benoît +Berga +Berghiais +Berghitain +Besk +Beski +Besko +Beskova +Beskäge +Beła +Beławka +Bełock +Bełodaris +Bełomża +Bełoplik +Biał +Biała +Białasz +Białawy +Białoczewo +Białogozhd +Białomin +Białopojas +Białotów +Biaśl +Bica +Bicarin +Bihoiu +Biholm +Biholmais +Biholmena +Biholmsund +Bihova +Bilia +Biliașov +Bilierhet +Biliertou +Bilik +Bilin +Biline +Bilishë +Bilitów +Biliçonis +Biliț +Bjäck +Bjäcka +Bjäckence +Bjäckier +Bjälj +Bjälle +Bjämtö +Bjängerre +Bjänne +Bjärhamți +Bjärne +Bjärp +Bjärpå +Bjärr +Bjärreneu +Bjäslöcki +Bjästawska +Bjäster +Bjäsvik +Bjäsö +Bjö +Bjöck +Bjödiken +Bjöfov +Bjög +Bjögavenis +Bjökës +Bjöna +Bjöv +Bjövec +Bjövs +Blom +Blomance +Blombes +Blomice +Blomiczyce +Blomie +Blomier +Blomiers +Blomierznon +Blomieu +Blomin +Blomire +Blomiș +Blomișău +Blomița +Blomorg +Blomorp +Blomors +Blomsta +Blomstava +Blomster +Blomsto +Blomtë +Boch +Bochelitta +Bogurodal +Bogusz +Bois +Boise +Boisht +Boisis +Boiskalba +Boiskie +Boison +Boles +Bolia +Borghinów +Borlad +Borlärny +Borsekin +Borșa Mur +Borșaniewy +Borșanis +Borșoardy +Borșov +Botorp +Botos +Botoș +Bouceasa +Bouceavajë +Boucet-Lays +Boult +Bour +Bour-Sava +Bour-Yon +Bourgueux +Boushë +Bova +Bovalski +Bragele +Bragne +Bragno +Bragny +Brance +Brancea +Branța +Braș +Braș-Sein +Braș-Seine +Brașcani +Brași +Brați +Bret +Bret-Lays +Brezio +Brezion +Brita +Britousses +Britouvița +Brobre +Brobreux +Brobrz +Brobrzno +Brodziord +Brodëz +Brzoi +Brzowice +Brzozë +Bräge +Brälj +Bräslärre +Brăca +Brăil +Brăray +Buce +Bucea +Buhult +Buhuș +Bulqizil +Bulqizilis +Bumbes +Bumbov +Bumbovë +Burg +Burghin +Burgunion +Burladźnin +Burr +Burrais +Burras +Burras-Rhin +Burre +Burregue +Burrkës +Burrë +Buzău +Buzăului +Buzăulunde +Buzăuța +Bydgidhult +Bydgidi-Jiu +Bydgidzka +Bydgidzko +Bydgidzyce +Bydgos +Bytonne +Bytorp +Bytos +Bytou-Roski +Bytou-Suce +Bytoș +Bytoș Arg +Bytoși +Bytoștimes +Bytällby +Bytällmo +Bytère +Byté +Bytë +Bârgu Nea +Bârguede +Bârgund +Bârgurovet +Bârlaiski +Bârlan +Bârlanë +Bäck +Bäckhamn +Bälgvik +Bälje +Bälki +Bälkie +Bälle +Bällm +Bétei +Béteil +Béteine +Béteinek +Béthult +Béthultord +Béthușnon +Béthuști +Bézie +Bézieby +Béziebyn +Béziec +Béziei +Bézielno +Bézielski +Bézieu +Bön +Băila +Băila-Bres +Băilabrazd +Băilac +Băilais +Băilaispol +Băilant +Băile +Băilea +Băiles +Băileńki +Bălaj +Bălajva +Bălajvani +Bălajë +Bălce +Bălti +Băltique +Bălădlea +Bănea +Bănea Time +Băneava +Băneavajë +Băneș +Bănești +Bła +Bła Podë +Błaszew +Błaszyce +Błaszyn +Błaszów +Bława +Bławeck +Bławek +Bławer +Bławice +Bławicz +Bławiec +Bławier +Bławy +Błaża +Cajvancea +Cajvandvik +Cala +Cala Hund +Cala-Bari +Cala-Ben +Cala-Billy +Cala-Boi +Cala-Gorp +Calac +Calad +Calage +Calais +Calaispoja +Calaj +Calan +Calance +Caland +Calanda +Calandritou +Calant-Saç +Calantighen +Calantiq +Calaski +Calatna +Calatne +Calața +Caluironis +Calunte +Caluntre +Cara +Carabótka +Caraine +Carancea +Carand +Caras +Carcin +Carcoirence +Carcure +Cehult +Ceno +Cenowo +Cenowolik +Cenoy +Cent +Cent-Den +Cent-Dence +Cent-Denis +Cent-Denti +Cent-Denë +Cent-Laurel +Cent-Mari +Cent-Que +Cent-Quen +Cent-Quenis +Centeryd +Centes +Centfermău +Centinnë +Centiq +Centiques +Centunnau +Cerg +Cerga +Cerges +Cergești +Cerghilt +Cernarnes +Cerne +Cerneby +Cernon +Chalomieck +Chamsheni +Chamurlëz +Chamț +Chamța +Char +Chara +Charamme +Charamureș +Charamë +Charașov +Charca +Charedgilny +Charenis +Charie +Charkelsacz +Charne +Charnei +Charszcz +Chartuzyn +Charyd +Charzebros +Charöv +Chaumon +Cherk +Cheron +Chmiers +Chmin +Chol +Cholec +Cholomier +Chorce +Chorna +Chorp +Chorsica +Chorski +Chorskie +Chorsko +Châmbești +Châmbov +Chânce +Chârghin +Chârlașov +Chârly +Chârlön +Ciac +Ciaccin +Ciace +Ciacka +Ciackë +Ciacovenës +Ciacz +Ciaczów +Ciot +Ciota Bilia +Ciota Ialon +Ciotawa +Ciotoșanis +Ciotoși +Ciotoșu +Ciottek +Ciottne +Ciotów +Ciuc +Ciucea +Ciuceasain +Ciuceava +Ciucec +Ciuchares +Ciuchero +Ciuchet +Ciucion +Ciuck +Cler +Clerfów +Clerg +Clergne +Clertë +Clervë +Cleryd +Clerz +Codlac +Codlas +Codlicazhd +Colombeș +Colomin +Colon +Colos +Comténë +Cons +Conskopol +Conskovence +Constalene +Constance +Constaszyce +Constaszów +Constaw +Constawa +Consurzica +Copșani +Corberlais +Corbo +Cors +Corsecki +Corsice +Cova +Covalle +Covanis +Covasne +Cré +Crée-Dzie +Crée-Mari +Crée-Sein +Crée-lëz +Crées +Créessier +Crénästea +Crénées +Crénë +Crénów +Crénö +Crérik +Créte +Crétei +Créter +Créteviken +Créthunede +Créthunion +Créuna +Créund +Créundy +Créune +Créunedje +Créunești +Créunga +Créunio +Créunion +Créunna +Créunta +Cugi +Cugidiu +Cugiers +Cugiervë +Cugilnda +Cugilno +Cugir Cons +Cugiu +Cugiu Giurt +Cugiu Hune +Cugiu Makë +Cuire +Cuire-Alpes +Cuire-Sains +Cuireș +Cuirola +Cuirolune +Cuiron +Cuironnes +Czar +Czaramshë +Czarancea +Czaranów +Czarași +Czarce +Czaren +Czari Sucea +Czarki +Czarmors +Czarna +Czarne +Czarnes +Czarock +Czaronieu +Czaronnes +Czec +Czec Kuj +Czecea +Czeceava +Czechova +Czecica +Czecino +Czecki +Czecuies +Czecy +Czeczeg +Czel +Czelad +Czele +Czelenaute +Czelenays +Czelenis +Czelgård +Czelicz +Czeline +Czelita +Czelko +Czelkoffita +Czelkowo +Czelnion +Czelski +Czelskie +Czelstrzyce +Czeluń +Câmbo +Câmbourrel +Câmbovik +Câmboviți +Câmpes +Câmpi +Câmpina +Câmpinge +Cërmes +Cërmet +Cërre +Cërrel +Cërres +Cërrezur +Cërri +Cërrin +Cërriti +Côte +Călaj +Călajva +Călajë +Călce +Călcea +Căltick +Căltineu +Călă +Călăvoie +Căraș +Cărași +Cărașov +Cărașova +Cărați +Cărbune +Cărbuty +Cărbutyn +Cărmănea +Cărne +Cără +Cărălatna +Cărăne +Cărărmon +Cărărne +Dalba +Dalba luire +Dalbergne +Dalberin +Dalbmiers +Dalbmin +Dalbmișane +Dand +Danda +Dande +Dander +Danderhejde +Danderk +Dandrik +Dandski +Dandy +Dandë +Dandëz +Dara +Darad +Daragosznon +Daramurefov +Darasne +Daray +Dej Cler +Delvan +Delvance +Delvandrów +Delvarby +Delvarne +Delves +Delviken +Deni +Deni Arg +Deni Mazhd +Denicanion +Denicz +Deniczew +Denicë +Denieineby +Deniew +Deniewo +Deniki +Deningård +Denion +Denionne +Deniquita +Denis +Deniți +Deuirek +Divja +Divjak +Divjakoff +Divjaków +Diées +Diéesłec +Diénées +Diénë +Diénów +Diénö +Diéund +Diéunești +Diéunga +Djur +Djur-Maron +Djur-Marp +Djur-Seil +Djur-Seine +Djurcovasne +Djure +Djureforce +Djureford +Djureforp +Djurekë +Djurenjas +Djureș +Djurești +Djurfantes +Djurg-Octei +Djurgiu +Djurrad +Djurre +Djurres +Djurreux +Djurreș +Djurt +Dobrazimrë +Dobre Suce +Dobro +Dobrze +Dobrzew +Dolnda +Dolndal +Dolndarik +Dolnik +Dolno +Dolny +Dorn +Dorne +Dorneș +Dorniken +Dornum +Douade +Douarës +Douarëz +Doubaw +Doubawski +Doubcze +Doubik +Doubin +Doubine +Doubines +Doubliq +Dragad +Dragadecuis +Dragel +Dragelles +Dragiu +Dragne +Dragnoaski +Dragnolen +Dragny +Drague +Draguirence +Dragålais +Dragów +Drava +Dravaj +Dravajnö +Drawa +Drawiec +Drawienia +Drawka +Drawkamëz +Drawska +Drawsko +Drawy +Dreu +Dreu Silla +Dreuilice +Dreuillac +Drohice +Drohicz +Drohova +Drohoven +Drohovence +Drohovenne +Drohovë +Duklin +Dukline +Duklinehoie +Dukling +Dukliniq +Duklinnów +Duklins +Duklinë +Dumbaver +Dumbavik +Dumbes +Dumbeș +Dumborp +Dumborrad +Dumbov +Dumbova +Dumbovasa +Dumbovda +Dumbovenis +Dumbovenjak +Dumbovița +Dumbovë +Dumbrăila +Dumbrą +Dwój +Dwóroye +Dwórzyce +Dwóränge +Dwóränges +Dwórå +Dwórëz +Dwóród +Dwótkowo +Dwów +Dwów Polaj +Dwóweck +Dwówecko +Dwówek +Dwówer +Dwówka +Dwóze +Dwózew +Dzie +Dzie Ślą +Dziec +Dzieckenica +Dziedzka +Dziedzkopol +Dziei +Dzielik +Dzieluj +Dzier +Dziercur +Dziers +Dziersek +Dziersjë +Dziersur +Dziersuren +Dzierz +Dzierzegi +Dzierzyn +Dziescatna +Dziescq +Dziestana +Dziew +Dzień +Dâmbo +Dâmboda +Dâmbohor +Dâmbohovë +Dâmborges +Dâmbork +Dâmborne +Dâmbornont +Dâmbour +Dâmboura +Dâmbourgiu +Dâmbourre +Dâmbourres +Dâmbovie +Dâmboville +Dâmboviț +Dâmpe +Dâmpenion +Dâmper +Dâmpers +Dâmpes +Dâmpi +Dâmpina +Dâmpinan +Dâmping +Dâmpinge +Dâmpingeby +Dâmpingjin +Dâmpingowa +Dâmpings +Dărași +Dărașov +Dărașova +Dărbutyn +Dărmăneș +Dărne +Dărneșion +Dărărne +Dărăta +Dąbki +Dąbko +Dąbro +Dąbrzewy +Dąbrzyn +Dąbygge +Dąbyggen +Dąbygges +Dęba +Dębice +Dębichet +Dębicz +Ensta +Enstaber +Enstaberine +Enstance +Enstandy +Enstane +Enstani +Enstanța +Enstava +Enstaw +Enstawka +Enstawy +Enstek +Enstekë +Enster +Ensterine +Ensterrance +Ensteryd +Ensterz +Enstine +Enstinja +Ensto +Enstoczeg +Enstor +Enstore +Enstoroszcz +Enstorov +Enstors +Enstrad +Enstrain +Enstrance +Enstrastorp +Enstrzyce +Enstrzyn +Enstère +Enstèrel +Enstë +Ermo +Ermolöv +Ermorbyn +Ermors +Ermorstoș +Ermorsunion +Erse +Erse-de-de +Ersebrazhd +Ersebro +Ersec +Erseda +Ersedal +Ersekänges +Ersekärr +Erselberg +Erselby +Ersele +Erselle +Ersellin +Ersenforov +Erseryd +Ersesson +Ersessonice +Ersmarașov +Ersmardy +Ersmari +Ersmarne +Ersmol +Ersmol-sur +Ersmoles +Ersmoliów +Ersmolj +Ersmolomor +Ersmolski +Essoka +Esson +Essonna +Essonnö +Essonshë +Essousca +Essoushë +Essoussin +Essousz +Essouszów +Evlia +Evliaj +Evliajvasna +Evliajë +Evliajëz +Evlica +Evlice +Evliceava +Evlich +Evliczewy +Evliczów +Evlier +Evlierin +Evlierkë +Evlik +Evlin +Evline +Evlineu +Evlingar +Evlingariaj +Evlinge +Evliq +Evlish +Evlishen +Evlishentra +Evlishë +Evlisz +Evlisztyn +Evlisztynge +Evlitånna +Evlitë +Evlitów +Evliwiczec +Evliç +Evliçon +Evliçov +Evlița +Ferr +Ferra +Ferrabrouge +Ferraintarp +Ferras +Ferraska +Ferraskinge +Ferrastë +Ferrastów +Ferraszka +Ferre +Ferred +Ferrede +Ferregi +Ferrelby +Ferrelence +Ferrelenon +Ferrelice +Ferrence +Ferrenis +Ferrentique +Ferrik +Ferrikens +Ferrita +Ferritours +Ferritu +Ferrvik +Ferrë +Ferrö +Fien +Fience +Fieneurtou +Fieni +Fieni Bilia +Fienis +Fienjakë +Fiensh +Fiente +Fientes +Filicard +Filichni +Filichnis +Filik +Filin +Filine +Filingjin +Filitön +Fini +Finicë +Finiew +Finiki +Finikie +Finion +Finionkë +Finionne +Finis +Finiów +Fjäck +Fjäcka +Fjäcki +Fjäcklier +Fjäcko +Fjäge +Fjäger +Fjälj +Fjälje +Fjälkopol +Fjälland +Fjällby +Fjälliec +Fjällieck +Fjämtösa +Fjänge +Fjängjin +Fjängs +Fjännarne +Fjänne +Fjärheine +Fjärne +Fjärp +Fjärrabut +Fjärre +Fjärrvina +Fjärvik +Fjäs +Fjästance +Fjäster +Fjästornis +Fjästère +Fjäsvik +Fjäsåker +Fjäsère +Fjäsères +Fjävja +Fjävjak +Font-Boires +Font-Denfov +Font-Lays +Font-Pasa +Font-Pautei +Font-Sava +Font-d'Ois +Fonte +Fonte-Atla +Fontiq +Fontre +Fontrebrzew +Fontrovence +Fontune +Fontów +Fors +Forsba +Forsbanis +Forsec +Forsekë +Forsjë +Forsjö +Forski +Forskie +Forskë +Forsmarów +Forstorp +Forsunna +Forsura +Forszesze +Fran +Frana +France +Francea +Franceava +Franch +Francon +Frand +Frandal +Frandy +Frandë +Frane +Franelan +Frangagów +Frangusz +Frani +Franiec +Franne +Franneuvras +Franski +Franti +Frantin +Franów +Franța +Fren +Frence +Frenis +Frenisis +Frenteil +Frin +Frins +Frint-Deni +Fush +Fushampol +Fushen +Fushenjak +Fushenjas +Fushkopi +Fushkowo +Fusht +Fushulce +Fushult +Fushytna +Fushytomice +Fushë +Fushës +Fushëz +Fushökë +Fushövs +Făget +Făget-Mark +Făget-Mica +Făgetonie +Făgetrond +Făgăce +Făgăcuen +Făgăla +Făgăltor +Făgăne +Făgătów +Făgău +Fălaj +Fălajva +Fălajë +Fălce +Fălti +Făltilliec +Făltin +Fălău +Gala +Gala-Garnë +Galage +Galainë +Galais +Galaisława +Galan +Galața +Gammarance +Gammari +Gammark +Gammarontra +Gammelouis +Gare +Garebro +Garede-Frad +Garehor +Garek +Garel +Gareniew +Gares +Garezur +Gareș +Gareși +Garo +Garohijak +Garonne +Garovence +Garovet +Garovoiei +Garowo +Garwol +Gdyn +Gdyn nad +Gdyn nadec +Gdyna +Gdyne-Marna +Gdyne-Seina +Gdyngonion +Gdyngozhd +Gdynie +Gdyniec +Gdyniecka +Gdyniers +Gdynki +Gdynkie +Gdyno +Genna +Genne +Genne-Cara +Gennec +Gennelice +Gennes +Gennlais +Gennlakovik +Gennland +Gennön +Geoagiu +Geoaiec +Geoara +Geoarabune +Geoaraholm +Geoaramures +Geoarance +Geoarand +Geoaras +Geoarasnys +Geoarazhë +Geoaraș +Geoarpacken +Geoasa +Geoasarne +Germance +Germdë +Germeim +Germorp +Germåken +Gheoagów +Gheord +Gheordenë +Gheordy +Gheorg +Gheorga +Gheorgagå +Gheorghiș +Gheorgonin +Gheormance +Gheormet +Gheormoni +Gheormåken +Gher +Gherblins +Ghergnanski +Ghergne +Gherinarë +Gherków +Ghermëz +Gherna +Ghernum +Gherny +Gherrance +Ghers +Ghervë +Gherz +Gherzy +Gherżon +Ghimarenjak +Ghimarn +Ghimbarken +Ghime +Ghimes +Ghimestany +Ghimița +Ghimnis +Ghimräs +Giro +Girobyliq +Girocinon +Giroda +Girodzkovie +Girogoin +Girogoins +Girogoszewo +Girogowa +Girohitaw +Girombov +Gironis +Giropojas +Girore +Girorence +Girorevina +Girosz +Giroszcz +Giroszewo +Girotanța +Girovec +Girovelin +Girovence +Girovernód +Girovestord +Girovodal +Girovoie +Girowa Wiec +Giur +Giur-Mar +Giur-Mara +Giur-Marbo +Giur-Marby +Giur-Mardy +Giur-Marne +Giure +Giureș +Giurești +Giurgieryd +Giurorhejde +Giurra +Giurrance +Giurt +Giurthuna +Gliwic +Gliwica +Gliwicark +Gliwicarne +Gliwicht +Gliwickla +Gliwicklin +Gliwickline +Gliwicov +Gliwicz +Gliwiczka +Gliwiczyn +Gliwicë +Gliwicëz +Glum +Glumberga +Glumieck +Glumielumsh +Glumier +Glumonne +Glumonneu +Glumsluita +Glumsläcka +Glumur-Suda +Glumurcova +Glumurrance +Glumë +Gnie +Gniec +Gniecki +Gniegłopon +Gniei +Gnielica +Gnielkopolj +Gnielnda +Gnielny +Gnielomsh +Gnieloupe +Gnielsh +Gnielstari +Gnieluń +Gnieni +Gnienion +Gnient-Den +Gnier +Gniers +Gniersjë +Gnierstock +Gnierz +Gnierzyce +Gnierzyna +Gniestawski +Gnień +Gnieński +Gogomin +Gogomiț +Gogozhd +Golui +Goluire +Goluj +Goluçov +Gorce +Gorcea +Gorcin +Gorz +Gorzemin +Gorznowo +Gorzyn +Gosica +Gosicat +Gosille +Gosillie +Gosionte +Gosis +Gous Silly +Gousz +Gośl +Gośni +Gośni Arg +Gośnique +Grammainvik +Grammand +Grammans +Grammar +Grammarance +Grammark +Grammesznoy +Grampojas +Gramur +Gramur-Marp +Gramur-Sais +Gramure +Gramë +Gramës +Gramëz +Gramö +Gramöja +Gramț +Gramța +Greva +Grever +Grevercueni +Greverga +Grevergne +Grevik +Groda +Grodaron +Grodhan +Grodë +Grägen +Gräges +Grälje +Grällby +Grärp +Gräs +Grästorra +Gräsvik +Grävjak +Guad +Guadec +Guadecuian +Guadelviț +Guadiron +Guadzier +Guadzion +Guadź +Guales +Gualin +Gualis +Guallance +Gualălac +Gubik +Gubiu +Gubiș +Gubiși +Gubișnov +Guia +Guia Timnis +Guiasta +Guiała +Guiaławka +Gunna +Gunne +Gunnösace +Gusseby +Gusson +Guya +Guyance +Gór Maren +Gór Mari +Gór Mazd +Gór Mazhd +Gór Mał +Gór Mice +Gór Mur +Gór Murt +Górowa +Górowergy +Górowerlan +Górowie +Górowo +Górowogusz +Górój +Górów +Górówka +Gătai +Gătaine +Gătaint +Gătaintluj +Gătaintrel +Gătaintund +Gătainvil +Gătais +Gătaison +Gătâlcea +Gătâlon +Gătâmbes +Gătâmbor +Gătâmbord +Gătâmpen +Gătâmpine +Gătâmpon +Gătârgiu +Gătârgon +Gătârgy +Gătârlad +Gătâtimes +Gătâtinis +Gătâș +Głubczy +Głubczyck +Głubczycka +Głubczyn +Głubczyno +Głubczynon +Głubczynov +Głubczytna +Głubczytne +Hack +Hacklik +Hacksfora +Hagued +Haguenë +Halle +Hallier +Hallsh +Harga +Hargiuc +Hargneluj +Harguna +Harguntany +Haut +Haut-Rhilt +Hauta Mais +Haute-de +Hautes +Hauts-Côte +Hauts-Orger +Hauts-de +Hauts-den +Hauty +Hautå +Hautås +Hede +Hede-Calsta +Hede-Cône +Hede-Fra +Hede-France +Hede-Franch +Hede-Frasna +Hede-Marne +Hede-Mica +Hede-Seim +Hedelomin +Herbers +Herberz +Herbihani +Herbihor +Herbihorp +Herbihorski +Herbunion +Herbuntad +Herbyn +Herbyniaj +Hercas +Hercaska +Hercasnau +Herce +Hercea +Hercie +Hercielkopi +Hercieu +Hercoie +Hercoired +Hercoireux +Hercoitou +Hercul +Herculce +Hercult +Hercurefor +Hercureș +Hercurești +Himain +Himaisłcz +Himalieux +Himalin +Himalité +Himan +Himand +Himande +Himander +Himandë +Himansure +Himar +Himaranch +Himark +Himarkentes +Himarkvinë +Himarköga +Himarów +Hish +Hishamța +Hishence +Hishkoveni +Hisht +Hishulce +Hishë +Hishögdeå +Hjuve-Cala +Hjuve-Calan +Hjuve-Fran +Hjuve-et +Hjuver +Hjuvergne +Hjuverhen +Hjuverre +Hjuverri +Hjuverrikum +Hjuvers +Hjuvertorp +Hjuvertou +Hjuverzyn +Hjuvilnon +Hjuvilva +Hjuvra +Hjuvrabë +Hjuvraing +Hjuvrance +Holm +Holmaintów +Holmais +Holmen +Holmenicz +Holmenis +Holmenița +Holmiecki +Holmiei +Holmielik +Holmienne +Holmień +Holmo +Hore +Hore-Alpes +Hore-Arre +Horeguemors +Horel +Horence +Horent +Horentiq +Hores +Hores-Côte +Horeuille +Horeux +Horeș +Horeși +Horeșnaia +Huaramëz +Huaren +Huarmon +Huaron +Huarp +Huarre +Huarë +Huaröveil +Hune +Hune Consta +Hune-Seil +Hune-Seine +Huneby +Hunedoarie +Hunehan +Hunei +Hunes +Huneux +Huneviker +Huneșance +Huș +Huș Mara +Hușance +Hușnon +Huști +Hyère +Hyèred +Hyèrefors +Hyèrel +Hyèrence +Hyèrenja +Hyèretorp +Hyèreux +Hyèrevik +Hyèrezozë +Hyèrezur +Hyèreș +Hyès-Côte +Hyès-Orik +Hyès-dec +Hyèvray +Hyèvrays +Hyèvres +Hyèvreuil +Hyèvreux +Hyèvrigna +Hyèvrigno +Hyèvrignon +Hälgvik +Hälgviken +Hälgviki +Hälje +Häljebacka +Hälki +Hälkind +Hällene +Hällermork +Hällmain +Håge +Håla +Hållski +Höga +Högbohovda +Högde-Fra +Högde-Mara +Högde-Sud +Hökö +Hökön +Hökörv +Hököv +Hörlața +Hörna +Hörräfsna +Hörrëz +Hörrów +Ialombes +Ialomieryd +Ialomines +Ialomsh +Ialon +Ialonstaine +Ialos +Ialoupe +Ialouprant +Ialovec +Ialovelish +Ialoverg +Iaș +Iașance +Iașov +Iașovë +Iaști +Iaștin +Iaștiques +Iașu +Idal +Idal-d'Ois +Idal-de-dec +Idal-de-den +Idalea +Idalenicht +Idalentrzy +Idalew +Idalia +Idalianna +Idaliask +Idaliques +Idallais +Idalle Bes +Idallisht +Idalliç +Idallshan +Idallsta +Idallstre +Idalmar +Idalon +Idalowa +Idalstand +Idaluireux +Idaluis +Idalösa +Idalăce +Idalșu +Iern +Ierna +Iernava +Ierne +Iernebiș +Iernei +Ierneil +Ierneine +Iernești +Iernik +Iernon +Iernum +Ierny +Iernów +Iernówka +Ilfors +Ilforse +Ilforsek +Ilforsjë +Ilforsjöd +Ilforski +Ilforskärr +Ilforsmar +Ilforstorp +Ilforstos +Ilfortreși +Ilfortrowo +Iloaielno +Iloaiennes +Iloara +Iloaraine +Iloarance +Iloasa +Iloasarëz +Imie +Imieby +Imiebylin +Imiec +Imiecka +Imiei +Imieliq +Imielkopeni +Imielsh +Imielstain +Imielstes +Imiennölle +Imiennön +Imientes +Imierce +Imiercie +Imiers +Imiersjë +Imierstorp +Imierzebo +Imierzewo +Imierzno +Imierzozów +Imierzyce +Imiescq +Imiester +Imiesz +Imieszcz +Imieu +Imiew +Imień +Imieńki +Indriaiski +Indriaj +Indrótkowa +Indrów +Indrąsk +Indrąski +Inga +Ingadi-Pyre +Ingadiken +Ingagård +Ingalik +Ingaliq +Ingarceamț +Ingarceava +Ingarehoiu +Ingares +Ingaryd +Ingaveillin +Ingavence +Ingavet +Isac +Isacce +Isace +Isacka +Isacken +Isacker +Isackë +Isacków +Isacovas +Isacovë +Isacz +Isaczë +Isacăilla +Isacărance +Isès-Côte +Isès-de +Isèvres +Isèvresko +Isèvreu +Isèvrigny +Iulia +Iuliaj +Iuliajëz +Iuliașova +Iulicasson +Iulicat +Iulich +Iulicz +Iuliczew +Iulierforse +Iuliernutby +Iulieron +Iuliers +Iuliertë +Iulik +Iulin +Iuline +Iuline-Arre +Iulines +Iulinevik +Iulinge +Iulingjin +Iuliq +Iulish +Iulisht +Iulishytta +Iulishë +Iulisz +Iuliszewy +Iulitälje +Iulitë +Iulitów +Iuliç +Iuliçance +Iuliști +Iuliț +Iwon +Iwon-sur +Iwon-surt +Iwona +Iwoni +Iwonka +Iwonki +Iwonne +Iwonnedoar +Iwonower +Iwonsta +Iwonstan +Iwonstance +Iwonstence +Iwonstorp +Iwont-Cuire +Iwontanța +Iwontgeryd +Iwontgerz +Iwontimet +Iwontlarce +Iwontrilno +Iła +Iła Podni +Iła Prance +Iłaszcz +Iłaszki +Iłaszno +Iłasztyn +Iłaszów +Iławecki +Iławek +Iławer +Iławice +Iławiec +Iławy +Iławy Dol +Iłaż Wier +Iłaża +Iłażon +Iłażonis +Iłażowa +Jabłobro +Jabłobrzew +Jabłocko +Jabłocze +Jabłodzion +Jabłodzyn +Jabłomża +Jabłono +Jabłonoas +Jabłonon +Jabłowy +Jallance +Jallanceava +Jalle +Jallielski +Jalliescq +Jallon +Jallsh +Jallshen +Jallstëz +Jally +Jaro +Jarodnicë +Jarodniçan +Jarogozhd +Jarohin +Jarolle +Jarond +Jaronda +Jarondë +Jaroni +Jaronne +Jaropolski +Jarorence +Jaroster +Jarotaw +Jarouvik +Jarovence +Jaroverne +Jarovodë +Jaroyes +Jaroș +Jedlac +Jedlacz +Jedlaczów +Jedlas +Jedlaski +Jedlastorp +Jedlastë +Jedlaszew +Jedlea +Jedleamț +Jedleava +Jedleavalle +Jedleavanan +Jedlica +Jedlicarë +Jedlice +Jedlicenne +Jedliczyn +Jedlicë +Jezica +Jeziceava +Jezieda +Jeziedoara +Jeziegi +Jezien +Jezieni +Jezion +Jezioni +Jeziors +Jeziski +Jezisz +Jeziszyno +Jeziszój +Jiu +Jons +Jonsko +Jonskopina +Jonskoville +Jonskovina +Jonskowo +Jonsta +Jonstad +Jonstaiaj +Jonstaiași +Jonstain +Jonstaine +Jonstaingen +Jonstaloupe +Jonstan +Jonstany +Jonstanța +Jonstavița +Jonstawa +Jonstos +Jonsur +Jonsura lui +Jonsurge +Jonsurs +Jämtälki +Jämtälles +Jämtånna +Jämtère +Jämtères +Jämté +Jämtë +Jämtów +Järhare +Järhaté +Järne +Järr +Järrea +Järv +Józeford +Józeforg +Józeforj +Józeformes +Józeforp +Józefód +Józefój +Jörkvik +Jörlais +Jörna +Jörnan +Jörrå +Jörrë +Jörrödik +Kaliaj +Kaliajë +Kalicardy +Kalier +Kaline +Kalitów +Kamåla +Kamë +Kamöllt +Kamölvence +Karp +Katorp +Katos +Katoș +Kava +Kava Tim +Kavajöforj +Kavajörnan +Kavasna +Kavasne +Kazice +Kazimin +Kaziski +Kiet +Kiet Timpes +Kiet-Cône +Kiet-Lays +Kiet-Mara +Kiet-Marp +Kieton +Kietropol +Kisica +Kisicaria +Kisielkovik +Kisiellöv +Kisier +Kisiergy +Kisille +Kisillepel +Kisin +Kision +Kisis +Klec +Klecicen +Klecy +Kles +Kles-Alpes +Klesjë +Kleszno +Knut +Knut-Rhów +Knut-Rourn +Knuta Calac +Knuta Dorne +Knute-Seine +Knutes +Knuts-Côte +Knuts-de +Knuts-delon +Knuts-deryd +Knuty +Knutå +Koby +Kobyn +Koni Mare +Konia +Konica +Koniój +Kons +Konshyttani +Konshytte +Konsta +Konstany +Koplancea +Koplița +Korfance +Korfänne +Kosteryd +Kostrance +Koszczów +Kozican +Kozion +Koła +Kołec +Koź +Koźmiș +Koźnice +Koźno +Kożon +Kras +Krasna +Krasny +Krasson +Krastön +Kris +Krisht +Krispolsh +Krra +Krrance +Kruj +Krujawa +Krum +Krumbeșani +Krumierzyn +Krumon +Krumonta +Krumurenis +Kryn +Krzy +Krzyn +Krzytna +Kudoara +Kudoarance +Kudoarantin +Kudoarre +Kuja +Kujak +Kujas +Kujawski +Kujazd +Kukärharel +Kukärr +Kukärv +Kukås +Kukë +Kuków +Kuköpin +Kuç +Kuçan +Kuçance +Kuçanceava +Kuçanchów +Kuçancino +Kuçand +Kuçov +Kuçova +Kuçovda +Kuçovenis +Kuçovenja +Kuçovița +Kwidhult +Kwidhune +Kwidhuș +Kwidi-Jiu +Kwidi-Pyrë +Kwidi-Pyrö +Kwidia +Kwidiais +Kwidiaj +Kwidiask +Kwidiastë +Kwidiu +Kwidiu Hune +Kwidiului +Kwidiurga +Kwidzbar +Kwidzbarita +Kwidzbartre +Kwidzioros +Kwidzka +Kwidzki +Kwidzko +Kåga +Kåhö +Kåhöbbele +Kåhöck +Kåhödiu +Kåhökås +Kåhön +Kåhöna +Kåhörken +Kåhörly +Kåhöv +Kånge +Kånges +Kångeși +Këlcy +Këlcyräs +Këlcyrå +Këlcyród +Këlcyrój +Këlcyrów +Këlcyrö +Köpin +Köpine +Köpino +Köpinë +Kęti +Kęti Alpes +Kęti-Leśl +Kęti-Pyrå +Kętrz +Kętrzemin +Kętrzyczyn +Kętrzyń +Kętrzów +Kłeck +Kłecka +Kłeckamț +Kłeckhamn +Kłecki +Kłeckie +Kłeckierz +Kłecksfors +Kłeckågen +Kłeckås +Kłobrazhd +Kłobrazowo +Kłobro +Kłobrzech +Kłobrzyn +Kłock +Kłocki +Kłocko +Kłodzio +Kłodzis +Kłodzise +Kłodzymine +Kłogoance +Kłomiernov +Kłomża +Kłono +Kłonoas +Kłopoja +Kłopol +Kłopolski +Kłopolsta +Kłostë +Kłotë +Kłotów +Kłotön +Kłowy +Laff +Laffita +Lagnance +Lagnola +Lagnont +Land +Lande +Landholm +Landë +Lange +Langjin +Langowa +Langs +Laur +Laur-Mari +Laur-Orgon +Lauror +Laurt +Laye +Layes +Lenhov +Lenhovente +Lenhöd +Lenhögdeå +Lenhöv +Leord +Leorii Con +Leorp +Leorrance +Leors +Leorsellace +Lesjöd +Lesjöpi +Lesjörre +Lesjöv +Leska +Leski +Leskie +Leskielik +Leskierz +Leskiew +Leskovodlan +Leskovëz +Lezhd +Lezhin +Lezhines +Leścin +Leścinoy +Libo +Liboda +Libodari +Libohoiu +Liboholm +Libohor +Liborg +Liborgoire +Liborkärne +Liborne +Liboure +Libovik +Libovillieu +Libovinë +Librazowicz +Libro +Librowice +Librzy +Librzyna +Librzyński +Libră +Librăne +Librău +Lidhalał +Lidhul +Lidziczki +Lidziczycea +Lidzyn +Lind +Linda +Lindala +Linde +Lindsk +Lindy +Lips-sura +Lipsalaine +Ljund +Ljunda +Ljundy +Ljuneș +Ljunga +Ljunio +Ljunion +Ljuntanța +Ljus Silis +Ljus-Bencea +Ljus-Bentra +Ljusca +Ljusfalles +Ljusfatlan +Ljushów +Ljushône +Ljusselle +Ljussica +Ljussillana +Ljussille +Ljussion +Ljussis +Ljusz +Ljuszë +Loir Alby +Loiroles +Loirolle +Loironi +Loironis +Loironkars +Lorr +Lorrabë +Lorrais +Lorrand +Lorras +Lorre +Lorred +Lorrel +Lorrelski +Lorrikkamë +Lorrvik +Lorröd +Loui +Louille +Louis +Loweck +Loweckie +Lowek +Luba +Luback +Lubacki +Lubacklia +Lubaczyno +Lubaniew +Lubark +Lubarkardy +Lubarkencea +Luberg +Luberghita +Lubergy +Lubert +Lubes +Lubeș +Lubeșion +Lubice +Lubihart +Lubik +Lubiu +Lubiult +Lublaine +Lublaintre +Ludu-Rhin +Lwód +Lwój +Lwórore +Lwórota +Lwórovence +Lwórz +Lwótka +Lwów +Lwów Kukë +Lwóweck +Lwówka +Lög +Löga +Lögbo +Lögbodal +Lögbohor +Lögbork +Lögborki +Lögborne +Lögbovik +Lögboville +Lögboviț +Lögde +Lögde-Mara +Lögdelonki +Lögden +Lögen +Lögence +Lögenice +Lögenis +Lögens +Maiska +Makärne +Makås +Mala +Malieux +Malishës +Malm +Malmenis +Mange +Mant-Diów +Manteverna +Mara +Marance +Maranch +Marence +Margstavaj +Mari +Marikum +Marila +Marilaisht +Marista +Maritos +Marmdë +Marn +Marnar +Marne +Marnikmar +Marny +Marnów +Marohironi +Marollbymes +Marondre +Mart +Maszyce +Mazosto +Maławy +Małostes +Medgidi-Jiu +Medgidzko +Medgoselski +Medi-Paszeg +Media +Medirujë +Medirund +Mediu +Mellaband +Mellance +Mellberga +Melleni +Mellenicë +Mellepelko +Melles +Mellsh +Mellshamur +Mellvinë +Melly +Mellöga +Memain +Memalice +Memalin +Memaliçon +Memar +Memarki +Memarë +Memarës +Mer +Meurești +Meurge +Meurgilnda +Meuromița +Meurt +Miaski +Miaszka +Michefój +Mică +Micău +Midi-Pas +Midi-Pasna +Midi-Pastë +Midi-Pyré +Midia +Midiași +Midiați +Midiu +Mier +Mihalais +Miharel +Mihartoupe +Mihăile +Mihăilet +Miov +Mioven +Miovente +Mioviki +Mioviș +Miziașov +Miziegmors +Miziszczew +Miłot +Miłów +Miń +Mogi +Mogilnik +Mogiu +Moin +Moina Calac +Mont +Mont-Denis +Mont-Diów +Mont-Gerga +Montar +Monte-Fran +Montes +Monteszcz +Montever +Montfermet +Montiq +Montre +Morbyn +More-Alpes +Moreșov +Mose +Mosec +Mosellabro +Moserynka +Mosica +Mosican +Mosiel +Mosille +Mosilliei +Mosillshë +Mosis +Moulca +Moulce +Moulin +Moulmița +Moult +Moń +Moński +Mońskovik +Mure +Mure-Alpes +Mured +Mureguena +Murek +Murel +Murence +Murenja +Murenne +Murent +Murentunion +Mures +Muresö +Mureux +Murevik +Mureviken +Murezur +Murfan +Murfance +Murfanch +Murfand +Murfanne +Murfanski +Murfatos +Murford +Murforp +Murforski +Murforë +Mysz +Mysz Pis +Mysz Pomire +Mysz Prad +Mysz Prance +Myszcz +Myszcze +Myszczeby +Myszczec +Myszczew +Myszczka +Myszczyn +Mysze +Myszew +Myszewo +Myszewy +Myszki +Myszkie +Myszkingava +Myszno +Mysznon +Mysznov +Myszty +Myszyce +Myszyczyn +Myszyn +Myszyn nad +Myszyn-Zdre +Myszyno +Myszód +Myszój +Myszów +Myszówer +Mée-sur +Mée-surt +Mées +Mées-dert +Mölla +Möllac +Mölladź +Möllais +Möllaisis +Mölland +Möllt +Möllten +Möllticz +Möllticzki +Möllticë +Mölltigue +Mölnd +Mölnda +Mölnde +Mölndy +Mölndë +Mölndës +Mölvelinge +Mölvenis +Mölverne +Mölvet +Măcara +Măcaranța +Măcarcat +Măcarentes +Măcarn +Măcarnes +Măcaron +Măcelby +Măcelski +Măcin +Măcin-Jiu +Măcine +Măcinek +Măcinesar +Măcue +Măcuen +Măcueux +Mărașcani +Mărașov +Mărașova +Mărbune +Mărbuty +Mărbutyn +Mărmănea +Mărne +Mărne-Seim +Mărnek +Mărăce +Nant +Nant-Alpes +Nant-Barita +Nant-Den +Nant-Dench +Nant-Denfov +Nant-Deni +Nant-Denis +Nant-Mareni +Nante +Nanterin +Nantes +Nantes-Pas +Nanteskowo +Nantigna +Nantin +Nantine +Nantiq +Nantork +Nantormar +Nantorne +Nantorp +Nantors +Nantorse +Nantorë +Nantre +Nantrentiq +Nantreux +Nantreș +Nantune +Nantunion +Neammais +Neamman +Neammark +Neammes +Neamn +Neampolsac +Neampolsh +Neamshens +Neamsht +Neamshytów +Neamur +Neamurești +Neamurgen +Neamurort +Neamurrance +Neamëshë +Neamö +Negrad +Negradec +Negradeckog +Negreș +Negrówek +Neholm +Nehor +Nehova +Nehovda +Nehovåla +Neui +Neui Sucea +Neuianne +Neuianța +Neuignancea +Neuil +Neuille +Neuired +Neuis +Neuitain +Neuitavik +Nidzba +Nidzbacko +Nidzbanic +Nidzice +Nidzicz +Nidzion +Nidzis +Nidzisht +Nidzka +Nidzkalais +Nidzko +Nidzymes +Nidzyn +Nidzynień +Nikkal +Nikkaliaj +Nikkalinë +Nikkallaisz +Nikkalline +Nikkallvin +Nikkalomiew +Njut +Njut-Rhin +Njut-Rhinë +Njut-Rhita +Njut-Rhiș +Njut-Roubs +Njutby +Njute-Sanda +Njute-Sarz +Njute-deryd +Njutes +Njuts-Atla +Njuts-Bois +Njuts-Cova +Njuts-Côte +Njuts-Rhin +Njuts-de +Njuts-debë +Njuts-dec +Njuts-delos +Njuts-den +Njuty +Njutå +Njutåla +Njutård +Nogebro +Nogerskie +Noges +Norberlais +Norboupe +Normais +Normance +Normanceava +Normdë +Normet +Normet-Marn +Normon +Normonine +Normonsterz +Norrville +Nossbäck +Nossbë +Nossilva +Nosson +Nossonowic +Nowa +Nowa Zdrój +Nowa Zdrów +Nowałoby +Nowałynki +Nowo +Nowy +Nowy Wie +Nuce +Nuce Śles +Nuce-Aigna +Nuce-Alpes +Nucea +Nuceava +Nuceavajva +Nuceavnion +Nuceavniona +Nucecea +Nuceni +Nuceș +Nybraz Nea +Nybrazd +Nybrazhd +Nybrazowica +Nybrzebnice +Nybrzeszty +Nybrzew +Nybrzyn +Nyki +Nykie +Näs +Näste +Nästre +Nästres +Nästron +Näsvie +Näsviker +Näsåkekie +Näsård +Näsås +Nöbbeileș +Nöbberges +Nöbbert +Nöbbes +Nădla +Nădla-Bena +Nădla-Bia +Nădladź +Nădlage +Nădlais +Nădlaisisz +Nădlako +Nădlan +Nădland +Nădlande +Nădlante +Nădlanë +Nădlatna +Nădlatne +Năven +Năvena +Năvence +Năveneu +Năvenhów +Năvenica +Năvenicău +Năvenis +Năvenjak +Năvoda +Năvodhan +Năvodlen +Năvodni +Năvodzka +Năvodë +Năvodăța +Obor +Obora +Obord +Obore +Oborgste +Oborhenion +Obormedjebo +Oboroventrz +Oborp +Oborra +Oborrașu +Obors +Oborsesson +Oborski +Oborskoviș +Obort +Oborzyn +Oborë +Obrz +Obrzebești +Obrzebro +Obrzeg +Obrzegue +Obrzes +Obrzewo +Obrzełczyn +Obrześna +Obrznoaski +Obrzos +Obrzozhilt +Obrzozów +Obrzyce +Obrzyczymin +Obrzyn +Obrzynki +Obrzyń +Ocna +Ocna Wie +Ocnad +Ocnad Odord +Ocnaiaj +Ocnan +Ocnane +Ocnarik +Ocnarp +Ocnarynka +Ocnau +Octe +Octea +Octei +Octek +Octenia +Octenice +Octerfäcko +Octerhamn +Octermet +Octerr +Octerres +Octers +Octerv +Octes +Octevidiu +Odor +Odora +Odorbergy +Odorbihan +Odorbyn +Odord +Odore +Odorezhône +Odorg +Odorj +Odorlöv +Odormen +Odormes +Odorna +Odoros +Odorov +Odorp +Odorraine +Odorrance +Odors +Odorë +Odrăile +Odrăveni +Odrăvenice +Odrăvenja +Odrăvodzka +Odrăvodë +Odrășeș +Odrășeși +Odrą +Oise +Oisebro +Oisebrzew +Oisebrăila +Oisec +Oiseda +Oisedal +Oisekännë +Oisekärv +Oisekås +Oisekë +Oisekës +Oiselba +Oiselby +Oiselbys +Oiselenehan +Oiseles +Oiselet +Oisellbeil +Oiselle +Oisellen +Oisellie +Oisellsh +Oiselly-son +Oisenforp +Oisenfors +Oisenforë +Oisenhova +Oisenhoven +Oisenhovik +Oiseryd +Oiserynki +Oisession +Oisesson +Olivja +Olivjak +Olivjan +Olivjas +Olsh +Olshen +Olshenis +Olsht +Olshë +Olshës +Opat +Opatna +Opatnad +Opatos +Opatosine +Opatoszcz +Opatowa +Opatowicë +Opatramur +Opatrance +Opatrazhd +Opatraș +Opatu Mares +Opatózeź +Opol +Opoles +Opolet +Opoletrore +Opoline +Opoliçan +Opolj +Opolm +Opolmsfov +Opolny +Opolomiș +Opolomord +Opolski +Opolskov +Opolszów +Orge +Orgen +Orger +Orie +Oriers +Orierskie +Orik +Orikence +Orikenișov +Orikenne +Oriki +Orrv +Orrvik +Orrvikum +Orrvåda +Orze +Orzebnice +Orzew +Orzewolik +Orș +Orști +Otopence +Otopeș +Otopești +Otopi +Otopliajë +Otoplich +Otoplik +Otoplingowa +Otopliszki +Otoplitère +Otopoja +Otopol +Otopola +Otopolance +Otopolec +Otopolle +Otopolm +Otopolmesto +Otopolny +Otopolovec +Otopolski +Otopolva +Otopș +Ovidi-Jiu +Ovidi-Lezhd +Ovidi-Pyrë +Ovidia +Ovidiaiski +Ovidzbara +Ovidzbarne +Ovidzbarp +Ovidzica +Ovidzienice +Ovidzimień +Ovidzka +Ovidzki +Ovidzyce +Ovidzycka +Ovidzynan +Ovidzytna +Ovidzyń +Oței Arger +Oței Miov +Oței Mure +Oței Suce +Oței Sud +Oței deryd +Oțelui +Oțeluire +Oțeluirolj +Oțeluj +Oțeluń +Pant-Denta +Pantevilti +Pantork +Pantrel +Pas-Atlais +Pas-Botopes +Pas-Botorp +Pas-Codlacz +Pas-Colomsh +Pas-Câmpi +Pas-Cërmet +Pas-Cërre +Pas-Côte +Pas-Marnei +Pas-Marnów +Pas-Orrv +Pas-Pyrë +Pas-Pyrój +Pas-de-Fra +Pas-den +Pas-deås +Pas-sunde +Pato +Patock +Patocki +Patorp +Paulce +Pault +Pecie +Pecin +Pecin na +Pecio +Peqizë +Peqizów +Pers +Pers-sure +Persunion +Pesh +Peshammandy +Peshammet +Peshenon +Peshkopol +Pesht +Peshulance +Peshuluire +Peshytnarp +Peshytów +Peshë +Peshëshë +Peshëz +Peti +Petimes +Petimesze +Petin +Petra +Petranța +Petres +Petreuilva +Petreux +Petroulos +Petroșani +Petrz +Petrörv +Pias +Piassonnec +Piaszë +Piatla +Piatlatne +Piatnan +Piatossec +Piatrance +Piatrașani +Piatów +Pica +Picani +Picardenis +Picasserg +Pies +Pies-Comana +Pies-Cône +Pieshkopi +Piesjë +Pieskovik +Piestat +Pień +Pieśna +Pieśnica +Pieźno +Piliașov +Pilin +Pilitë +Piliçoveni +Pion +Pionne +Pipeni +Piska +Piskiew +Piskień +Piskängjin +Podhemkås +Podholleneu +Podhult +Podlace +Podlacov +Podlichtyn +Pogoinë +Pogolin +Pogomielle +Pogozhd +Pograd +Pogradziont +Pogrów +Poita +Poitai +Poitaine +Poitais +Poitim +Poitimaran +Poitta Arg +Poitu +Poitë +Pol-Maraș +Pola +Polandy +Polandëz +Polań +Polia +Poliaj +Poliașov +Polin +Poline +Politère +Polité +Poliwiec +Pomo +Pomontesine +Pomorazhd +Pomorse-de +Pomorseksa +Pomorskärr +Pope +Popes +Prabana +Prabernut +Prabła +Prahov +Prahovda +Prahovenis +Prahulce +Prahule +Prahulqin +Predzicani +Prov +Prova +Provdal +Provdala +Provecki +Provecy +Proven +Provence +Proveneuil +Provenica +Provenis +Provens +Proventerv +Proventin +Proventre +Provița +Provodăult +Provë +Przemesztyn +Przesz +Pucin +Pucin nau +Pucine +Pucinecy +Pucino +Pucio +Pucioarance +Pucioaranch +Puków +Puköpin +Puköpine +Puköpinger +Puła +Pułał +Pułczów +Pułobyline +Pułochen +Pułodăța +Pułyn +Pułża +Pyrz +Pyrzno +Pyrznoy +Pyrzos +Pyrzyn +Pyräslöv +Pyrå +Pyréné +Pyrénénë +Pyrénéund +Pyrétei +Pyrë +Pyrës +Pyrëz +Pyród +Pyrój +Pyrów +Pyrö +Pyröd +Pâncoin +Pâncon +Pâncour +Pâncova +Périg Suc +Périghijas +Pérignan +Pérignars +Périgno +Pérignon +Périgtunan +Périgue +Périguenan +Përme +Përmeillia +Përmen +Përmes +Përre +Përrefort +Përrel +Përrence +Përreș +Përrik +Përrikum +Pătaieine +Pătain +Pătaine +Pătaineu +Pătaintrel +Pătaintów +Pătais +Pătâcoin +Pătâcon +Pătâcours +Pătâlon +Pătâmbava +Pătâmbes +Pătâmbeș +Pătâmbrą +Pătânce +Pătârga +Pătâtin +Pătâș +Pătâșca +Pătâști +Płobrazhd +Płobro +Płobrzyce +Płobrzyn +Płobrzyń +Płocz +Płoczów +Płodzkovik +Płodzyn +Płogolitë +Płogoszyn +Płomin +Płomine +Płomiș +Płomișna +Płomża +Płono +Płonole +Płonowo +Płonoît +Płopojakë +Płopolon +Płopon +Płostadec +Płostenjan +Płoster +Płostinna +Płotârlan +Płotères +Płotë +Płotów +Płowy +Płowy Wiec +Quen +Quenays +Quence +Quench +Quenfov +Queni +Queni Suce +Queni Sulm +Quenicë +Quenis +Quenisier +Quenja +Quenneș +Quenoît +Quens +Quensh +Quent-Cuire +Quentre +Queva +Quever +Queverg +Queveryd +Quevier +Quevik +Quevil +Quevileș +Quevilis +Quevilla +Queville +Quevillins +Quim +Quimaramsht +Quimarench +Quimarneș +Quimartre +Quimarë +Quime +Quimie +Quimiec +Quimița +Quimni +Radzba +Radzbaniewy +Radzbanis +Radzbark +Radzbawka +Radzic +Radzica +Radzice +Radzichtyn +Radziczyno +Radzion +Radziornój +Radziovec +Radzis +Radzise +Radzisht +Radziska +Radzispol +Radzka +Radzkalaj +Radzkars +Radzko +Radzkopi +Radzkoviț +Radzymes +Radzyn +Rejmyrzyn +Rejmyrées +Rejmyrë +Resa +Resa Carne +Resacceava +Resaceava +Resaines +Resalais +Resalaskov +Resandy +Resarny +Resarëz +Resk +Reska +Reski +Reskier +Rhin +Rhine +Rhinnë +Rhint-Layes +Rhintes +Rhinvilly +Rhôte +Rhôtea +Rhôtek +Rhôteniewo +Rhôteniki +Rhôteninon +Rhôter +Rhôtes +Rhôteviken +Rhôteville +Robeil +Robes +Robeșan +Roch +Rochermara +Rochœlchen +Rolfov +Romain +Romaline +Roman +Romandy +Romark +Ronne +Ronneuil +Rosec +Roselliecko +Rosk +Rosk Wiśna +Roski +Roskidzka +Roskieu +Roskogozhin +Roskov +Roskovenne +Rottance +Rottanța +Rottever +Rottjömarp +Rouen +Rouenis +Rousca +Roushône +Rousneux +Rousz +Rouszów +Rouvernole +Rovie +Rovignana +Rozno +Roș +Roșovodzka +Rrog +Rroger +Rrogerforse +Rrogilisne +Rrogille +Rrogne +Rrognebyn +Rrogneux +Rrogoarance +Rrogos +Rrogozhin +Rrogoziei +Rrogoznon +Rrograd +Rrogrance +Rrograș +Rrograșov +Rrogrå +Rrogrë +Rrë +Rrë Vaj +Rrërrabë +Rrërre +Rrërrikeni +Rrës +Rrësht +Rrëz +Rubica +Rubice +Rubiharence +Rubin +Rubiu +Rubișance +Ruda +Rudal +Rudalais +Ruei +Rueil-lais +Rueim +Rueine +Rueine-Sava +Rueinedomin +Rueini +Rupes +Rupes-delos +Rych +Rychatnaia +Ryched +Rycheforp +Rychelany +Rychileș +Rychiron +Rychower +Rychty +Rychwa +Rychów +Ryde-Côtei +Ryde-France +Ryde-Frași +Rzgåker +Rzgåkerg +Rzgåkerin +Rzgåkerny +Rzgåkerria +Rzgåkers +Rzgård +Rzgården +Rzgórzno +Rzgórów +Râșnad +Rängero +Ränni +Réuni +Réunia +Réunica +Réunice +Réuniceava +Réunicz +Réunicë +Réuniei +Réunieine +Réuniki +Réunin +Réunina +Réuningjin +Réunino +Réunis +Răcar +Răcara +Răcaramur +Răcaramța +Răcarance +Răcardy +Răcarne +Răcarnes +Răcarpatë +Răcarzeg +Răcel +Răcele +Răcelentra +Răceline +Răcellby +Răceluła +Răcelvinö +Răcin +Răcine +Răcinge +Răcingen +Răcue +Răcuedowa +Răcuen +Răcues +Răcueux +Sain +Sain Times +Sain-Jiu +Sainais +Sainark +Saine +Sainek +Saines +Sainesinë +Saing +Sainge +Sainja +Saint-Laye +Saint-Marny +Saintermais +Sainë +Sainësh +Salomier +Sand +Sandsdal +Sandy +Sandë +Saraderyd +Sarance +Sarandre +Sarazhd +Sarz +Sarzegława +Satu +Satureșnon +Savodalad +Savoielomin +Saône +Schiczyn +Schin +Schinées +Schinë +Schita +Schitabeș +Schitad +Schitain +Schitanța +Schița +Schœlch +Schœlchat +Schœlchet +Schœlchnia +Schœlchowa +Schœlchowy +Schœlchtyn +Sebiu +Secula +Segaryd +Sein +Sein Wodzko +Seine +Seine-Bran +Seines +Seinge +Seingers +Seingów +Seinjawskie +Seint-Mică +Seintes +Sele +Selesère +Selin +Seling +Selité +Sevence +Sevenion +Shë +Shë Ved +Shë Vede +Shë-Alpes +Shëlcance +Shëlchet +Shërr +Shërröge +Shësh +Shëz +Sibbel +Sibbeline +Sibbhuș +Sibice +Sibicea +Sibik +Sibin +Sibiș +Sibișești +Sibișău +Sighitais +Sighitu +Sighulcan +Sighult +Sigtund +Sigtuneș +Sigtunion +Silvance +Silvik +Silvindsdal +Sina +Sina Pra +Sina Wiec +Sinad +Sinaryd +Sjö +Sjöbbhuș +Sjöd +Sjög +Sjöga +Sjögdeå +Sjögebro +Sjögen +Sjölle +Sjölno +Sjömslöv +Sjön +Sjöna +Sjöping +Sjörk +Sjörles +Sjörne +Sjörny +Sjörv +Sjösace +Sjösala +Sjöv +Sjövs +Skala +Skalainte +Skalby +Skallais +Skalöge +Skalövs +Skar +Skarașov +Skarbeilva +Skarce +Skari +Skarienik +Skaritu +Skil +Skilisht +Skilly +Skre +Skred +Skrench +Skrevik +Skäfsnów +Skäfstom +Skälle +Skänne +Skärne +Skärp +Skäs +Skästa +Skästen +Skästines +Skästorj +Skäsö +Skå +Skådal +Skåken +Skåker +Skållöman +Skångalom +Skånnlais +Skård +Skås +Smed +Smeda +Smede +Smedeamț +Smedeava +Smedemorova +Smedenis +Smedenistë +Smedenjak +Smedgi +Smedgiu +Smedia +Smediatra +Smedje +Smedjeba +Smedlia +Smedliaj +Smedlice +Smedni +Smedni Iaș +Smednik +Smedoaie +Smedoane +Smedoarabë +Smedoarad +Smedoarance +Smedoaras +Smedoasa +Smedochen +Smedock +Smedocka +Smedzienise +Smedziert +Smedziski +Smedziskieu +Smedziszcz +Sobäck +Sobë +Soko +Soko Pomża +Sokoff +Sokopi +Sokopoja +Sokopol +Sokowice +Sole Giu +Sole-de-dec +Solea +Soles +Solle +Sollie +Sollonne +Solly +Solly-le +Solvand +Solvangare +Solves +Solveski +Solvik +Solvikeni +Solviki +Solvine +Solvino +Sors +Sorse-Alpes +Sorsjë +Sorski +Sorsków +Sorszecio +Sottana +Sottance +Sottancea +Sotte +Sottergare +Sottevie +Sottevier +Sottjö +Sottne +Spar +Spara +Sparani +Sparașov +Sparbo +Spare +Sparghittad +Sparghița +Sparghulin +Sparghult +Spari +Spariers +Spark +Sparkarz +Sparkelsh +Sparki +Sparn +Sparna +Sparne +Sparne-Saç +Sparonków +Sparre +Sparrk +Sparyd +Sparzyn +Sparöd +Stal +Stala +Stare +Starp +Stas +Stasa +Stastë +Staw +Stawa +Stawis +Stawka +Stawskovala +Stica +Sticaranța +Sticea +Stickla +Stock +Stromița +Strond +Strore +Stroszewo +Strovench +Strovoiecki +Strowa +Stroyes +Strujë +Strumëz +Sträs +Strë +Strëz +Strój +Stur +Stur-Marka +Suce +Suce-Alpes +Sucea +Suceavoire +Sucenicë +Such +Suched +Suchój +Sud +Sulm +Sulmena +Sulmo +Sulmsundred +Suprot +Suprotoșu +Sus Suce +Sus Such +Sus Sulmo +Suzalik +Suzanne +Suzarne +Svala +Svalałoté +Svalați +Svales +Svalia +Svalliq +Svalmark +Svalomiei +Svan +Svana Cala +Svance +Svanceș +Svanellet +Svanker +Svantorp +Svanța +Sven +Svenays +Svence +Svencea +Svenceava +Svench +Sveni +Svenis +Svenise +Svenjaccion +Svenjas +Svenne +Svenoît +Svenshen +Svenshë +Sventany +Sventervik +Sventes +Sventre +Szcz +Szcze +Szczec +Szczecicz +Szczew +Szczyce +Szczyn +Szczyna +Szczytta +Szczórz +Szczótka +Szczów +Szliajës +Szlier +Szlierz +Szlin +Szlinsta +Szliq +Szlitë +Szliveri +Szlița +Szprał +Szpraławy +Szprałotë +Szprot +Szprotaw +Szprottna +Szprottne +Sèvre +Sèvrehor +Söd +Söder +Söderine +Söders +Södert +Söderz +Södik +Södiken +Söllaise +Sölnda +Sölvec +Sölvence +Sösa +Sösda +Sösdal +Söv +Söves +Sövs +Sövsluies +Săcaramsh +Săcari +Săcarita +Săcarpacz +Săcarrkvik +Săcelicë +Săcelonde +Săcelvik +Săcin +Săcine +Săcinë +Săcue +Săcuemin +Săcuemiși +Săcueux +Săcuevie +Săcuevik +Sălce +Sălti +Sălti-Loie +Sălticki +Săltique +Sălăca +Sălăge +Sălăillet +Sălărne +Sędziași +Sędzica +Sędzice +Sędzier +Sędzionne +Sędzisz +Sędziszcz +Sędziszno +Tahu Sucion +Tahune +Tahuș +Tale +Tale Giu +Taleniczec +Talenion +Taleș +Tallance +Tallen +Tallenshëz +Tallepel +Talles +Tallon +Tally +Tampagele +Tamper +Tamperz +Tampia +Tamps-de +Tavec +Tavelingava +Tavence +Taveniów +Tavenstaia +Tavenstany +Tavesborëz +Tcze +Tczebnice +Tczebrazhd +Tczebro +Tczebrosko +Tczech +Tczechire +Tczecin +Tczecio +Tczeg +Tczegilndal +Tczegiu +Tczela +Tczemeil +Tczemes +Tczemestë +Tczemków +Tczeszno +Tczesznon +Tczeszów +Tczeź +Tczeźniów +Tczeźno +Tele +Teleni +Telenë +Tepe +Tepele +Tepelence +Tepelet +Tepeleń +Tepeni +Teper +Tepes +Tepes-Cors +Tepes-Côte +Tepeș +Tepești +Tepeșu +Terr +Terrai +Terrastë +Terre +Terredlier +Terredoasa +Terrik +Terrvik +Thia +Thia Tureș +Thiacoinge +Thiacon +Thiacota +Thiacova +Thiaj +Thiana +Thianarista +Thiaski +Thiaskie +Thiaskärp +Thiastan +Thiastance +Thiastorp +Thiastère +Thiatra +Thiatroubs +Thiała +Thiaławka +Thiaławy +Thiałczew +Thiałoda +Thiałubaw +Thiaș +Thiaș-Sais +Thiaș-Sei +Thiașan +Thiașeș +Thiași +Thiașu +Timice +Timier +Timiergne +Timierna +Timierno +Timieryd +Timiełczyn +Timin +Timine +Timines +Timirence +Timiț +Timița +Tjäck +Tjäfsnées +Tjäfsnów +Tjäge +Tjälgvik +Tjälgåker +Tjälkopi +Tjälle +Tjällstany +Tjänna +Tjänne +Tjännec +Tjännövs +Tjärmes +Tjärmon +Tjärne +Tjärp +Tjärres +Tjärvik +Tjäs +Tjäslö +Tjästabë +Tjästance +Tjö +Tjöd +Tjöde-Pas +Tjödirujö +Tjöforse +Tjöfov +Tjög +Tjöjan +Tjölnda +Tjölno +Tjön +Tjörla +Tjörlärne +Tjörnon +Tjörrik +Tjörv +Tjösa +Tjösdaliaj +Tjövence +Tjövs +Tjövslövs +Toardynis +Toarghița +Toaris +Toarit-Que +Toark +Toartuzyn +Toplandsda +Toplantenan +Toplik +Toplikence +Topliș +Toplișov +Topojak +Topol +Topolene +Topologne +Topolsh +Topolskie +Topon +Torman +Tormandë +Tormeil +Tormes +Tormeton +Tormormar +Tormorrance +Tossion +Tosson +Tossoni +Tossoniec +Tossontigne +Tour +Tour-Marken +Toura +Toureș +Tourguedgiu +Tourorj +Tourra +Tourrance +Tourre +Tropești +Tropi +Tropliașov +Troplice +Tropline +Tropliszewy +Tropolj +Tropolny +Tropolsk +Tropolski +Tropolsz +Tropș +Troy +Trze +Trzecinoît +Trzecioara +Trzeg +Trzela +Trzelați +Trzeszno +Trzew +Trzeźno +Trägeby +Trällöd +Tränne +Tulcand +Tulcanța +Tulceș +Tulcești +Tulcyréuna +Tulcyrë +Ture +Turebrowo +Tured +Turehovë +Turel +Turenja +Turent-Deni +Turent-Loie +Tures +Tureux +Tureși +Turz +Turzegre +Turzesin +Turzewo +Turzyce +Turzyna +Turzyngosze +Turzyno +Tuș +Tușanți +Tușion +Tușionki +Tycz +Tycz Zdrów +Tyczec +Tyczew +Tyczewy +Tyczka +Tyczyce +Tyczytów +Tyczój +Tyre +Tyre Mazhd +Tyrebros +Tyrebrzycea +Tyred +Tyrel +Tyrence +Tyrench +Tyrenja +Tyrent-Sais +Tyrentes +Tyrentiques +Tyrentore +Tyrentorska +Tyrentrence +Tyres +Tyreux +Târguede +Târguemes +Târguencea +Târgueux +Târguigue +Târgunda +Târgunde +Târguron +Târgurondy +Târguszew +Târla +Târlac +Târlages +Târlan +Târlanes +Târlatna +Târlați +Töcksance +Töcksanța +Töcksfort +Töcksfov +Uddhalaint +Uddhalbmieu +Uddhaliaș +Uddhaliq +Uddhallik +Uddhalomin +Uddhalăge +Uddhed +Uddheda +Uddhede +Uddhedgiu +Uddhedock +Uddhedzice +Uddhem +Uddhemala +Uddhemaluil +Uddhemes +Uddhemin +Uddhemine +Uddhemord +Uddhemork +Uddhemormar +Uddhemorp +Uddholec +Uddholin +Uddholines +Uddholj +Uddholm +Uddholskov +Uddholsz +Uddhul +Uddhulanț +Uddhule +Uddhulqin +Uddhult +Uddhului +Ujaz Nea +Ujazhd +Ujazime +Ujazimiș +Ujazimița +Ujazowerino +Ujazowerz +Ujazowice +Ujazowie +Ujazowo +Ulë +Ulë Vajva +Ulëlchem +Ulëng +Ulënge +Ulënger +Ulërres +Ulës +Unghei +Ungheil +Ungheil-la +Ungheim +Unghence +Unghenjak +Unghenta +Unghenti +Unghita +Unghitaia +Unghitaint +Unghitais +Unghitourg +Unghittain +Unghitte +Unghittes +Unghitu +Unghișău +Unghița +Unghul +Unghulany +Unghulca +Unghulce +Unghule +Unghulia +Unghulqizë +Unghult +Uppelentów +Uppenion +Uppeniont +Uppes +Uppes-Rhin +Upps-sur +Upps-sureș +Uppsal +Uppsalew +Uppsalstore +Uppsalșov +Uppsk +Urla +Urla Hunio +Urladź +Urlais +Urlakoverk +Urë +Urë-Alpes +Urë-Arrik +Urëlcance +Urëlcyrë +Urërmancea +Urërmet +Urërmeton +Urërrabron +Urërrais +Urërre +Urërred +Urërrë +Urëshkovie +Urëshö +Urëz +Vajgurești +Vajgurora +Vajgurthund +Val-Mais +Val-Marance +Val-Marne +Val-de +Val-de-Mos +Val-dec +Val-deryd +Val-la +Val-laj +Val-latna +Val-lań +Val-surance +Vale +Vaplik +Vaplikumë +Vaplița +Vaslärne +Vaslö +Vaslöjan +Vaslölno +Vaslörk +Vassbë +Vasserz +Vassmorse +Vasson +Vassontre +Vatrance +Vatre +Vatrours +Vatrzyn +Vede +Vede-France +Vede-Seine +Velac +Velais +Venja +Venjas +Venjasa +Vern +Verna +Vernarki +Verne +Vernebärre +Vernód +Vernów +Vert +Vertin +Vertro +Vicova +Vicovasne +Vika +Vikars +Vikmance +Vikmancea +Vikmanów +Villby +Ville +Villentek +Villie +Villmorbo +Villon +Villshësh +Villtorp +Vingarna +Vinge +Vingjin +Vinne +Vinneva +Vittan +Vittjöd +Vlă Con +Vlăca +Vlăcan +Vlăcin +Vlăcuence +Vlăcutany +Vlăge +Vlăgen +Vlăhinées +Vlăil +Vlăilești +Vlăilia +Vlăillos +Vlăla +Vlălac +Vlălt +Vlăląska +Vlăląski +Vlăne +Vlărbyna +Vlărmălan +Vlărne +Vlătë +Vlău +Vlău Sibik +Vlăulia +Vlăulna +Vlăvelia +Vlăveliq +Vlăvenicë +Vlăvestra +Vlăvodë +Vlășnaryd +Vlăț +Vlăț Nea +Vlăța +Volu Rud +Volui +Voluj +Volumsh +Volunde +Vorängs +Voräs +Voräste +Voräsås +Vorå +Vorénénë +Voréteine +Voréthult +Voród +Vorój +Vorö +Voröd +Voröder +Vosges +Vran +Vran Nea +Vrana +Vrance +Vrance-Den +Vrancece +Vranceceava +Vranceșand +Vrand +Vranda +Vrandhede +Vrandsda +Vrandy +Vrandë +Vrani +Vrani Alpes +Vranieu +Vraniew +Vrannes +Vranti +Vrantin-de +Vrantinia +Vrantos +Vrantë +Vrany +Vranów +Vranței +Vulcea +Vulceamț +Vulceasa +Vulceava +Vulceavarbo +Vulceavasna +Vulceș +Vulcești +Vulchen +Vulchenice +Vulchenikie +Vulcyresås +Vulcyrées +Vulcyrénë +Vulcyrë +Vâlce +Vâlon +Vâlonice +Vâlonkë +Vâlonków +Vâlonne +Vâlonnes +Värmdöpi +Värne +Värnes +Värnevik +Värnut +Värnutby +Värpås +Värr +Värre +Värv +Väs +Väslösa +Väster +Västevie +Västorp +Västorpå +Västos +Västou +Västreu +Väsvik +Väsville +Väsåken +Våxtocka +Våxtopes +Våxtos +Wejher +Wejherblin +Wejhercin +Wejherg +Wejherga +Wejhergne +Wejhergy +Wejherhenis +Wejherin +Wejherine +Wejherinna +Wejherk +Wejhermaine +Wejhermar +Wejhermes +Wejheron +Wejherrande +Wejherrigne +Wejhers +Wejherski +Wejhert +Wejhertorp +Wejhertoș +Wejhertë +Wejheryd +Wejherz +Wejherzyce +Wejherzyn +Wiel +Wiel-soulos +Wiel-sureș +Wieladecice +Wielance +Wielaniec +Wiele +Wielenis +Wielentevik +Wieleoagów +Wieleordy +Wielichtyn +Wielicov +Wielko +Wielkovence +Wielles +Wielniki +Wielont +Wielousson +Wielouviken +Wielski +Wielvidiu +Wielviken +Wielville +Witka +Wiąbkowiec +Wiąbkowy +Wiąbrazhd +Wiąbro +Wiąski +Wiąsko +Wiście +Wiśl +Wiśnicz +Wiśniew +Wiśnionne +Wlełchare +Wlełczyce +Wlełczyn +Wlełczyna +Wlełczynis +Wleśna +Wleśnic +Wleśnica +Wleśnice +Wleśnicz +Wleźnik +Wleźnikum +Wleźno +Woda +Wodal +Wodala +Wodalance +Wola +Wola-Garne +Wolaiski +Wolaiskie +Wolaț +Wolsac +Wolsace +Wolsacker +Wolsacz +Wolsh +Wolsk Poita +Wolski +Wolskie +Wolskingjin +Wolskovence +Wolstabelsh +Wolstany +Woź +Woźmița +Woźni Iaș +Woźno +Woźnon +Woźnov +Woźnoît +Wyson +Wysonne +Wysonnebiș +Wysonneby +Wysonnec +Wysonneu +Wysonnlac +Wysonnö +Wysonsta +Wysonstad +Wysonstain +Wysouscat +Wysoushög +Wysousseda +Wysoussin +Wąbki +Wąbkie +Wąbko +Wąbkopinge +Wąbkopolsh +Wąbkowonia +Wąbro +Wąbrondy +Wąbronnes +Wąbrovence +Wąbrzyn +Wąbrzyń +Wąbygge +Wąbyggen +Wąbygges +Wąbyggeși +Węgor +Węgora +Węgord +Węgore +Węgorj +Węgormaisz +Węgormar +Węgormark +Węgornis +Węgorp +Węgorrance +Węgorranch +Węgors +Węgorzów +Yerr +Yerra +Yerrabë +Yerrabëz +Yerraine +Yerraintrel +Yerrance +Yerras +Yerrasa +Yerrasny +Yerrastë +Yerraszew +Yerraszë +Yerre +Yerred +Yerredniec +Yerrelance +Yerrelonne +Yerrelvik +Yerren +Yerrenice +Yerreniș +Yerrik +Yerriki +Yerrë +Yerrërmet +Yerrësht +Yerrëz +Yerrökë +Yon +Ytte +Ytte-Garan +Ytte-Maria +Ytte-Sava +Ytte-Savaj +Ytte-Seine +Ytte-Sevik +Yttea +Yttei +Ytteilla +Ytteillik +Ytteillomin +Yttek +Yttenan +Yttenican +Ytterforden +Yttermåda +Ytterre +Ytters +Ytterv +Yttervik +Yttes +Ytteville +Yvel +Yvelad +Yveladeline +Yvele +Yvele Suce +Yvelen +Yvelena +Yvelenis +Yvelentance +Yvelentra +Yveleormdë +Yvelgård +Yvelicara +Yvelicht +Yveliczew +Yvelin +Yvelinais +Yveline +Yvelingeby +Yvelinges +Yvelinë +Yvelita +Yvelkopinte +Yvelkopol +Yvellëz +Yvelon +Yvelou +Yvelourne +Yvelskara +Yvelski +Yvelskie +Yvelskov +Yvelsta +Yvelstabë +Yvelstani +Yvelstors +Yvelunsta +Yveluławy +Yvelvik +Yvelvillacz +Yvelvița +Zagåkering +Zagåkers +Zagård +Zagów +Zagówek +Zale +Zale-Biała +Zalene-Sain +Zalenë +Zalăhița +Zalărașov +Zalărne +Zalău +Zaląsence +Zaląsenis +Zaląska +Zaląski +Zaląskie +Zaląskind +Zawice +Zawiec +Zawiec Kuj +Zawieceava +Zawiechor +Zawiecki +Zawiecko +Zawierg +Zawieryd +Zawiskovë +Zdräfsna +Zdrännë +Zdrännö +Zdräslöv +Zdrästena +Zdrénées +Zdrénénë +Zdrénénö +Zdrénéund +Zdrénéune +Zdrë +Zdrës +Zdrëshen +Zdrëz +Zdród +Zdrój +Zdrów +Zdrö +Zdröd +Zdröderlan +Zdrödervë +Zdrödik +Zgie +Zgieby +Zgiebyn +Zgiec +Zgiedzice +Zgiegłowy +Zgiei +Zgieliergy +Zgieline +Zgielitów +Zgieliçani +Zgielny +Zgielos +Zgielsztyna +Zgielui +Zgienion +Zgienne +Zgier +Zgiersicz +Zgierskie +Zgierskë +Zgierszeg +Zgiescq +Zgiestë +Zgieu +Zgiew +Zgień +Ziel +Zieladec +Ziele +Zielen +Zielenis +Zielin +Zielkopol +Zielou +Zieloupes +Zielundy +Zieluławer +Zielviț +Zimn +Zimnica +Zimnice +Zimnicz +Ziębihamn +Ziębihan +Ziębik +Ziębin +Ziębiu +Ziędzion +Ziędzise +Ziędzymes +Ziędzyn +Ziętre +Ziętrzyna +Zlat +Zlatla +Zlatlac +Zlatlais +Zlatlaj +Zlatos +Zlatosine +Zlatosines +Zlatostos +Zlatoszcz +Zlatower +Zlatrabune +Zlatrad +Zlatrahor +Zlatramsh +Zlatrance +Zlatrazhd +Zlatraśl +Zlatu Marne +Zlatu dec +Zlatój +Zlatórë +Zlatów +Zăraș +Zărași +Zărașov +Zărați +Zărbune +Zărbuty +Zărbutyn +Zărmănea +Zărmăneș +Zărne +Zărărne +Ząbko +Ząbkopina +Ząbkovence +Ząbkoviken +Ząbkowo +Ząbrombrą +Ząbrondy +Ząbrore +Ząbrores +Ząbrovence +Ząbroye +Ząbroyes +Ząbrz +Ząbrzewy +Ząbrzyce +Ząbygge +Ząbyggen +Ząbygger +Ząbyggeși +Złock +Złocki +Złocz +Złocz Zdre +Złoczka +Złoczyce +Złoczyn +Złodzica +Złodzion +Złodzis +Złogonicz +Złogorzebo +Złogoszcz +Złogosze +Złogozhine +Złomier +Złomiți +Złomłyn +Złomża +Złono +Złonov +Złonoît +Złopolon +Złopon +Złostance +Złostra +Złotë +Złowy +Złowy Dolm +Złowy Wola diff --git a/tribe.cpp b/tribe.cpp new file mode 100644 index 000000000..30ff2b68e --- /dev/null +++ b/tribe.cpp @@ -0,0 +1,120 @@ +#include "stdafx.h" + + +using namespace std; + +class KillEveryone : public Tribe { + public: + KillEveryone() : Tribe("", false) {} + virtual double getStanding(const Creature*) const override { + return -1; + } +}; + +Tribe* const Tribe::monster = new Tribe("", false); +Tribe* const Tribe::pest = new Tribe("", false); +Tribe* const Tribe::wildlife = new Tribe("", false); +Tribe* const Tribe::elven = new Tribe("elves", true); +Tribe* const Tribe::goblin = new Tribe("goblins", true); +Tribe* const Tribe::dwarven = new Tribe("dwarves", true); +Tribe* const Tribe::player = new Tribe("", false); +Tribe* const Tribe::killEveryone = new KillEveryone(); + +Tribe::Tribe(const string& n, bool d) : diplomatic(d), name(n) { +} + +const string& Tribe::getName() { + return name; +} + +double Tribe::getStanding(const Creature* c) const { + if (c->getTribe() == this) + return 1; + if (standing.count(c)) + return standing.at(c); + if (enemyTribes.count(c->getTribe())) + return -1; + return 0; +} + +void Tribe::initStanding(const Creature* c) { + standing[c] = getStanding(c); +} + +void Tribe::addEnemy(Tribe* t) { + enemyTribes.insert(t); + t->enemyTribes.insert(this); +} + +void Tribe::makeSlightEnemy(const Creature* c) { + standing[c] = -0.001; +} + +static const double killBonus = 0.1; +static const double killPenalty = 0.5; +static const double attackPenalty = 0.2; +static const double thiefPenalty = 0.5; +static const double importantMemberMult = 0.5 / killBonus; + +double Tribe::getMultiplier(const Creature* member) { + if (contains(importantMembers, member)) + return importantMemberMult; + else + return 1; +} + + +void Tribe::onKillEvent(const Creature* member, const Creature* attacker) { + if (member->getTribe() != this || attacker == nullptr) + return; + initStanding(attacker); + standing[attacker] -= killPenalty * getMultiplier(member); + for (Tribe* t : enemyTribes) + if (t->diplomatic) { + t->initStanding(attacker); + t->standing[attacker] += killBonus * getMultiplier(member); + } +} + +void Tribe::onAttackEvent(const Creature* member, const Creature* attacker) { + if (member->getTribe() != this) + return; + if (contains(attacks, make_pair(member, attacker))) + return; + attacks.emplace_back(member, attacker); + initStanding(attacker); + standing[attacker] -= attackPenalty * getMultiplier(member); +} + +void Tribe::addImportantMember(const Creature* c) { + importantMembers.push_back(c); +} + +vector Tribe::getImportantMembers() { + return importantMembers; +} + +void Tribe::onItemsStolen(const Creature* attacker) { + if (diplomatic) { + initStanding(attacker); + standing[attacker] -= thiefPenalty; + } +} + +void Tribe::init() { + EventListener::addListener(goblin); + EventListener::addListener(dwarven); + EventListener::addListener(elven); + elven->addEnemy(goblin); + elven->addEnemy(dwarven); + goblin->addEnemy(dwarven); + goblin->addEnemy(wildlife); + player->addEnemy(monster); + player->addEnemy(goblin); + player->addEnemy(pest); + monster->addEnemy(wildlife); + wildlife->addEnemy(player); + wildlife->addEnemy(goblin); + wildlife->addEnemy(monster); +} + diff --git a/tribe.h b/tribe.h new file mode 100644 index 000000000..5c21a3821 --- /dev/null +++ b/tribe.h @@ -0,0 +1,53 @@ +#ifndef _TRIBE_H +#define _TRIBE_H + +#include +#include + +#include "enums.h" +#include "creature.h" +#include "event.h" + +class Tribe : public EventListener { + public: + virtual double getStanding(const Creature*) const; + + Tribe(const string& name, bool diplomatic); + + virtual void onKillEvent(const Creature* victim, const Creature* killer) override; + virtual void onAttackEvent(const Creature* victim, const Creature* attacker) override; + + void onItemsStolen(const Creature* thief); + void makeSlightEnemy(const Creature*); + void addImportantMember(const Creature*); + vector getImportantMembers(); + const string& getName(); + void addEnemy(Tribe*); + + static void init(); + + static Tribe* const monster; + static Tribe* const pest; + static Tribe* const wildlife; + static Tribe* const elven; + static Tribe* const dwarven; + static Tribe* const goblin; + static Tribe* const player; + static Tribe* const vulture; + static Tribe* const killEveryone; + + private: + + bool diplomatic; + + void initStanding(const Creature*); + double getMultiplier(const Creature* member); + + unordered_map standing; + vector> attacks; + vector importantMembers; + unordered_set enemyTribes; + string name; +}; + +#endif diff --git a/trigger.cpp b/trigger.cpp new file mode 100644 index 000000000..18b49b1c7 --- /dev/null +++ b/trigger.cpp @@ -0,0 +1,102 @@ +#include "stdafx.h" + +Trigger::Trigger(Level* l, Vec2 p) : level(l), position(p) { +} + +Trigger::Trigger(const ViewObject& obj, Level* l, Vec2 p): viewObject(obj), level(l), position(p) { +} + +Optional Trigger::getViewObject() const { + return viewObject; +} + +class Portal : public Trigger { + public: + Portal(const ViewObject& obj, Level* l, Vec2 position) : Trigger(obj, l, position) { + if (previous) { + other = previous; + other->other = this; + previous = nullptr; + startTime = other->startTime = -1; + } else + previous = this; + } + + virtual void onCreatureEnter(Creature* c) override { + if (!active) { + active = true; + return; + } + if (other) { + other->active = false; + c->you(MsgType::ENTER_PORTAL, ""); + if (other->level == level) { + if (level->canMoveCreature(c, other->position - c->getPosition())) { + level->moveCreature(c, other->position - c->getPosition()); + return; + } + for (Vec2 v : other->position.neighbors8()) + if (level->canMoveCreature(c, v - c->getPosition())) { + level->moveCreature(c, v - c->getPosition()); + return; + } + } else + level->changeLevel(other->level, other->position, c); + } else + c->privateMessage("The portal is not working."); + } + + virtual bool interceptsFlyingItem(Item* it) const override { + return other && !Random.roll(5); + } + + virtual void onInterceptFlyingItem(PItem it, Attack a, int remainingDist, Vec2 dir) { + level->globalMessage(position, it->getTheName() + " disappears in the portal."); + other->level->throwItem(std::move(it), a, remainingDist, other->position, dir); + } + + virtual void tick(double time) override { + if (startTime == -1) + startTime = time; + if (time - startTime >= 100) { + level->getSquare(position)->removeTrigger(this); + level->globalMessage(position, "The portal disappears."); + } + } + + private: + double startTime = 1000000; + bool active = true; + Portal* other = nullptr; + static Portal* previous; +}; + +Portal* Portal::previous = nullptr; + +PTrigger Trigger::getPortal(const ViewObject& obj, Level* l, Vec2 position) { + return PTrigger(new Portal(obj, l, position)); +} + +class Trap : public Trigger { + public: + Trap(const ViewObject& obj, Level* l, Vec2 position, PEffect _effect, Tribe* _tribe) + : Trigger(obj, l, position), effect(std::move(_effect)), tribe(_tribe) { + } + + virtual void onCreatureEnter(Creature* c) override { + if (c->getTribe() != tribe) { + c->you(MsgType::TRIGGER_TRAP, ""); + effect->applyToCreature(c, EffectStrength::NORMAL); + EventListener::addTriggerEvent(c->getLevel(), c->getPosition()); + c->getSquare()->removeTrigger(this); + } + } + + private: + PEffect effect; + Tribe* tribe; +}; + +PTrigger Trigger::getTrap(const ViewObject& obj, Level* l, Vec2 position, PEffect effect, Tribe* tribe) { + return PTrigger(new Trap(obj, l, position, std::move(effect), tribe)); +} diff --git a/trigger.h b/trigger.h new file mode 100644 index 000000000..9af587ca5 --- /dev/null +++ b/trigger.h @@ -0,0 +1,31 @@ +#ifndef _TRIGGER_H +#define _TRIGGER_H + +#include "view_object.h" +#include "util.h" +#include "creature.h" + +class Trigger { + public: + + Optional getViewObject() const; + + virtual void onCreatureEnter(Creature* c) {} + virtual bool interceptsFlyingItem(Item* it) const { return false; } + virtual void onInterceptFlyingItem(PItem it, Attack a, int remainingDist, Vec2 dir) {} + + virtual void tick(double time) {} + + static PTrigger getPortal(const ViewObject& obj, Level*, Vec2 position); + static PTrigger getTrap(const ViewObject& obj, Level* l, Vec2 position, PEffect _effect, Tribe* _tribe); + + protected: + Trigger(Level*, Vec2 position); + Trigger(const ViewObject& obj, Level* l, Vec2 p); + + Optional viewObject; + Level* level; + Vec2 position; +}; + +#endif diff --git a/typodermic license agreement.pdf b/typodermic license agreement.pdf new file mode 100644 index 000000000..15004859e Binary files /dev/null and b/typodermic license agreement.pdf differ diff --git a/util.cpp b/util.cpp new file mode 100644 index 000000000..cd5e60a4e --- /dev/null +++ b/util.cpp @@ -0,0 +1,408 @@ +#include "stdafx.h" + +using namespace std; + +void RandomGen::init(int seed) { + generator.seed(seed); +} + +int RandomGen::getRandom(int max) { + return getRandom(0, max); +} + +int RandomGen::getRandom(int min, int max) { + CHECK(max > min); + return uniform_int_distribution(min, max - 1)(generator); +} + +bool RandomGen::roll(int chance) { + return getRandom(chance) == 0; +} + +double RandomGen::getDouble() { + return uniform_real_distribution()(generator); +} + +double RandomGen::getDouble(double a, double b) { + return uniform_real_distribution(a, b)(generator); +} + +RandomGen Random; + +template string convertToString(const int&); +template string convertToString(const size_t&); +template string convertToString(const char&); +template string convertToString(const double&); + +template int convertFromString(const string&); +template char convertFromString(const string&); +template double convertFromString(const string&); + +template +string convertToString(const T& t){ + std::stringstream ss; + ss << t; + return ss.str(); +} + +template +T convertFromString(const string& s){ + std::stringstream ss(s); + T t; + ss >> t; + CHECK(ss) << "Error parsing " << s << " to " << typeid(T).name(); + return t; +} + +void trim(string& s) { + CHECK(s.size() > 0); + while (isspace(s[0])) + s.erase(s.begin()); + while (isspace(*s.rbegin())) + s.erase(s.size() - 1); +} + +string toUpper(const string& s) { + string ret(s); + transform(ret.begin(), ret.end(), ret.begin(), ::toupper); + return ret; +} + +string toLower(const string& s) { + string ret(s); + transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; +} + +vector split(const string& s, char delim) { + int begin = 0; + vector ret; + for (int i : Range(s.size() + 1)) + if (i == s.size() || s[i] == delim) { + string tmp = s.substr(begin, i - begin); + if (!tmp.empty()) + ret.push_back(tmp); + begin = i + 1; + } + return ret; +} + +template<> +bool contains(const string& s, const string& p) { + return s.find(p) != string::npos; +} + + +Vec2::Vec2(int _x, int _y) : x(_x), y(_y) { +} + +Vec2 Vec2::mult(const Vec2& v) const { + return Vec2(x * v.x, y * v.y); +} + +Vec2 Vec2::div(const Vec2& v) const { + return Vec2(x / v.x, y / v.y); +} + +int Vec2::dotProduct(Vec2 a, Vec2 b) { + return a.x * b.x + a.y * b.y; +} + +vector Vec2::box(int radius, bool shuffle) { + if (radius == 0) + return {*this}; + vector v; + for (int k = -radius; k < radius; ++k) + v.push_back(*this + Vec2(k, -radius)); + for (int k = -radius; k < radius; ++k) + v.push_back(*this + Vec2(radius, k)); + for (int k = -radius; k < radius; ++k) + v.push_back(*this + Vec2(-k, radius)); + for (int k = -radius; k < radius; ++k) + v.push_back(*this + Vec2(-radius, -k)); + if (shuffle) + random_shuffle(v.begin(), v.end(), [](int a) { return Random.getRandom(a);}); + return v; +} + +vector Vec2::directions8(bool shuffle) { + return Vec2(0, 0).neighbors8(shuffle); +} + +vector Vec2::neighbors8(bool shuffle) const { + vector res = {Vec2(x, y + 1), Vec2(x + 1, y), Vec2(x, y - 1), Vec2(x - 1, y), Vec2(x + 1, y + 1), Vec2(x + 1, y - 1), Vec2(x - 1, y - 1), Vec2(x - 1, y + 1)}; + if (shuffle) + random_shuffle(res.begin(), res.end(),[](int a) { return Random.getRandom(a);}); + return res; +} + +vector Vec2::directions4(bool shuffle) { + return Vec2(0, 0).neighbors4(shuffle); +} + +vector Vec2::neighbors4(bool shuffle) const { + vector res = { Vec2(x, y + 1), Vec2(x + 1, y), Vec2(x, y - 1), Vec2(x - 1, y)}; + if (shuffle) + random_shuffle(res.begin(), res.end(),[](int a) { return Random.getRandom(a);}); + return res; +} + +bool Vec2::isCardinal4() const { + return abs(x) + abs(y) == 1; +} + +vector Vec2::corners() { + return { Vec2(1, 1), Vec2(1, -1), Vec2(-1, -1), Vec2(-1, 1)}; +} + +bool Vec2::inRectangle(int px, int py, int kx, int ky) const { + return x >= px && x < kx && y >= py && y < ky; +} + +bool Vec2::inRectangle(const Rectangle& r) const { + return inRectangle(r.getPX(), r.getPY(), r.getKX(), r.getKY()); +} + +bool Vec2::operator== (const Vec2& v) const { + return v.x == x && v.y == y; +} + +bool Vec2::operator!= (const Vec2& v) const { + return v.x != x || v.y != y; +} + +Vec2& Vec2::operator +=(const Vec2& v) { + x += v.x; + y += v.y; + return *this; +} + +Vec2 Vec2::operator + (const Vec2& v) const { + return Vec2(x + v.x, y + v.y); +} + +Vec2 Vec2::operator * (int a) const { + return Vec2(x * a, y * a); +} + +Vec2 Vec2::operator / (int a) const { + return Vec2(x / a, y / a); +} + +Vec2& Vec2::operator -=(const Vec2& v) { + x -= v.x; + y -= v.y; + return *this; +} + +Vec2 Vec2::operator - (const Vec2& v) const { + return Vec2(x - v.x, y - v.y); +} + +Vec2 Vec2::operator - () const { + return Vec2(-x, -y); +} + +bool Vec2::operator < (Vec2 v) const { + return x < v.x || (x == v.x && y < v.y); +} + +int Vec2::length8() const { + return max(abs(x), abs(y)); +} + +int Vec2::dist8(Vec2 v) const { + return (v - *this).length8(); +} + +int Vec2::length4() const { + return abs(x) + abs(y); +} + +double Vec2::lengthD() const { + return sqrt(x * x + y * y); +} + +Vec2 Vec2::shorten() const { + CHECK(x == 0 || y == 0 || abs(x) == abs(y)); + int d = length8(); + return Vec2(x / d, y / d); +} + +static int sgn(int a) { + if (a == 0) + return 0; + if (a < 0) + return -1; + else + return 1; +} + +static int sgn(int a, int b) { + if (abs(a) >= abs(b)) + return sgn(a); + else + return 0; +} + +pair Vec2::approxL1() const { + return make_pair(Vec2(sgn(x, x), sgn(y,y)), Vec2(sgn(x, y), sgn(y, x))); +} + +string Vec2::getBearing() const { + double ang = atan2(y, x) / 3.14159265359 * 180 / 45; + if (ang < 0) + ang += 8; + if (ang < 0.5 || ang >= 7.5) + return "east"; + if (ang >= 0.5 && ang < 1.5) + return "south-east"; + if (ang >= 1.5 && ang < 2.5) + return "south"; + if (ang >= 2.5 && ang < 3.5) + return "south-west"; + if (ang >= 3.5 && ang < 4.5) + return "west"; + if (ang >= 4.5 && ang < 5.5) + return "north-west"; + if (ang >= 5.5 && ang < 6.5) + return "north"; + if (ang >= 6.5 && ang < 7.5) + return "north-east"; + Debug(FATAL) << ang; + return ""; +} + +Rectangle::Rectangle(int w, int h) : px(0), py(0), kx(w), ky(h) { + CHECK(w > 0 && h > 0); +} + +Rectangle::Rectangle(Vec2 d) : px(0), py(0), kx(d.x), ky(d.y) { + CHECK(d.x > 0 && d.y > 0); +} + +Rectangle::Rectangle(int px1, int py1, int kx1, int ky1) : px(px1), py(py1), kx(kx1), ky(ky1) { + CHECK(kx > px && ky > py); +} + +Rectangle::Rectangle(Vec2 p, Vec2 k) : px(p.x), py(p.y), kx(k.x), ky(k.y) { + CHECK(k.x > p.x); + CHECK(k.y > p.y); +} + +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 { + return Vec2(Random.getRandom(px, kx), Random.getRandom(py, ky)); +} + +Vec2 Rectangle::middle() const { + return Vec2((px + kx) / 2, (py + ky) / 2); +} + +int Rectangle::getPX() const { + return px; +} + +int Rectangle::getPY() const { + return py; +} + +int Rectangle::getKX() const { + return kx; +} + +int Rectangle::getKY() const { + return ky; +} + +int Rectangle::getW() const { + return kx - px; +} + +int Rectangle::getH() const { + return ky - py; +} + +bool Rectangle::intersects(const Rectangle& other) const { + bool a = Vec2(px, py).inRectangle(other); + bool b = Vec2(kx - 1, py).inRectangle(other); + bool c = Vec2(px, ky - 1).inRectangle(other); + bool d = Vec2(kx - 1, ky - 1).inRectangle(other); + bool e = Vec2(other.px, other.py).inRectangle(*this); + bool f = Vec2(other.kx - 1, other.py).inRectangle(*this); + bool g = Vec2(other.px, other.ky - 1).inRectangle(*this); + bool h = Vec2(other.kx - 1, other.ky - 1).inRectangle(*this); + return a || b || c || d || e || f || g || h; +} + +Rectangle Rectangle::minusMargin(int margin) const { + CHECK(px + margin < kx - margin && py + margin < ky - margin) << "Margin too big"; + return Rectangle(px + margin, py + margin, kx - margin, ky - margin); +} + +Vec2 Rectangle::Iter::operator* () const { + return pos; +} +bool Rectangle::Iter::operator != (const Iter& other) const { + return pos != other.pos; +} + +const Rectangle::Iter& Rectangle::Iter::operator++ () { + ++pos.y; + if (pos.y >= ky) { + pos.y = py; + ++pos.x; + } else + CHECK(pos.x < kx) << "Iterator out of range: " << **this << ", " << Vec2(px, py) << " " << Vec2(kx, ky); + return *this; +} + +Rectangle::Iter Rectangle::begin() { + return Iter(px, py, px, py, kx, ky); +} + +Rectangle::Iter Rectangle::end() { + return Iter(kx, py, px, py, kx, ky); +} + +Range::Range(int a, int b) : start(a), finish(b) { + CHECK(a <= b); +} +Range::Range(int a) : Range(0, a) {} + +Range::Iter Range::begin() { + return Iter(start, start, finish); +} + +Range::Iter Range::end() { + return Iter(finish, start, finish); +} + +Range::Iter::Iter(int i, int a, int b) : ind(i), min(a), max(b) {} + +int Range::Iter::operator* () const { + return ind; +} + +bool Range::Iter::operator != (const Iter& other) const { + return other.ind != ind; +} + +const Range::Iter& Range::Iter::operator++ () { + CHECK(++ind <= max); + return *this; +} + +string combine(const vector& adj) { + string res; + for (int i : All(adj)) { + if (i == adj.size() - 1 && i > 0) + res.append(" and "); + else if (i > 0) + res.append(", "); + res.append(adj[i]); + } + return res; +} + diff --git a/util.h b/util.h new file mode 100644 index 000000000..5aedbb052 --- /dev/null +++ b/util.h @@ -0,0 +1,655 @@ +#ifndef _UTIL_H +#define _UTIL_H + +#include + +#include "debug.h" + +template +string convertToString(const T& t); + +template +T convertFromString(const string& s); + +class Item; +typedef unique_ptr PItem; + +class Creature; +typedef unique_ptr PCreature; + +class Effect; +typedef unique_ptr PEffect; + +class Square; +typedef unique_ptr PSquare; + +class MonsterAI; +typedef unique_ptr PMonsterAI; + +class Behaviour; +typedef unique_ptr PBehaviour; + +class Obstacle; +typedef unique_ptr PObstacle; + +class Task; +typedef unique_ptr PTask; + +class Controller; +typedef unique_ptr PController; + +class Trigger; +typedef unique_ptr PTrigger; + +void trim(string& s); +string toUpper(const string& s); +string toLower(const string& s); + +vector split(const string& s, char delim); + +class Rectangle; + +class Vec2 { + public: + int x, y; + Vec2() : x(0), y(0) {} + Vec2(int x, int y); + bool inRectangle(int px, int py, int kx, int ky) const; + bool inRectangle(const Rectangle&) const; + bool operator == (const Vec2& v) const; + bool operator != (const Vec2& v) const; + Vec2 operator + (const Vec2& v) const; + Vec2 operator * (int) const; + Vec2 operator / (int) const; + Vec2& operator += (const Vec2& v); + Vec2 operator - (const Vec2& v) const; + Vec2& operator -= (const Vec2& v); + Vec2 operator - () const; + bool operator < (Vec2) const; + Vec2 mult(const Vec2& v) const; + Vec2 div(const Vec2& v) const; + static int dotProduct(Vec2 a, Vec2 b); + int length8() const; + int length4() const; + int dist8(Vec2) const; + double lengthD() const; + Vec2 shorten() const; + pair approxL1() const; + string getBearing() const; + bool isCardinal4() const; + + vector box(int radius, bool shuffle = false); + static vector directions8(bool shuffle = false); + vector neighbors8(bool shuffle = false) const; + static vector directions4(bool shuffle = false); + vector neighbors4(bool shuffle = false) const; + static vector corners(); +}; + +namespace std { + +template <> struct hash { + size_t operator()(const Vec2& obj) const { + return hash()(obj.x) * 10000 + hash()(obj.y); + } +}; + +} + + +class Rectangle { + public: + Rectangle(int width, int height); + Rectangle(Vec2 dim); + Rectangle(int px, int py, int kx, int ky); + Rectangle(Vec2 p, Vec2 k); + + int getPX() const; + int getPY() const; + int getKX() const; + int getKY() const; + int getW() const; + int getH() const; + + bool intersects(const Rectangle& other) const; + + Rectangle minusMargin(int margin) const; + + Vec2 randomVec2() const; + Vec2 middle() const; + + class Iter { + public: + Iter(int x, int y, int px, int py, int kx, int ky); + + Vec2 operator* () const; + bool operator != (const Iter& other) const; + + const Iter& operator++ (); + + private: + Vec2 pos; + int px, py, kx, ky; + }; + + Iter begin(); + Iter end(); + private: + int px, py, kx, ky; +}; + +class Range { + public: + Range(int start, int end); + Range(int end); + + class Iter { + public: + Iter(int ind, int min, int max); + + int operator* () const; + bool operator != (const Iter& other) const; + + const Iter& operator++ (); + + private: + int ind; + int min; + int max; + }; + + Iter begin(); + Iter end(); + + private: + int start; + int finish; +}; + +template +Range All(const T& container) { + return Range(container.size()); +} + +extern const vector neighbors; + +class RandomGen { + public: + void init(int seed); + int getRandom(int max); + int getRandom(int min, int max); + double getDouble(); + double getDouble(double a, double b); + bool roll(int chance); + + private: + default_random_engine generator; +}; + +extern RandomGen Random; + +template +class Table { + public: + Table(Table&& t) = default; + + Table(int x, int y, int w, int h) : width(w), height(h), px(x), py(y), mem(new T[width * height]()) { + } + + Table(const Rectangle& rect) : Table(rect.getPX(), rect.getPY(), rect.getW(), rect.getH()) { + } + + Table(const Rectangle& rect, const T& value) : Table(rect.getPX(), rect.getPY(), rect.getW(), rect.getH(), value) { + } + + Table(int w, int h) : Table(0, 0, w, h) { + } + + Table(int x, int y, int width, int height, const T& value) : Table(x, y, width, height) { + for (int i : Range(width * height)) + mem[i] = value; + } + + Table(int width, int height, const T& value) : Table(0, 0, width, height, value) { + } + + Rectangle getBounds() const { + return Rectangle(px, py, px + width, py + height); + } + + Table& operator = (Table&& other) = default; + + int getWidth() const { + return width; + } + int getHeight() const { + return height; + } + + class RowAccess { + public: + RowAccess(T* m, int p, int w) : px(p), width(w), mem(m) {}; + T& operator[](int ind) { + CHECK(ind >= px && ind < px + width); + return mem[ind - px]; + } + + const T& operator[](int ind) const { + CHECK(ind >= px && ind < px + width); + return mem[ind - px]; + } + + private: + int px; + int width; + T* mem; + }; + + RowAccess operator[](int ind) { + return RowAccess(mem.get() + (ind - px) * height, py, height); + } + + RowAccess operator[](int ind) const { + return RowAccess(mem.get() + (ind - px) * height, py, height); + } + + T& operator[](Vec2 vAbs) { + Vec2 v(vAbs.x - px, vAbs.y - py); + CHECK(v.x >= 0 && v.y >= 0 && v.x < width && v.y < height) << + "Table index out of bounds " << getBounds() << " " << vAbs; + return mem[v.x * height + v.y]; + } + + const T& operator[](Vec2 vAbs) const { + Vec2 v(vAbs.x - px, vAbs.y - py); + CHECK(v.x >= 0 && v.y >= 0 && v.x < width && v.y < height) << + "Table index out of bounds " << getBounds() << " " << vAbs; + return mem[v.x * height + v.y]; + } + + private: + int width; + int height; + int px = 0; + int py = 0; + unique_ptr mem; +}; + +template +T chooseRandom(const vector& vi, const vector& pi, double r = -1) { + vector v(vi); + vector p(pi); + CHECK(v.size() == p.size()); + double sum = 0; + for (double elem : p) + sum += elem; + if (r == -1) + r = Random.getDouble(0, sum); + sum = 0; + for (int i : All(p)) { + sum += p[i]; + if (sum >= r) + return v[i]; + } + return v.back(); +} + + +template +T chooseRandom(const vector& vi, double r = -1) { + vector v(vi); + vector pi(v.size(), 1); + return chooseRandom(v, pi, r); +} + +template +T chooseRandom(const set& vi, double r = -1) { + vector v(vi.size()); + std::copy(vi.begin(), vi.end(), v.begin()); + return chooseRandom(v, r); +} + +template +T chooseRandom(initializer_list vi, initializer_list pi, double r = -1) { + return chooseRandom(vector(vi), vector(pi), r); +} + +template +T chooseRandom(initializer_list vi, double r = -1) { + vector v(vi); + vector pi(v.size(), 1); + return chooseRandom(v, pi, r); +} + +template +T chooseRandom(vector> vi, double r = -1) { + vector v; + vector p; + for (auto elem : vi) { + v.push_back(elem.first); + p.push_back(elem.second); + } + return chooseRandom(v, p); +} + +template +T chooseRandom(initializer_list> vi, double r = -1) { + return chooseRandom(vector>(vi), r); +} + +template +vector randomPermutation(vector v) { + random_shuffle(v.begin(), v.end(), [](int a) { return Random.getRandom(a);}); + return v; +} + +template +vector randomPermutation(const set& vi) { + vector v(vi.size()); + std::copy(vi.begin(), vi.end(), v.begin()); + return randomPermutation(v); +} + +template +vector randomPermutation(initializer_list vi) { + vector v(vi); + random_shuffle(v.begin(), v.end(), [](int a) { return Random.getRandom(a);}); + return v; +} + +template +vector getKeys(const map& m) { + vector ret; + for (auto elem : m) + ret.push_back(elem.first); + return ret; +} + +template +map > groupBy(const vector& values, function getKey) { + map > ret; + for (const T& elem : values) + ret[getKey(elem)].push_back(elem); + return ret; +} + +template +vector getPrefix(const vector& v, int start, int length) { + CHECK(start >= 0 && length > 0 && start + length <= v.size()); + vector ret; + for (int i : Range(start, start + length)) + ret.push_back(v[i]); + return ret; +} + +template +bool contains(const T& v, const V& elem) { + return std::find(v.begin(), v.end(), elem) != v.end(); +} + +template<> +bool contains(const string& s, const string& pattern); + +template +bool contains(const initializer_list& v, const V& elem) { + return contains(vector(v), elem); +} + +template +vector concat(const vector& v, const vector& w) { + vector ret(v); + for (T elem : w) + ret.push_back(elem); + return ret; +} + +template +function alwaysTrue() { + return [](T) { return true; }; +} + +template +function alwaysFalse() { + return [](T) { return false; }; +} + +template +function andFun(function x, const function y) { + return [x, y](T t) { return x(t) && y(t); }; +} + +class Nothing { +}; + +template +class Optional { + public: + Optional(const T& t) : elem(new T(t)) {} + Optional(const Optional& t) { + if (t) + elem.reset(new T(*t)); + } + Optional(Optional&&) = default; + Optional(Nothing) {} + Optional() {} + + T& operator = (const T& t) { + elem.reset(new T(t)); + return *elem.get(); + } + + void operator = (Nothing) { + elem.reset(); + } + + T& operator = (T&& t) { + if (elem) + *elem = std::move(t); + else + elem.reset(new T(std::move(t))); + return *elem.get(); + } + + T* operator -> () { + CHECK(elem.get() != nullptr); + return elem.get(); + } + + const T* operator -> () const { + CHECK(elem.get() != nullptr); + return elem.get(); + } + + bool operator == (const T& t) const { + return elem.get() && *elem.get() == t; + } + + bool operator != (const T& t) const { + return !elem.get() || *elem.get() != t; + } + + operator bool() const { + return elem.get() != nullptr; + } + + T& operator * () { + CHECK(elem.get()); + return *elem.get(); + } + + const T& operator * () const { + CHECK(elem.get()); + return *elem.get(); + } + + private: + unique_ptr elem; +}; + +template +bool contains(const initializer_list& v, const Optional& elem) { + return elem && contains(vector(v), *elem); +} + + +template +class MustInitialize { + public: + MustInitialize(const MustInitialize& o) : elem(o.elem) { + CHECK(!elem.empty()) << "Element not initialized"; + } + + MustInitialize() {} + + T& operator = (const T& t) { + + if (!elem.empty()) + elem.pop(); + CHECK(elem.empty()); + elem.push(t); + return elem.front(); + } + + T* operator -> () { + CHECK(!elem.empty()); + return &elem.front(); + } + + const T* operator -> () const { + CHECK(!elem.empty()) << "Element not initialized"; + return &elem.front(); + } + + bool operator == (const T& t) const { + CHECK(!elem.empty()) << "Element not initialized"; + return elem.front() == t; + } + + bool operator != (const T& t) const { + CHECK(!elem.empty()) << "Element not initialized"; + return elem.front() != t; + } + + T& operator * () { + CHECK(!elem.empty()) << "Element not initialized"; + return elem.front(); + } + + const T& operator * () const { + CHECK(!elem.empty()) << "Element not initialized"; + return elem.front(); + } + + private: + queue elem; +}; + +template +vector concat(const vector>& vectors) { + vector ret; + for (const vector& v : vectors) + for (const T& t : v) + ret.push_back(t); + return ret; +} + +template +inline T& operator <<(T& d, Vec2 msg) { + return d << "(" << msg.x << "," << msg.y << ")"; +} + +inline std::istream& operator >>(std::istream& d, Vec2& msg) { + string in; + d >> in; + vector s = split(in.substr(1, in.size() - 2), ','); + CHECKEQ((int)s.size(), 2); + msg = Vec2(convertFromString(s[0]), convertFromString(s[1])); + return d; +} + +inline Debug& operator <<(Debug& d, Rectangle rect) { + return d << "(" << rect.getPX() << "," << rect.getPY() << ") (" << rect.getKX() << "," << rect.getKY() << ")"; +} + +template +Debug& operator<<(Debug& d, const Table& container){ + for (int i : Range(container.height())) { + d << i << ":"; + for (int j : Range(container.width())) + d << container[j][i] << ","; + d << '\n'; + } + return d; +} + +inline NoDebug& operator <<(NoDebug& d, Vec2 msg) { + return d; +} + +inline NoDebug& operator <<(NoDebug& d, Rectangle msg) { + return d; +} + +template +NoDebug& operator<<(NoDebug& d, const Table& container){ + return d; +} + +template +void removeIndex(vector& v, int index) { + v[index] = std::move(v.back()); + v.pop_back(); +} + +template +Optional findElement(vector& v, const T& element) { + for (int i : All(v)) + if (v[i] == element) + return i; + return Nothing(); +} + +template +Optional findElement(const vector& v, const T* element) { + for (int i : All(v)) + if (v[i] == element) + return i; + return Nothing(); +} + +template +void removeElement(vector& v, const T& element) { + auto ind = findElement(v, element); + CHECK(ind) << "Element not found"; + removeIndex(v, *ind); +} + +template +void removeElement(vector& v, const T* element) { + auto ind = findElement(v, element); + CHECK(ind) << "Element not found"; + removeIndex(v, *ind); +} + +template +T getOnlyElement(const vector& v) { + CHECK(v.size() == 1); + return v[0]; +} + +inline string addAParticle(const string& s) { + if (contains({'a', 'e', 'u', 'i', 'o'}, s[0])) + return string("an ") + s; + else + return string("a ") + s; +} + +inline string capitalFirst(string s) { + if (islower(s[0])) + s[0] = toupper(s[0]); + return s; +} + +string combine(const vector& adj); + +#endif diff --git a/view.cpp b/view.cpp new file mode 100644 index 000000000..23ec1ac94 --- /dev/null +++ b/view.cpp @@ -0,0 +1,21 @@ +#include "stdafx.h" + +using namespace std; + + +const static string prefix = "[title]"; + +string View::getTitlePrefix(const string& name) { + return prefix + name; +} + +bool View::hasTitlePrefix(const string& name) { + return name.size() >= prefix.size() && name.substr(0, prefix.size()) == prefix; +} + +string View::removeTitlePrefix(const string& name) { + CHECK(hasTitlePrefix(name)); + return name.substr(prefix.size()); +} + + diff --git a/view.h b/view.h new file mode 100644 index 000000000..7eea34034 --- /dev/null +++ b/view.h @@ -0,0 +1,151 @@ +#ifndef _VIEW_H +#define _VIEW_H + +#include +#include +#include + +#include "util.h" +#include "action.h" +#include "collective_action.h" +#include "debug.h" +#include "view_object.h" + +class CreatureView; +class Level; + +class View { + public: + + /** Does all the library specific init.*/ + virtual void initialize() = 0; + + /** Displays a splash screen in an active loop until \paramname{ready} is set to true in another thread.*/ + virtual void displaySplash(bool& ready) = 0; + + /** Shutdown routine.*/ + virtual void close() = 0; + + /** Reads the game state from \paramname{creatureView} and refreshes the display.*/ + virtual void refreshView(const CreatureView* creatureView) = 0; + + /** Scrolls back to the center of the view on next refresh.*/ + virtual void resetCenter() = 0; + + //@{ + /** Adds a new message to the box. The message is remembered between refreshes, + and is discarded when appropriate.*/ + virtual void addMessage(const string& message) = 0; + virtual void addImportantMessage(const string& message) = 0; + //@} + + /** Clears the message box.*/ + virtual void clearMessages() = 0; + + /** Reads the keyboard in a blocking manner. Used for turn-based game play.*/ + virtual Action getAction() = 0; + + /** Reads input in a non-blocking manner.*/ + virtual CollectiveAction getClick() = 0; + + /** Returns whether a travel interrupt key is pressed at a given moment.*/ + virtual bool travelInterrupt() = 0; + + /** Draws a window with some options for the player to choose. \paramname{index} indicates the highlighted item. + Returns Nothing() if the player cancelled the choice.*/ + virtual Optional chooseFromList(const string& title, const vector& options, int index = 0) = 0; + + /** Let's the player choose a direction from the main 8. Returns Nothing() if the player cancelled the choice.*/ + virtual Optional chooseDirection(const string& message) = 0; + + /** Asks the player a yer-or-no question.*/ + virtual bool yesOrNoPrompt(const string& message) = 0; + + /** Draws a window with some text. The text is formatted to fit the window.*/ + virtual void presentText(const string& title, const string& text) = 0; + + /** Draws a window with a list of items.*/ + virtual void presentList(const string& title, const vector& options, bool scrollDown = false) = 0; + + /** Let's the player choose a number. Returns Nothing() if the player cancelled the choice.*/ + virtual Optional getNumber(const string& title, int max) = 0; + + /** Draws an animation of an object between two locations on a map.*/ + virtual void animateObject(vector trajectory, ViewObject object) = 0; + + /** Returns the current real time in milliseconds. The clock is stopped on blocking keyboard input, + so it can be used to sync game time in real-time mode.*/ + virtual int getTimeMilli() = 0; + + /** Stops the real time clock.*/ + virtual void stopClock() = 0; + + /** Sets the real time clock.*/ + virtual void setTimeMilli(int) = 0; + + /** Continues the real time clock after it had been stopped.*/ + virtual void continueClock() = 0; + + /** Returns whether the real time clock is currently stopped.*/ + virtual bool isClockStopped() = 0; + + /** Returns a default View that additionally logs all player actions into a file.*/ + static View* createLoggingView(ofstream& of); + + /** Returns a default View that reads all player actions from a file instead of the keyboard.*/ + static View* createReplayView(ifstream& ifs); + + /** Marks a given menu item to be displayed as a title.*/ + static string getTitlePrefix(const string& name); + + /** Returns whether a menu item is marked as a title.*/ + static bool hasTitlePrefix(const string& name); + + /** Removes the title prefix from a menu item.*/ + static string removeTitlePrefix(const string& name); + + /** Represents all the game information displayed around the map window.*/ + class GameInfo { + public: + enum class InfoType { PLAYER, BAND} infoType; + + class BandInfo { + public: + int number; + string name; + string warning; + vector> buttons; + string monsterHeader; + vector creatures; + vector enemies; + map tasks; + int numGold = 0; + int activeButton = 0; + double time; + bool gatheringTeam = false; + vector team; + } bandInfo; + + class PlayerInfo { + public: + int speed; + int defense; + int attack; + int strength; + int dexterity; + bool bleeding; + double time; + int numGold; + string playerName; + vector adjectives; + string title; + string levelName; + string weaponName; + double elfStanding; + double dwarfStanding; + double goblinStanding; + } playerInfo; + } gameInfo; +}; + +#endif diff --git a/view_index.cpp b/view_index.cpp new file mode 100644 index 000000000..9529a9088 --- /dev/null +++ b/view_index.cpp @@ -0,0 +1,30 @@ +#include "stdafx.h" + +void ViewIndex::insert(const ViewObject& obj) { + objects.erase(obj.layer()); + objects.emplace(obj.layer(), obj); +} + +bool ViewIndex::hasObject(ViewLayer l) const { + return objects.count(l); +} + +ViewObject ViewIndex::getObject(ViewLayer l) { + TRY(return objects.at(l), "no object on layer " << (int)l); +} + +Optional ViewIndex::getTopObject(vector layers) { + for (auto it = allLayers.rbegin(); it != allLayers.rend(); ++it) + if (contains(layers, *it) && hasObject(*it)) + return getObject(*it); + return Nothing(); +} + +void ViewIndex::setHighlight(HighlightType h, double amount) { + highlight = {h, amount}; +} + +Optional ViewIndex::getHighlight() const { + return highlight; +} + diff --git a/view_index.h b/view_index.h new file mode 100644 index 000000000..ec306d080 --- /dev/null +++ b/view_index.h @@ -0,0 +1,27 @@ +#ifndef _VIEW_INDEX +#define _VIEW_INDEX + +#include "view_object.h" + +class ViewIndex { + public: + void insert(const ViewObject& obj); + bool hasObject(ViewLayer) const; + ViewObject getObject(ViewLayer); + Optional getTopObject(vector); + + void setHighlight(HighlightType, double amount = 1); + + struct HighlightInfo { + HighlightType type; + double amount; + }; + + Optional getHighlight() const; + + private: + unordered_map objects; + Optional highlight; +}; + +#endif diff --git a/view_object.cpp b/view_object.cpp new file mode 100644 index 000000000..ae6bd7c96 --- /dev/null +++ b/view_object.cpp @@ -0,0 +1,211 @@ +#include "stdafx.h" + +using namespace std; + +ViewObject::ViewObject(ViewId id, ViewLayer l, const string& d) : resource_id(id), viewLayer(l), description(d) { + if (islower(description[0])) + description[0] = toupper(description[0]); +} + +void ViewObject::setBleeding(double b) { + bleeding = b; +} + +void ViewObject::setHostile(bool s) { + hostile = s; +} + +void ViewObject::setBlind(bool s) { + blind = s; +} + +void ViewObject::setInvisible(bool s) { + invisible = s; +} + +bool ViewObject::isInvisible() const { + return invisible; +} + + +void ViewObject::setPlayer(bool s) { + player = s; +} + +bool ViewObject::isPlayer() const { + return player; +} + +void ViewObject::setHidden(bool s) { + hidden = s; +} + +bool ViewObject::isHidden() const { + return hidden; +} + +void ViewObject::setBurning(double s) { + burning = s; +} + +double ViewObject::getBurning() const { + return burning; +} + +double ViewObject::getBleeding() const { + return bleeding; +} + +void ViewObject::setHeight(double h) { + height = h; +} + +double ViewObject::getHeight() const { + return height; +} + +void ViewObject::setSizeIncrease(double s) { + sizeIncrease += s; +} + +double ViewObject::getSizeIncrease() const { + return sizeIncrease; +} + +string ViewObject::getBareDescription() const { + return description; +} + +string ViewObject::getDescription() const { + string mods = getBleeding() > 0 ? "wounded" : ""; + if (hostile) { + if (mods.size() > 0) + mods.append(", "); + mods.append("hostile"); + } + if (blind) { + if (mods.size() > 0) + mods.append(", "); + mods.append("blind"); + } + if (mods.size() > 0) + mods = " (" + mods + ")"; + return description + mods; +} + +ViewLayer ViewObject::layer() const { + return viewLayer; +} + + +static vector creatureIds { + ViewId::PLAYER, + ViewId::ELF, + ViewId::ELF_CHILD, + ViewId::ELF_LORD, + ViewId::ELVEN_SHOPKEEPER, + ViewId::DWARF, + ViewId::DWARF_BARON, + ViewId::DWARVEN_SHOPKEEPER, + ViewId::GREAT_GOBLIN, + ViewId::GOBLIN, + ViewId::BANDIT, + ViewId::ZOMBIE, + ViewId::VAMPIRE, + ViewId::MUMMY, + ViewId::MUMMY_LORD, + ViewId::HORSE, + ViewId::COW, + ViewId::PIG, + ViewId::SHEEP, + ViewId::JACKAL, + ViewId::DEER, + ViewId::BOAR, + ViewId::FOX, + ViewId::BEAR, + ViewId::WOLF, + ViewId::BAT, + ViewId::RAT, + ViewId::SNAKE, + ViewId::VULTURE, + ViewId::GNOME, + ViewId::VODNIK, + ViewId::LEPRECHAUN, + ViewId::KRAKEN, + ViewId::KRAKEN2, + ViewId::FIRE_SPHERE, + ViewId::NIGHTMARE, + ViewId::DEATH, + ViewId::SPECIAL_BEAST, + ViewId::SPECIAL_HUMANOID, + ViewId::CANIF_TREE, + ViewId::DECID_TREE, +}; + +static vector itemIds { + ViewId::BODY_PART, + ViewId::BONE, + ViewId::SWORD, + ViewId::ELVEN_SWORD, + ViewId::KNIFE, + ViewId::WAR_HAMMER, + ViewId::BATTLE_AXE, + ViewId::BOW, + ViewId::ARROW, + ViewId::SCROLL, + ViewId::STEEL_AMULET, + ViewId::COPPER_AMULET, + ViewId::CRYSTAL_AMULET, + ViewId::WOODEN_AMULET, + ViewId::AMBER_AMULET, + ViewId::BOOK, + ViewId::FIRST_AID, + ViewId::EFFERVESCENT_POTION, + ViewId::MURKY_POTION, + ViewId::SWIRLY_POTION, + ViewId::VIOLET_POTION, + ViewId::PUCE_POTION, + ViewId::SMOKY_POTION, + ViewId::FIZZY_POTION, + ViewId::MILKY_POTION, + ViewId::GOLD, + ViewId::LEATHER_ARMOR, + ViewId::LEATHER_HELM, + ViewId::CHAIN_ARMOR, + ViewId::IRON_HELM, + ViewId::BOULDER, + ViewId::ROCK, + ViewId::SLIMY_MUSHROOM, + ViewId::PINK_MUSHROOM, + ViewId::DOTTED_MUSHROOM, + ViewId::GLOWING_MUSHROOM, + ViewId::GREEN_MUSHROOM, + ViewId::BLACK_MUSHROOM +}; + +static bool hallu = false; + +void ViewObject::setHallu(bool b) { + hallu = b; +} + +ViewId ViewObject::id() const { + if (hallu) { + if (contains(creatureIds, resource_id)) + return creatureIds[Random.getRandom(creatureIds.size())]; + if (contains(itemIds, resource_id)) + return itemIds[Random.getRandom(itemIds.size())]; + } + return resource_id; +} + +const ViewObject& ViewObject::unknownMonster() { + static ViewObject ret(ViewId::UNKNOWN_MONSTER, ViewLayer::CREATURE, "Unknown creature"); + return ret; +} + +const ViewObject& ViewObject::empty() { + static ViewObject ret(ViewId::BORDER_GUARD, ViewLayer::FLOOR, ""); + return ret; +} + diff --git a/view_object.h b/view_object.h new file mode 100644 index 000000000..725681454 --- /dev/null +++ b/view_object.h @@ -0,0 +1,63 @@ +#ifndef _VIEW_OBJECT_H +#define _VIEW_OBJECT_H + +#include "debug.h" +#include "enums.h" + +class ViewObject { + public: + ViewObject(ViewId id, ViewLayer l, const string& description); + + ViewObject() { Debug(FATAL) << "Don't call this constructor"; } + + void setBleeding(double); + double getBleeding() const; + + void setHostile(bool); + void setBlind(bool); + void setInvisible(bool); + + void setPlayer(bool); + bool isPlayer() const; + + void setHidden(bool); + bool isHidden() const; + bool isInvisible() const; + + static void setHallu(bool); + + void setBurning(double); + double getBurning() const; + + void setHeight(double); + double getHeight() const; + + void setSizeIncrease(double); + double getSizeIncrease() const; + + string getDescription() const; + string getBareDescription() const; + + ViewLayer layer() const; + ViewId id() const; + + const static ViewObject& unknownMonster(); + const static ViewObject& empty(); + + private: + double bleeding = 0; + bool hostile = false; + bool blind = false; + bool invisible = false; + bool player = false; + ViewId resource_id; + ViewLayer viewLayer; + string description; + bool hidden = false; + double burning = false; + double height = 0; + double sizeIncrease = 0; +}; + + +#endif diff --git a/village_control.cpp b/village_control.cpp new file mode 100644 index 000000000..dc006350b --- /dev/null +++ b/village_control.cpp @@ -0,0 +1,184 @@ +#include "stdafx.h" + +VillageControl::VillageControl(const Collective* c, const Level* l, StairDirection dir, StairKey key) + : villain(c), level(l), direction(dir), stairKey(key){ + EventListener::addListener(this); +} + +VillageControl::~VillageControl() { + EventListener::removeListener(this); +} + +void VillageControl::addCreature(Creature* c, int attackTime) { + attackTimes.insert(make_pair(c, attackTime)); +} + +bool VillageControl::startedAttack(Creature* c) { + return attackTimes.count(c) && attackTimes.at(c) <= c->getTime(); +} + +void VillageControl::onKillEvent(const Creature* victim, const Creature* killer) { + if (attackTimes.count(victim)) { + attackTimes.erase(victim); + if (attackTimes.empty()) + messageBuffer.addMessage(MessageBuffer::important("You have conquered this land. " + "You've been playing KeeperRL alpha.")); + } +} + +class HumanVillageControl : public VillageControl { + public: + HumanVillageControl(const Collective* villain, const Location* location, StairDirection dir, StairKey key) + : VillageControl(villain, location->getLevel(), dir, key), villageLocation(location) {} + + virtual MoveInfo getMove(Creature* c) override { + CHECK(startedAttack(c)); + if (pathToDungeon.empty()) + genPathToDungeon(); + if (c->getLevel() == villain->getLevel()) { + if (Optional move = c->getMoveTowards(villain->getHeartPos())) + return {1.0, [this, move, c] () { + c->move(*move); + }}; + else { + for (Vec2 v : Vec2::directions8(true)) + if (c->canDestroy(v)) + return {1.0, [this, v, c] () { + c->destroy(v); + }}; + return NoMove; + } + } else { + Vec2 stairs = getOnlyElement(c->getLevel()->getLandingSquares(direction, stairKey)); + if (c->getPosition() == stairs) + return {1.0, [this, c] () { + c->applySquare(); + }}; + int& pathInd = lastPathLocation[c]; + CHECK(pathInd >= 0 && pathInd < pathToDungeon.size() - 1) + << "Wrong index " << pathInd << " " << (int)pathToDungeon.size(); + if (c->getPosition() == pathToDungeon[pathInd + 1]) + ++pathInd; + if (c->getPosition() == pathToDungeon[pathInd]) { + if (c->canMove(pathToDungeon[pathInd + 1] - pathToDungeon[pathInd])) + return {1.0, [=] () { + c->move(pathToDungeon[pathInd + 1] - pathToDungeon[pathInd]); + }}; + else + return NoMove; + } else + if (Optional move = c->getMoveTowards(pathToDungeon[pathInd])) + return {1.0, [=] () { + c->move(*move); + }}; + else + return NoMove; + } + } + + private: + + void genPathToDungeon() { + const Level* level = villageLocation->getLevel(); + for (Vec2 v : villageLocation->getBounds()) + if (level->getSquare(v)->getTravelDir().size() == 1) { + pathToDungeon.push_back(v); + } + CHECK(pathToDungeon.size() == 1) << "Couldn't find path beginning in village"; + pathToDungeon.push_back(pathToDungeon.back() + + getOnlyElement(level->getSquare(pathToDungeon.back())->getTravelDir())); + while (1) { + vector nextMove = level->getSquare(pathToDungeon.back())->getTravelDir(); + if (nextMove.size() == 1) + break; + CHECK(nextMove.size() == 2); + Vec2 next; + if (pathToDungeon.back() + nextMove[0] != pathToDungeon[pathToDungeon.size() - 2]) + next = nextMove[0]; + else + next = nextMove[1]; + pathToDungeon.push_back(pathToDungeon.back() + next); + } + } + + const Location* villageLocation; + vector pathToDungeon; + map lastPathLocation; +}; + +VillageControl* VillageControl::humanVillage(const Collective* villain, const Location* location, + StairDirection dir, StairKey key) { + return new HumanVillageControl(villain, location, dir, key); +} + + +class DwarfVillageControl : public VillageControl { + public: + DwarfVillageControl(const Collective* villain, const Level* level, StairDirection dir, StairKey key) + : VillageControl(villain, level, dir, key) {} + + virtual MoveInfo getMove(Creature* c) override { + CHECK(startedAttack(c)); + if (c->getLevel() == villain->getLevel()) { + if (Optional move = c->getMoveTowards(villain->getHeartPos())) + return {1.0, [this, move, c] () { + c->move(*move); + }}; + else { + for (Vec2 v : Vec2::directions8(true)) + if (c->canDestroy(v)) + return {1.0, [this, v, c] () { + c->destroy(v); + }}; + return NoMove; + } + } else { + Vec2 stairs = getOnlyElement(c->getLevel()->getLandingSquares(direction, stairKey)); + if (c->getPosition() == stairs) + return {1.0, [this, c] () { + c->applySquare(); + }}; + if (Optional move = c->getMoveTowards(stairs)) + return {1.0, [=] () { + c->move(*move); + }}; + else + return NoMove; + } + } + + private: + + void genPathToDungeon() { + const Level* level = villageLocation->getLevel(); + for (Vec2 v : villageLocation->getBounds()) + if (level->getSquare(v)->getTravelDir().size() == 1) { + pathToDungeon.push_back(v); + } + CHECK(pathToDungeon.size() == 1) << "Couldn't find path beginning in village"; + pathToDungeon.push_back(pathToDungeon.back() + + getOnlyElement(level->getSquare(pathToDungeon.back())->getTravelDir())); + while (1) { + vector nextMove = level->getSquare(pathToDungeon.back())->getTravelDir(); + if (nextMove.size() == 1) + break; + CHECK(nextMove.size() == 2); + Vec2 next; + if (pathToDungeon.back() + nextMove[0] != pathToDungeon[pathToDungeon.size() - 2]) + next = nextMove[0]; + else + next = nextMove[1]; + pathToDungeon.push_back(pathToDungeon.back() + next); + } + } + + const Location* villageLocation; + vector pathToDungeon; + map lastPathLocation; +}; + +VillageControl* VillageControl::dwarfVillage(const Collective* villain, const Level* level, + StairDirection dir, StairKey key) { + return new DwarfVillageControl(villain, level, dir, key); +} + diff --git a/village_control.h b/village_control.h new file mode 100644 index 000000000..1a7b32a13 --- /dev/null +++ b/village_control.h @@ -0,0 +1,27 @@ +#ifndef _VILLAGE_CONTROL_H +#define _VILLAGE_CONTROL_H + +class VillageControl : public EventListener { + public: + virtual ~VillageControl(); + void addCreature(Creature* c, int attackTime); + + virtual MoveInfo getMove(Creature* c) = 0; + bool startedAttack(Creature* c); + + virtual void onKillEvent(const Creature* victim, const Creature* killer) override; + + static VillageControl* humanVillage(const Collective* villain, const Location* villageLocation, + StairDirection dir, StairKey key); + static VillageControl* dwarfVillage(const Collective* villain, const Level*, + StairDirection dir, StairKey key); + protected: + VillageControl(const Collective* villain, const Level*, StairDirection, StairKey); + map attackTimes; + const Collective* villain; + const Level* level; + StairDirection direction; + StairKey stairKey; +}; + +#endif diff --git a/window_view.cpp b/window_view.cpp new file mode 100644 index 000000000..673e654df --- /dev/null +++ b/window_view.cpp @@ -0,0 +1,1364 @@ +#include "stdafx.h" + + +using sf::Color; +using sf::String; +using sf::RenderWindow; +using sf::VideoMode; +using sf::Text; +using sf::Font; +using sf::Event; +using sf::RectangleShape; +using sf::Vector2f; +using sf::Vector2u; +using sf::Image; +using sf::Sprite; +using sf::Texture; +using sf::Keyboard; + +using namespace std; + +Color white(255, 255, 255); +Color yellow(250, 255, 0); +Color lightBrown(210, 150, 0); +Color orangeBrown(250, 150, 0); +Color brown(240, 130, 0); +Color darkBrown(100, 60, 0); +Color lightGray(150, 150, 150); +Color gray(100, 100, 100); +Color almostGray(102, 102, 102); +Color darkGray(50, 50, 50); +Color almostDarkGray(60, 60, 60); +Color black(0, 0, 0); +Color almostWhite(200, 200, 200); +Color green(0, 255, 0); +Color lightGreen(100, 255, 100); +Color darkGreen(0, 150, 0); +Color red(255, 0, 0); +Color lightRed(255, 100, 100); +Color pink(255, 20, 147); +Color orange(255, 165, 0); +Color blue(0, 0, 255); +Color darkBlue(50, 50, 200); +Color lightBlue(100, 100, 255); +Color purple(160, 32, 240); +Color violet(120, 0, 255); +Color translucentBlack(0, 0, 0); + +View* View::createLoggingView(ofstream& of) { + return new LoggingView(of); +} + +View* View::createReplayView(ifstream& ifs) { + return new ReplayView(ifs); +} + +struct Tile { + Color color; + String text; + bool symFont = false; + int px = -1, py = -1; + Tile(sf::Uint32 ch, Color col, bool sym = false) : color(col), text(ch), symFont(sym) { + } + Tile(int x, int y) : px(x), py(y) {} +}; + +Tile getSpecialCreature(const ViewObject& obj, bool humanoid) { + RandomGen r; + r.init(std::hash()(obj.getBareDescription())); + string let = humanoid ? "WETYUIPLKJHFAXBM" : "qwetyupkjfaxbnm"; + char c; + if (contains(let, obj.getBareDescription()[0])) + c = obj.getBareDescription()[0]; + else + if (contains(let, tolower(obj.getBareDescription()[0]))) + c = tolower(obj.getBareDescription()[0]); + else + c = let[r.getRandom(let.size())]; + Color col(r.getRandom(80, 250), r.getRandom(80, 250), 0); + return Tile(c, col); +} + +Tile getTile(const ViewObject& obj) { + switch (obj.id()) { + case ViewId::PLAYER: return Tile('@', white); + case ViewId::UNKNOWN_MONSTER: return Tile('?', lightGreen); + case ViewId::SPECIAL_BEAST: return getSpecialCreature(obj, false); + case ViewId::SPECIAL_HUMANOID: return getSpecialCreature(obj, true); + case ViewId::ELF: return Tile('@', green); + case ViewId::ELF_CHILD: return Tile('@', lightGreen); + case ViewId::ELF_LORD: return Tile('@', darkGreen); + case ViewId::ELVEN_SHOPKEEPER: return Tile('@', lightBlue); + case ViewId::IMP: return Tile('i', lightBrown); + case ViewId::BILE_DEMON: return Tile('Q', orange); + case ViewId::HELL_HOUND: return Tile('d', purple); + case ViewId::CHICKEN: return Tile('c', yellow); + case ViewId::DWARF: return Tile('h', blue); + case ViewId::DWARF_BARON: return Tile('h', darkBlue); + case ViewId::DWARVEN_SHOPKEEPER: return Tile('h', lightBlue); + case ViewId::FLOOR: return Tile('.', white); + case ViewId::BRIDGE: return Tile('_', brown); + case ViewId::PATH: return Tile('.', lightGray); + case ViewId::SAND: return Tile('.', yellow); + case ViewId::GRASS: return Tile(0x1d0f0, green, true); + case ViewId::WALL: return Tile('#', lightGray); + case ViewId::MOUNTAIN: return Tile(0x25ee, darkGray, true); + case ViewId::GOLD_ORE: return Tile(L'⁂', yellow, true); + case ViewId::SNOW: return Tile(0x25ee, white, true); + case ViewId::HILL: return Tile(0x1d022, darkGreen, true); + case ViewId::WOOD_WALL: return Tile('#', darkBrown); + case ViewId::BLACK_WALL: return Tile('#', lightGray); + case ViewId::YELLOW_WALL: return Tile('#', yellow); + case ViewId::SECRETPASS: return Tile('#', lightGray); + case ViewId::DOWN_STAIRCASE: return Tile(0x2798, almostWhite, true); + case ViewId::UP_STAIRCASE: return Tile(0x279a, almostWhite, true); + case ViewId::GREAT_GOBLIN: return Tile('O', purple); + case ViewId::GOBLIN: return Tile('o', darkBlue); + case ViewId::BANDIT: return Tile('@', darkBlue); + case ViewId::KNIGHT: return Tile('@', white); + case ViewId::AVATAR: return Tile('@', blue); + case ViewId::ARCHER: return Tile('@', brown); + case ViewId::PESEANT: return Tile('@', green); + case ViewId::CHILD: return Tile('@', lightGreen); + case ViewId::ZOMBIE: return Tile('Z', green); + case ViewId::VAMPIRE: return Tile('V', darkGray); + case ViewId::MUMMY: return Tile('Z', yellow); + case ViewId::MUMMY_LORD: return Tile('Z', orange); + case ViewId::JACKAL: return Tile('d', lightBrown); + case ViewId::DEER: return Tile('R', darkBrown); + case ViewId::HORSE: return Tile('H', lightBrown); + case ViewId::COW: return Tile('C', white); + case ViewId::SHEEP: return Tile('s', white); + case ViewId::PIG: return Tile('p', yellow); + case ViewId::BOAR: return Tile('b', lightBrown); + case ViewId::FOX: return Tile('d', orangeBrown); + case ViewId::WOLF: return Tile('d', darkBlue); + case ViewId::VODNIK: return Tile('f', green); + case ViewId::KRAKEN: return Tile('S', darkGreen); + case ViewId::DEATH: return Tile('D', darkGray); + case ViewId::KRAKEN2: return Tile('S', green); + case ViewId::NIGHTMARE: return Tile('n', purple); + case ViewId::FIRE_SPHERE: return Tile('e', red); + case ViewId::BEAR: return Tile('N', brown); + case ViewId::BAT: return Tile('b', darkGray); + case ViewId::GNOME: return Tile('g', green); + case ViewId::LEPRECHAUN: return Tile('l', green); + case ViewId::RAT: return Tile('r', brown); + case ViewId::SNAKE: return Tile('s', yellow); + case ViewId::VULTURE: return Tile('v', darkGray); + case ViewId::BODY_PART: return Tile('%', red); + case ViewId::BONE: return Tile('%', white); + case ViewId::BUSH: return Tile('&', darkGreen); + case ViewId::DECID_TREE: return Tile(0x1f70d, darkGreen, true); + case ViewId::CANIF_TREE: return Tile(0x2663, darkGreen, true); + case ViewId::WATER: return Tile('~', lightBlue); + case ViewId::MAGMA: return Tile('~', red); + case ViewId::ABYSS: return Tile('~', darkGray); + case ViewId::DOOR: return Tile(0x1f062, brown, true); + case ViewId::SWORD: return Tile(')', lightGray); + case ViewId::ELVEN_SWORD: return Tile(')', gray); + case ViewId::KNIFE: return Tile(')', white); + case ViewId::WAR_HAMMER: return Tile(')', blue); + case ViewId::BATTLE_AXE: return Tile(')', green); + case ViewId::BOW: return Tile(')', brown); + case ViewId::ARROW: return Tile('\\', brown); + case ViewId::SCROLL: return Tile('?', white); + case ViewId::STEEL_AMULET: return Tile('\"', yellow); + case ViewId::COPPER_AMULET: return Tile('\"', yellow); + case ViewId::CRYSTAL_AMULET: return Tile('\"', yellow); + case ViewId::WOODEN_AMULET: return Tile('\"', yellow); + case ViewId::AMBER_AMULET: return Tile('\"', yellow); + case ViewId::BOOK: return Tile('+', yellow); + case ViewId::FIRST_AID: return Tile('+', red); + case ViewId::TRAP_ITEM: return Tile('+', yellow); + case ViewId::EFFERVESCENT_POTION: return Tile('!', lightRed); + case ViewId::MURKY_POTION: return Tile('!', blue); + case ViewId::SWIRLY_POTION: return Tile('!', yellow); + case ViewId::VIOLET_POTION: return Tile('!', violet); + case ViewId::PUCE_POTION: return Tile('!', darkBrown); + case ViewId::SMOKY_POTION: return Tile('!', lightGray); + case ViewId::FIZZY_POTION: return Tile('!', lightBlue); + case ViewId::MILKY_POTION: return Tile('!', white); + case ViewId::SLIMY_MUSHROOM: return Tile(0x22c6, darkGray, true); + case ViewId::PINK_MUSHROOM: return Tile(0x22c6, pink, true); + case ViewId::DOTTED_MUSHROOM: return Tile(0x22c6, green, true); + case ViewId::GLOWING_MUSHROOM: return Tile(0x22c6, lightBlue, true); + case ViewId::GREEN_MUSHROOM: return Tile(0x22c6, green, true); + case ViewId::BLACK_MUSHROOM: return Tile(0x22c6, darkGray, true); + case ViewId::FOUNTAIN: return Tile('0', lightBlue); + case ViewId::GOLD: return Tile('$', yellow); + case ViewId::CHEST: return Tile('=', brown); + case ViewId::COFFIN: return Tile(L'⚰', darkGray, true); + case ViewId::BOULDER: return Tile(L'●', lightGray, true); + case ViewId::UNARMED_BOULDER_TRAP: return Tile(L'○', lightGray, true); + case ViewId::PORTAL: return Tile(0x1d6af, lightGreen, true); + case ViewId::TRAP: return Tile(L'➹', yellow, true); + case ViewId::GAS_TRAP: return Tile(L'☠', green, true); + case ViewId::UNARMED_GAS_TRAP: return Tile(L'☠', lightGray, true); + case ViewId::ROCK: return Tile('*', lightGray); + case ViewId::BED: return Tile('=', white); + case ViewId::DUNGEON_HEART: return Tile(L'♥', white, true); + case ViewId::ALTAR: return Tile(L'Ω', white); + case ViewId::TORTURE_TABLE: return Tile('=', gray); + case ViewId::TRAINING_DUMMY: return Tile(L'‡', brown, true); + case ViewId::WORKSHOP: return Tile('&', lightBlue); + case ViewId::GRAVE: return Tile(0x2617, gray, true); + case ViewId::BARS: return Tile(L'⧻', lightBlue); + case ViewId::BORDER_GUARD: return Tile(' ', white); + case ViewId::LEATHER_ARMOR: return Tile('[', brown); + case ViewId::LEATHER_HELM: return Tile('[', brown); + case ViewId::CHAIN_ARMOR: return Tile('[', lightGray); + case ViewId::IRON_HELM: return Tile('[', lightGray); + case ViewId::DESTROYED_FURNITURE: return Tile('*', brown); + case ViewId::BURNT_FURNITURE: return Tile('*', darkGray); + case ViewId::FALLEN_TREE: return Tile('*', green); + case ViewId::BURNT_TREE: return Tile('*', darkGray); + case ViewId::GUARD_POST: return Tile(L'⚐', yellow, true); + } + Debug(FATAL) << "unhandled view id " << (int)obj.id(); + return Tile(' ', white); +} + +RenderWindow* display; +sf::View* sfView; + +int screenWidth; +int screenHeight; + +Font textFont; +int textSize = 20; +int smallTextSize = 12; + +Font tileFont; +Font symbolFont; + +Image mapBuffer; +Texture tiles; + +class Clock { + public: + + int getMillis() { + if (lastPause > -1) + return lastPause - pausedTime; + else + return clock.getElapsedTime().asMilliseconds() - pausedTime; + } + + void setMillis(int time) { + if (lastPause > -1) + pausedTime = lastPause - time; + else + pausedTime = clock.getElapsedTime().asMilliseconds() - time; + } + + void pause() { + if (lastPause == -1) + lastPause = clock.getElapsedTime().asMilliseconds(); + } + + void cont() { + if (lastPause > -1) { + pausedTime += clock.getElapsedTime().asMilliseconds() - lastPause; + lastPause = -1; + } + } + + bool isPaused() { + return lastPause > -1; + } + + private: + int pausedTime = 0; + int lastPause = -1; + sf::Clock clock; +}; + +static Clock myClock; + +int getTextWidth(const Font& font, int size, String s) { + Text t(s, font, size); + return t.getLocalBounds().width; +} + +void drawText(const Font& font, int size, Color color, int x, int y, String s, bool center = false) { + int ox = 0; + int oy = 0; + Text t(s, font, size); + if (center) { + sf::FloatRect bounds = t.getLocalBounds(); + ox -= bounds.left + bounds.width / 2; + //oy -= bounds.top + bounds.height / 2; + } + t.setPosition(x + ox, y + oy); + t.setColor(color); + display->draw(t); +} + +void drawText(Color color, int x, int y, string s, bool center = false, int size = textSize) { + std::basic_string utf32; + sf::Utf8::toUtf32(s.begin(), s.end(), std::back_inserter(utf32)); + drawText(textFont, size, color, x, y, utf32, center); +} + +void drawText(Color color, int x, int y, const char* c, bool center = false, int size = textSize) { + drawText(textFont, size, color, x, y, String(c), center); +} + +void drawImage(int px, int py, const Image& image) { + Texture t; + t.loadFromImage(image); + Sprite s(t); + s.setPosition(px, py); + display->draw(s); +} + +void drawSprite(int x, int y, int px, int py, int w, int h, const Texture& t) { + Sprite s(t, sf::IntRect(px, py, w, h)); + s.setPosition(x, y); + display->draw(s); +} + +void WindowView::initialize() { + display = new RenderWindow(VideoMode(1024, 600, 32), "KeeperRL"); + sfView = new sf::View(display->getDefaultView()); + screenHeight = display->getSize().y; + screenWidth = display->getSize().x; + + textFont.loadFromFile("coolvetica rg.ttf"); + tileFont.loadFromFile("coolvetica rg.ttf"); + symbolFont.loadFromFile("Symbola.ttf"); + + normalLayout = MapLayout::gridLayout(screenWidth, screenHeight, 16, 20, 0, 30, 255, 0, 1, allLayers); + tppLayout = MapLayout::tppLayout(screenWidth, screenHeight, 16, 20, 0, 30, 220, 85); + unzoomLayout = MapLayout::gridLayout(screenWidth, screenHeight, 8, 10, 0, 30, 220, 85, 1, + {ViewLayer::FLOOR, ViewLayer::CREATURE}); + worldLayout = MapLayout::worldLayout(screenWidth, screenHeight, 0, 80, 220, 75); + allLayouts.push_back(normalLayout); + allLayouts.push_back(tppLayout); + allLayouts.push_back(unzoomLayout); + allLayouts.push_back(worldLayout); + mapBuffer.create(600, 600); + mapLayout = normalLayout; + //Image tileImage; + //CHECK(tileImage.loadFromFile("tile_test.png")); + //tiles.loadFromImage(tileImage); +} + +void WindowView::displaySplash(bool& ready) { + Image splash; + CHECK(splash.loadFromFile("splash.png")); + drawImage((screenWidth - splash.getSize().x) / 2, (screenHeight - splash.getSize().y) / 2, splash); + drawText(white, screenWidth / 2, screenHeight - 60, "Loading...", true); + drawAndClearBuffer(); + while (!ready) { + sf::sleep(sf::milliseconds(30)); + Event event; + while (display->pollEvent(event)) { + if (event.type == Event::Resized) { + resize(event.size.width, event.size.height); + } + if (event.type == Event::GainedFocus || event.type == Event::Resized) { + drawImage((screenWidth - splash.getSize().x) / 2, (screenHeight - splash.getSize().y) / 2, splash); + drawText(white, screenWidth / 2, screenHeight - 60, "Loading...", true); + drawAndClearBuffer(); + } + } + } +} + +bool keypressed() { + Event event; + while (display->pollEvent(event)) + if (event.type == Event::KeyPressed) + return true; + return false; +} + +void WindowView::resize(int width, int height) { + screenWidth = width; + screenHeight = height; + for (MapLayout* layout : allLayouts) + layout->updateScreenSize(screenWidth, screenHeight); + display->setView(*(sfView = new sf::View(sf::FloatRect(0, 0, screenWidth, screenHeight)))); +} + +Optional WindowView::readkey() { + Event event; + while (1) { + bool wasPaused = false; + if (!myClock.isPaused()) { + myClock.pause(); + wasPaused = true; + } + display->waitEvent(event); + if (considerScrollEvent(event)) + return Nothing(); + if (wasPaused) + myClock.cont(); + Debug() << "Event " << event.type; + if (event.type == Event::KeyPressed) { + Event::KeyEvent ret(event.key); + while (display->pollEvent(event)); + return ret; + } + if (event.type == Event::Resized) { + resize(event.size.width, event.size.height); + return Nothing(); + } + if (event.type == Event::GainedFocus) + return Nothing(); + } +} + +void WindowView::close() { +} + +void drawFilledRectangle(const Rectangle& t, Color color, Optional outline = Nothing()) { + RectangleShape r(Vector2f(t.getW(), t.getH())); + r.setPosition(t.getPX(), t.getPY()); + r.setFillColor(color); + if (outline) { + r.setOutlineThickness(2); + r.setOutlineColor(*outline); + } + display->draw(r); +} + +void drawFilledRectangle(int px, int py, int kx, int ky, Color color, Optional outline = Nothing()) { + drawFilledRectangle(Rectangle(px, py, kx, ky), color, outline); +} + +static Color getColor(const ViewObject& object) { + if (object.isInvisible()) + return darkGray; + if (object.isHidden()) + return lightGray; + /* if (object.isBurning()) + return red;*/ + double bleeding = object.getBleeding(); + if (bleeding > 0) + bleeding = 0.5 + bleeding / 2; + bleeding = min(1., bleeding); + Color color = getTile(object).color; + return Color( + (1 - bleeding) * color.r + bleeding * 255, + (1 - bleeding) * color.g, + (1 - bleeding) * color.b); +} + +static Color getMemoryColor(const ViewObject& object) { + Color color = getTile(object).color; + float cf = 3.5; + float r = color.r, g = color.g, b = color.b; + return Color( + (cf * r + g + b) / (2 + cf), + (r + g * cf + b) / (2 + cf), + (r + g + b * cf) / (2 + cf)); +} + +int fireVar = 50; + +Color getFireColor() { + return Color(200 + Random.getRandom(-fireVar, fireVar), Random.getRandom(fireVar), Random.getRandom(fireVar), 150); +} + +void printStanding(int x, int y, double standing, const string& tribeName) { + standing = min(1., max(-1., standing)); + Color color = standing < 0 + ? Color(255, 255 * (1. + standing), 255 *(1. + standing)) + : Color(255 * (1. - standing), 255, 255 * (1. - standing)); + drawText(color, x, y, (standing >= 0 ? "friend of " : "enemy of ") + tribeName); +} + +Color getSpeedColor(int value) { + if (value > 100) + return Color(max(0, 255 - (value - 100) * 2), 255, max(0, 255 - (value - 100) * 2)); + else + return Color(255, max(0, 255 + (value - 100) * 4), max(0, 255 + (value - 100) * 4)); +} + +int topBarHeight = 50; +int rightBarWidth = 230; + +void WindowView::drawPlayerInfo() { + GameInfo::PlayerInfo& info = gameInfo.playerInfo; + string title = info.title; + if (!info.adjectives.empty() || !info.playerName.empty()) + title = " " + title; + for (int i : All(info.adjectives)) + title = string(i (info.time); + drawText(white, 10, line1, playerLine); + drawText(getSpeedColor(info.speed), 10, line2, speed); + drawText(white, 135, line2, attr + " " + turn); + drawText(white, 350, line1, info.levelName + " " + money); + drawText(white, 600, line1, wielding); + // printStanding(screenWidth - rightBarWidth, line0, info.elfStanding, "elves"); + // printStanding(screenWidth - rightBarWidth, line1, info.dwarfStanding, "dwarves"); + // printStanding(screenWidth - rightBarWidth, line2, info.goblinStanding, "goblins"); +} + +string getPlural(const string& a, const string&b, int num) { + if (num == 1) + return "1 " + a; + else + return convertToString(num) + " " + b; +} + +const int legendLineHeight = 30; +const int legendStartHeight = topBarHeight + 80; + +static map> getCreatureMap(vector creatures) { + map> creatureMap; + for (int i : All(creatures)) { + auto elem = creatures[i]; + if (!creatureMap.count(elem->getName())) { + creatureMap.insert(make_pair(elem->getName(), make_pair(elem->getViewObject(), 1))); + } else + ++creatureMap[elem->getName()].second; + } + return creatureMap; +} + +static void drawViewObject(const ViewObject& obj, int x, int y) { + Tile tile = getTile(obj); + drawText(tile.symFont ? symbolFont : textFont, 20, getColor(obj), x, y, tile.text, true); +} + +void WindowView::drawMinions(GameInfo::BandInfo& info) { + map> creatureMap = getCreatureMap(info.creatures); + map> enemyMap = getCreatureMap(info.enemies); + drawText(white, screenWidth - rightBarWidth, legendStartHeight, info.monsterHeader); + int cnt = 0; + int lineStart = legendStartHeight + 35; + for (auto elem : creatureMap){ + int height = lineStart + cnt * legendLineHeight; + drawViewObject(elem.second.first, screenWidth - rightBarWidth + 10, height); + Color col = (elem.first == chosenCreature) ? green : white; + drawText(col, screenWidth - rightBarWidth + 30, height, + convertToString(elem.second.second) + " " + elem.first); + creatureGroupButtons.emplace_back( + screenWidth - rightBarWidth, height, screenWidth - rightBarWidth + 150, height + legendLineHeight); + creatureNames.push_back(elem.first); + ++cnt; + } + + if (info.gatheringTeam && !info.team.empty()) { + drawText(white, screenWidth - rightBarWidth, lineStart + (cnt + 1) * legendLineHeight, + getPlural("monster", "monsters", info.team.size())); + ++cnt; + } + if (info.creatures.size() > 1 || info.gatheringTeam) { + int height = lineStart + (cnt + 1) * legendLineHeight; + drawText((info.gatheringTeam && info.team.empty()) ? green : white, screenWidth - rightBarWidth, height, + info.team.empty() ? "[gather team]" : "[command team]"); + int butWidth = 150; + teamButton = Rectangle(screenWidth - rightBarWidth, height, + screenWidth - rightBarWidth + butWidth, height + legendLineHeight); + if (info.gatheringTeam) { + drawText(white, screenWidth - rightBarWidth + butWidth, height, "[cancel]"); + cancelTeamButton = Rectangle(screenWidth - rightBarWidth + butWidth, height, + screenWidth - rightBarWidth + 230, height + legendLineHeight); + } + cnt += 2; + } + ++cnt; + if (!enemyMap.empty()) { + drawText(white, screenWidth - rightBarWidth, lineStart + (cnt + 1) * legendLineHeight, "Enemies:"); + for (auto elem : enemyMap){ + int height = lineStart + (cnt + 2) * legendLineHeight + 10; + drawViewObject(elem.second.first, screenWidth - rightBarWidth + 10, height); + drawText(white, screenWidth - rightBarWidth + 30, height, + convertToString(elem.second.second) + " " + elem.first); + ++cnt; + } + } + if (chosenCreature != "") { + if (!creatureMap.count(chosenCreature)) { + chosenCreature = ""; + } else { + int width = 220; + vector chosen; + for (const Creature* c : info.creatures) + if (c->getName() == chosenCreature) + chosen.push_back(c); + drawFilledRectangle(screenWidth - rightBarWidth - width - 20, lineStart, + screenWidth - rightBarWidth, legendStartHeight + 35 + (chosen.size() + 3) * legendLineHeight, black); + drawText(lightBlue, screenWidth - rightBarWidth - width - 10, lineStart, + info.gatheringTeam ? "Click to add to team:" : "Click to possess:"); + int cnt = 1; + for (const Creature* c : chosen) { + int height = lineStart + cnt * legendLineHeight; + drawViewObject(c->getViewObject(), screenWidth - rightBarWidth - width, height); + drawText(contains(info.team, c) ? green : white, screenWidth - rightBarWidth - width + 30, height, + "level: " + convertToString(c->getExpLevel()) + " " + info.tasks[c]); + creatureButtons.emplace_back(screenWidth - rightBarWidth - width, height, + screenWidth - rightBarWidth, height + legendLineHeight); + chosenCreatures.push_back(c); + ++cnt; + } + int height = lineStart + cnt * legendLineHeight + 10; + drawText(white, screenWidth - rightBarWidth - width, height, "[show description]"); + descriptionButton = Rectangle(screenWidth - rightBarWidth - width, height, + screenWidth - rightBarWidth, height + legendLineHeight); + } + } +} + +void WindowView::drawBuildings(GameInfo::BandInfo& info) { + for (int i : All(info.buttons)) { + string text = get<0>(info.buttons[i]); + int height = legendStartHeight + i * legendLineHeight; + drawViewObject(get<1>(info.buttons[i]), screenWidth - rightBarWidth, height); + drawText(i == info.activeButton ? green : (get<2>(info.buttons[i]) ? white : lightGray), + screenWidth - rightBarWidth + 30, height, text); + roomButtons.emplace_back(screenWidth - rightBarWidth, height, + screenWidth - rightBarWidth + 150, height + legendLineHeight); + } +} + +void WindowView::drawKeeperHelp() { + vector helpText { "use mouse to", "dig and build", "", "click on minion list", "to possess", + "", "heroes come from", "the up staircase", "", "[space] pause", "[z] zoom"}; + int cnt = 0; + for (string line : helpText) { + int height = legendStartHeight + cnt * legendLineHeight; + drawText(lightBlue, screenWidth - rightBarWidth, height, line); + cnt ++; + } +} + +void WindowView::drawBandInfo() { + GameInfo::BandInfo& info = gameInfo.bandInfo; + int line0 = screenHeight - 90; + int line1 = screenHeight - 65; + int line2 = screenHeight - 40; + drawFilledRectangle(0, line1 - 10, screenWidth - rightBarWidth, screenHeight, translucentBlack); + string playerLine = info.name; + drawText(white, 10, line1, playerLine); + if (!myClock.isPaused()) + drawText(red, 10, line2, info.warning); + else + drawText(red, 10, line2, "PAUSED"); + + drawText(white, 600, line2, "$ " + convertToString(info.numGold) + " T:" + convertToString(info.time)); + sf::Uint32 optionSyms[] = {L'⌂', 0x1f718, L'i', L'?'}; + optionButtons.clear(); + for (int i = 0; i < 4; ++i) { + int w = 45; + int line = topBarHeight - 20; + int h = 45; + int leftPos = screenWidth - rightBarWidth + 15; + drawText(i < 2 ? symbolFont : textFont, 35, i == int(collectiveOption) ? green : white, + leftPos + i * w, line, optionSyms[i], true); + optionButtons.emplace_back(leftPos + i * w - w / 2, line, + leftPos + (i + 1) * w - w / 2, line + h); + } + roomButtons.clear(); + int cnt = 0; + creatureGroupButtons.clear(); + creatureButtons.clear(); + creatureNames.clear(); + teamButton = Nothing(); + cancelTeamButton = Nothing(); + chosenCreatures.clear(); + descriptionButton = Nothing(); + if (collectiveOption != CollectiveOption::MINIONS) + chosenCreature = ""; + switch (collectiveOption) { + case CollectiveOption::MINIONS: drawMinions(info); break; + case CollectiveOption::BUILDINGS: drawBuildings(info); break; + case CollectiveOption::KEY_MAPPING: drawKeeperHelp(); break; + case CollectiveOption::LEGEND: break; + } +} + +void WindowView::refreshText() { + int lineHeight = 25; + int numMsg = 0; + for (int i : All(currentMessage)) + if (!currentMessage[i].empty()) + numMsg = i + 1; + drawFilledRectangle(0, 0, screenWidth, lineHeight * (numMsg + 1), translucentBlack); + for (int i : All(currentMessage)) + drawText(oldMessage ? gray : white, 10, 10 + lineHeight * i, currentMessage[i]); + switch (gameInfo.infoType) { + case GameInfo::InfoType::PLAYER: + drawPlayerInfo(); + break; + case GameInfo::InfoType::BAND: drawBandInfo(); break; + } +} + +static vector> keyMapping { + {"u", "leave minion"}, + {"i", "inventory"}, + {"e", "equipment"}, + {"pad 5 or enter", ""}, + {"", "pick up or"}, + {"", "interact"}, + {"", "with square"}, + {"d", "drop"}, + {"space", "wait"}, + {"a", "apply item"}, + {"t", "throw"}, + {"m", "history"}, + {"h", "hide"}, + {"c", "chat"}, + {"p", "pay"}, + {"ctrl + arrow", "travel"}, + {"alt + arrow", "fire"}, + {"z", "zoom"}, + {"shift + z", "world map"}, + {"F1", "legend"}, +}; + +/*Vec2 WindowView::projectOnBorders(Rectangle area, Vec2 pos) { + Vec2 center = Vec2((area.getPX() + area.getKX()) / 2, (area.getPY() + area.getKY()) / 2); + Vec2 d = pos - center; + if (d.x == 0) { + return Vec2(center.x, d.y > 0 ? area.getKY() - 1 : area.getPY()); + } + int cy = d.y * area.getW() / 2 / abs(d.x); + if (center.y + cy >= area.getPY() && center.y + cy < area.getKY()) + return Vec2(d.x > 0 ? area.getKX() - 1 : area.getPX(), center.y + cy); + int cx = d.x * area.getH() / 2 / abs(d.y); + CHECK(center.x + cx >= area.getPX() && center.x + cx < area.getKX()); + return Vec2(center.x + cx, d.y > 0 ? area.getKY() - 1: area.getPY()); +}*/ + +void WindowView::drawObjectAbs(int x, int y, const ViewObject& object, Color color, int sizeX, int sizeY) { + Tile tile = getTile(object); + if (tile.px >= 0) { + drawSprite(x, y, tile.px * 32, tile.py * 32, 32, 32, tiles); + } + else { + if (object.isPlayer()) { + drawFilledRectangle(x, y, x + sizeX, y + sizeY, gray); + int w = 1; + drawFilledRectangle(x + w, y + w, x + sizeX - w, y + sizeY - w, black); + } + drawText(tile.symFont ? symbolFont : tileFont, sizeY + object.getSizeIncrease(), color, + x + sizeX / 2, y - 3 - object.getSizeIncrease(), tile.text, true); + if (object.getBurning() > 0) { + drawText(symbolFont, sizeY, getFireColor(), + x + sizeX / 2, y + sizeY - 3, L'ѡ', true); + if (object.getBurning() > 0.5) + drawText(symbolFont, sizeY, getFireColor(), + x + sizeX / 2, y + sizeY - 3, L'Ѡ', true); + } + } +} + +unordered_map objects; +const MapMemory* lastMemory = nullptr; +unordered_map marked; + +Color getHighlightColor(ViewIndex::HighlightInfo info) { + switch (info.type) { + case HighlightType::BUILD: return yellow; + case HighlightType::POISON_GAS: return Color(0, info.amount * 255, 0); + } + Debug(FATAL) << "pokpok"; + return black; +} + +void WindowView::resetCenter() { + center = Vec2(0, 0); +} + +void WindowView::refreshView(const CreatureView* collective) { + const Level* level = collective->getLevel(); + collective->refreshGameInfo(gameInfo); + objects.clear(); + marked.clear(); + if (center == Vec2(0, 0) || collective->staticPosition()) + center = collective->getPosition().mult(Vec2(mapLayout->squareWidth(Vec2(0, 0)), + mapLayout->squareHeight(Vec2(0, 0)))); + Vec2 movePos = center - mouseOffset; + movePos.x = max(movePos.x, 0); + movePos.x = min(movePos.x, int(collective->getLevel()->getBounds().getKX() * mapLayout->squareWidth(Vec2(0, 0)))); + movePos.y = max(movePos.y, 0); + movePos.y = min(movePos.y, int(collective->getLevel()->getBounds().getKY() * mapLayout->squareHeight(Vec2(0, 0)))); + mapLayout->updatePlayerPos(movePos); + for (Vec2 pos : mapLayout->getAllTiles()) + if (level->inBounds(pos)) { + ViewIndex index = collective->getViewIndex(pos); + if (index.getHighlight()) + marked[pos] = getHighlightColor(*index.getHighlight()); + Optional obj = index.getTopObject(mapLayout->getLayers()); + if (obj) + objects.insert(std::make_pair(pos, *obj)); + } + // for (const Creature* c : player->getVisibleCreatures()) +// if (!projectOnScreen(c->getPosition()).inRectangle(mapWindowMargin)) +// objects.insert(std::make_pair(c->getPosition(), c->getViewObject())); + lastMemory = &collective->getMemory(level); + refreshScreen(); +} + +void WindowView::animateObject(vector trajectory, ViewObject object) { + for (Vec2 pos : trajectory) { + Optional prev; + if (objects.count(pos)) + prev = objects.at(pos); + objects.erase(pos); + objects.insert({pos, object}); + refreshScreen(); + // sf::sleep(sf::milliseconds(30)); + objects.erase(pos); + if (prev) + objects.insert({pos, *prev}); + } + objects.erase(trajectory.back()); + objects.insert({trajectory.back(), object}); +} + +void WindowView::drawMap() { + if (!lastMemory) + return; + + Rectangle mapWindow = mapLayout->getBounds(); + drawFilledRectangle(mapWindow, black); + bool pixelView = false; + for (Vec2 wpos : mapLayout->getAllTiles()) { + int sizeX = mapLayout->squareWidth(wpos); + int sizeY = mapLayout->squareHeight(wpos); + Vec2 pos = mapLayout->projectOnScreen(wpos, 0); + if (marked.count(wpos)) + drawFilledRectangle(pos.x, pos.y, pos.x + sizeX, pos.y + sizeY, marked.at(wpos)); + if (lastMemory->hasViewIndex(wpos) && !objects.count(wpos)) { + Optional object = lastMemory->getViewIndex(wpos).getTopObject(mapLayout->getLayers()); + if (object) { + if (sizeX > 1) + drawObjectAbs(pos.x, pos.y, *object, getMemoryColor(*object), sizeX, sizeY); + else { + mapBuffer.setPixel(pos.x, pos.y, getColor(*object)); + pixelView = true; + } + } + } + } + map objIndex; + for (auto elem : objects) { + objIndex.insert(std::make_pair(elem.second.getDescription(), elem.second)); + Vec2 wpos = elem.first; + int sizeX = mapLayout->squareWidth(wpos); + int sizeY = mapLayout->squareHeight(wpos); + Vec2 pos = mapLayout->projectOnScreen(wpos, elem.second.getHeight()); + if (sizeX > 1) + drawObjectAbs(pos.x, pos.y, + elem.second, getColor(elem.second), sizeX, sizeY); + else { + mapBuffer.setPixel(pos.x, pos.y, getColor(elem.second)); + pixelView = true; + } + // } + /* else if (elem.second.layer() == ViewLayer::CREATURE && mapView->font != nullptr) { + Vec2 proj = projectOnBorders(mapWindow, pos); + drawObjectAbs( + mapView->mapX + proj.x * mapView->squareWidth, + mapView->mapY + proj.y * mapView->squareHeight, + elem.second, getColor(elem.second), &smallFont, &smallSymbolFont); + }*/ + } + if (pixelView) + drawImage(mapLayout->getBounds().getPX(), mapLayout->getBounds().getPY(), mapBuffer); + int rightPos = screenWidth - rightBarWidth; + drawFilledRectangle(rightPos - 10, topBarHeight - 10, screenWidth, screenHeight, translucentBlack); + if (gameInfo.infoType == GameInfo::InfoType::PLAYER || collectiveOption == CollectiveOption::LEGEND) { + int cnt = 0; + if (legendOption == LegendOption::OBJECTS) { + for (auto elem : objIndex) { + drawViewObject(elem.second, rightPos, legendStartHeight + cnt * 25); + drawText(white, rightPos + 30, legendStartHeight + cnt * 25, elem.first); + ++cnt; + } + string f1text; + if (gameInfo.infoType == GameInfo::InfoType::PLAYER) + f1text = "F1 for help"; + drawText(lightBlue, rightPos, legendStartHeight + cnt * 25 + 25, f1text); + } else { + int i = 0; + for (auto elem : keyMapping) { + if (elem.first.size() > 0) + drawText(lightBlue, rightPos - 5, legendStartHeight + i * 21, "[" + elem.first + "]"); + drawText(lightBlue, rightPos + 110, legendStartHeight + i * 21, elem.second); + ++i; + } + } + } + refreshText(); + +//} +} + +void WindowView::refreshScreen(bool flipBuffer) { + drawMap(); + if (flipBuffer) + drawAndClearBuffer(); +} + +int indexHeight(const vector& options, int index) { + CHECK(index < options.size() && index >= 0); + int tmp = 0; + for (int i : All(options)) + if (!View::hasTitlePrefix(options[i]) && tmp++ == index) + return i; + Debug(FATAL) << "Bad index " << options << " " << index; + return -1; +} + +Optional WindowView::chooseDirection(const string& message) { + showMessage(message); + refreshScreen(); + do { + auto key = readkey(); + if (key) + switch (key->code) { + case Keyboard::Up: + case Keyboard::Numpad8: showMessage(""); refreshScreen(); return Vec2(0, -1); + case Keyboard::Numpad9: showMessage(""); refreshScreen(); return Vec2(1, -1); + case Keyboard::Right: + case Keyboard::Numpad6: showMessage(""); refreshScreen(); return Vec2(1, 0); + case Keyboard::Numpad3: showMessage(""); refreshScreen(); return Vec2(1, 1); + case Keyboard::Down: + case Keyboard::Numpad2: showMessage(""); refreshScreen(); return Vec2(0, 1); + case Keyboard::Numpad1: showMessage(""); refreshScreen(); return Vec2(-1, 1); + case Keyboard::Left: + case Keyboard::Numpad4: showMessage(""); refreshScreen(); return Vec2(-1, 0); + case Keyboard::Numpad7: showMessage(""); refreshScreen(); return Vec2(-1, -1); + case Keyboard::Escape: showMessage(""); refreshScreen(); return Nothing(); + default: break; + } + } while (1); +} + +void WindowView::clearMessageBox() { + messageInd = 0; + oldMessage = false; + for (int i : All(currentMessage)) + currentMessage[i].clear(); +} + + +void WindowView::showMessage(const string& message) { + messageInd = 0; + oldMessage = false; + for (int i : All(currentMessage)) + currentMessage[i].clear(); + currentMessage[0] = message; +} + +bool WindowView::yesOrNoPrompt(const string& message) { + showMessage(message + " (y/n)"); + refreshScreen(); + do { + auto key = readkey(); + if (key) + switch (key->code) { + case Keyboard::Y: showMessage(""); refreshScreen(); return true; + case Keyboard::Escape: + case Keyboard::Space: + case Keyboard::N: showMessage(""); refreshScreen(); return false; + default: break; + } + } while (1); +} + +Optional WindowView::getNumber(const string& title, int max) { + vector options; + for (int i : Range(1, max + 1)) + options.push_back(convertToString(i)); + Optional res = WindowView::chooseFromList(title, options); + if (!res) + return Nothing(); + else + return 1 + *res; +} + +int getMaxLines(); + +Optional WindowView::chooseFromList(const string& title, const vector& options, int index) { + if (options.size() == 0) + return Nothing(); + int numLines = min((int) options.size(), getMaxLines()); + int count = 0; + for (string s : options) + if (!hasTitlePrefix(s)) + ++count; + while (1) { + numLines = min((int) options.size(), getMaxLines()); + int height = indexHeight(options, index); + int cutoff = min(max(0, height - numLines / 2), (int) options.size() - numLines); + int itemsCutoff = 0; + for (int i : Range(cutoff)) + if (!hasTitlePrefix(options[i])) + ++itemsCutoff; + vector window = getPrefix(options, cutoff , numLines); + drawList(title, window, index - itemsCutoff); + auto key = readkey(); + if (key) + switch (key->code) { + case Keyboard::PageDown: index += 10; if (index >= count) index = count - 1; break; + case Keyboard::PageUp: index -= 10; if (index < 0) index = 0; break; + case Keyboard::Numpad8: + case Keyboard::Up: if (index > 0) --index; break; + case Keyboard::Numpad2: + case Keyboard::Down: if (index < count - 1) ++index; break; + case Keyboard::Numpad5: + case Keyboard::Return : clearMessageBox(); /*showMessage("");*/ return index; + case Keyboard::Escape : clearMessageBox();/* showMessage("");*/ return Nothing(); + default: break; + } + } + Debug(FATAL) << "Very bad"; + return 123; +} + +void WindowView::presentText(const string& title, const string& text) { + int maxWidth = 80; + vector rows; + for (string line : split(text, '\n')) { + rows.push_back(""); + for (string word : split(line, ' ')) + if (rows.back().size() + word.size() + 1 <= maxWidth) + rows.back().append((rows.back().size() > 0 ? " " : "") + word); + else + rows.push_back(word); + } + presentList(title, rows, false); +} + +void WindowView::presentList(const string& title, const vector& options, bool scrollDown) { + int numLines = min((int) options.size(), getMaxLines()); + if (numLines == 0) + return; + int index = scrollDown ? options.size() - numLines : 0; + while (1) { + numLines = min((int) options.size(), getMaxLines()); + vector window = getPrefix(options, index, numLines); + drawList(title, window, -1); + auto key = readkey(); + if (key) + switch (key->code) { + case Keyboard::Numpad8: + case Keyboard::Up: if (index > 0) --index; break; + case Keyboard::Numpad2: + case Keyboard::Down: if (index < options.size() - numLines) ++index; break; + case Keyboard::Return : refreshScreen(); return; + case Keyboard::Escape : refreshScreen(); return; + default: break; + } + } + refreshScreen(); +} + +static int yMargin = 10; +static int itemYMargin = 20; +static int itemSpacing = 25; +static int ySpacing = 100; + +int getMaxLines() { + return (screenHeight - ySpacing - ySpacing - 2 * yMargin - itemSpacing - itemYMargin) / itemSpacing; +} + +void WindowView::drawList(const string& title, const vector& options, int hightlight) { + int xMargin = 10; + int itemXMargin = 30; + int border = 2; + int h = 0; + int width = 800; + int xSpacing = (screenWidth - width) / 2; + refreshScreen(false); + if (hightlight > -1) + h = indexHeight(options, hightlight); + //drawFilledRectangle(xSpacing, ySpacing, screenWidth - xSpacing, screenHeight - ySpacing, white); + drawFilledRectangle(xSpacing, ySpacing, xSpacing + width, screenHeight - ySpacing, translucentBlack, white); + drawText(white, xSpacing + xMargin, ySpacing + yMargin, title); + if (hightlight > -1) + drawFilledRectangle(xSpacing + xMargin, ySpacing + yMargin + (h + 1) * itemSpacing + itemYMargin, + width + xSpacing - xMargin, ySpacing + yMargin + (h + 2) * itemSpacing + itemYMargin -1, darkGreen); + for (int i : All(options)) { + if (!hasTitlePrefix(options[i])) + drawText(white, xSpacing + xMargin + itemXMargin, ySpacing + yMargin + (i + 1) * itemSpacing + itemYMargin, options[i]); + else + drawText(yellow, xSpacing + xMargin, ySpacing + yMargin + (i + 1) * itemSpacing + itemYMargin, removeTitlePrefix(options[i])); + + } + drawAndClearBuffer(); +} + +void WindowView::drawAndClearBuffer() { + display->display(); + display->clear(black); +} + + + +void WindowView::addImportantMessage(const string& message) { + presentText("Important!", message); +} + +void WindowView::clearMessages() { + showMessage(""); +} + +void WindowView::addMessage(const string& message) { + if (oldMessage) + showMessage(""); + if (currentMessage[messageInd].size() + message.size() + 1 > maxMsgLength && + messageInd == currentMessage.size() - 1) { + currentMessage[messageInd] += " (more)"; + refreshScreen(); + while (1) { + auto key = readkey(); + if (key && (key->code == Keyboard::Space || key->code == Keyboard::Return)) + break; + } + showMessage(message); + } else { + if (currentMessage[messageInd].size() + message.size() + 1 > maxMsgLength) + ++messageInd; + currentMessage[messageInd] += (currentMessage[messageInd].size() > 0 ? " " : "") + message; + refreshScreen(); + } +} + +void WindowView::unzoom(bool pixel, bool tpp) { + if (pixel && mapLayout != worldLayout) + mapLayout = worldLayout; + /* else if (tpp && mapLayout != tppLayout) + mapLayout = tppLayout;*/ + else if (mapLayout != normalLayout) + mapLayout = normalLayout; + else + mapLayout = unzoomLayout; + refreshScreen(); +} + +bool WindowView::travelInterrupt() { + return keypressed(); +} + +ActionId getDirActionId(const Event::KeyEvent& key) { + if (key.control) + return ActionId::TRAVEL; + if (key.alt) + return ActionId::FIRE; + else + return ActionId::MOVE; +} + + +int WindowView::getTimeMilli() { + return myClock.getMillis(); +} + +void WindowView::setTimeMilli(int time) { + myClock.setMillis(time); +} + +void WindowView::stopClock() { + myClock.pause(); +} + +void WindowView::continueClock() { + myClock.cont(); +} + +bool WindowView::isClockStopped() { + return myClock.isPaused(); +} + +bool pressed = false; +bool rightPressed = false; + +bool WindowView::considerScrollEvent(sf::Event& event) { + switch (event.type) { + case Event::MouseButtonReleased : + if (event.mouseButton.button == sf::Mouse::Right) { + center -= mouseOffset; + mouseOffset = Vec2(0, 0); + rightPressed = false; + return true; + } + break; + case Event::MouseMoved: + if (rightPressed) { + mouseOffset = Vec2(event.mouseMove.x, event.mouseMove.y) - lastMousePos; + return true; + } + break; + case Event::MouseButtonPressed: + if (event.mouseButton.button == sf::Mouse::Right) { + rightPressed = true; + lastMousePos = Vec2(event.mouseButton.x, event.mouseButton.y); + return true; + } + break; + default: + break; + } + return false; +} + +Vec2 lastGoTo(-1, -1); +CollectiveAction WindowView::getClick() { + Event event; + while (display->pollEvent(event)) { + considerScrollEvent(event); + switch (event.type) { + case Event::KeyPressed: + switch (event.key.code) { + case Keyboard::Z: + unzoom(false, false); + center = Vec2(0, 0); + return CollectiveAction(CollectiveAction::IDLE); + case Keyboard::Space: + if (!myClock.isPaused()) + myClock.pause(); + else + myClock.cont(); + return CollectiveAction(CollectiveAction::IDLE); + case Keyboard::Escape: + if (yesOrNoPrompt("Are you sure you want to quit?")) + exit(0); + break; + /* case Keyboard::F1: + collectiveOption = CollectiveOption((1 + int(collectiveOption)) % 4); + break;*/ + default: + break; + } + break; + case Event::MouseButtonReleased : + if (event.mouseButton.button == sf::Mouse::Left) { + pressed = false; + return CollectiveAction(CollectiveAction::BUTTON_RELEASE); + } + break; + case Event::MouseMoved: + if (pressed) { + Vec2 goTo = mapLayout->projectOnMap(Vec2(event.mouseMove.x, event.mouseMove.y)); + if (goTo != lastGoTo ) { + return CollectiveAction(CollectiveAction::GO_TO, goTo); + lastGoTo = goTo; + } else + continue; + } + break; + case Event::MouseButtonPressed: { + CollectiveAction::Type t; + Vec2 clickPos(event.mouseButton.x, event.mouseButton.y); + if (event.mouseButton.button == sf::Mouse::Right) + chosenCreature = ""; + if (event.mouseButton.button == sf::Mouse::Left) { + if (teamButton && clickPos.inRectangle(*teamButton)) + return CollectiveAction(CollectiveAction::GATHER_TEAM); + if (cancelTeamButton && clickPos.inRectangle(*cancelTeamButton)) { + chosenCreature = ""; + return CollectiveAction(CollectiveAction::CANCEL_TEAM); + } + for (int i : All(optionButtons)) + if (clickPos.inRectangle(optionButtons[i])) + collectiveOption = (CollectiveOption) i; + for (int i : All(roomButtons)) + if (clickPos.inRectangle(roomButtons[i])) { + chosenCreature = ""; + return CollectiveAction(CollectiveAction::ROOM_BUTTON, i); + } + for (int i : All(creatureGroupButtons)) + if (clickPos.inRectangle(creatureGroupButtons[i])) { + if (chosenCreature == creatureNames[i]) + chosenCreature = ""; + else + chosenCreature = creatureNames[i]; + return CollectiveAction(CollectiveAction::IDLE); + } + for (int i : All(creatureButtons)) + if (clickPos.inRectangle(creatureButtons[i])) { + return CollectiveAction(CollectiveAction::CREATURE_BUTTON, chosenCreatures[i]); + } + if (descriptionButton && clickPos.inRectangle(*descriptionButton)) { + return CollectiveAction(CollectiveAction::CREATURE_DESCRIPTION, chosenCreatures[0]); + } + pressed = true; + chosenCreature = ""; + t = CollectiveAction::GO_TO; + return CollectiveAction(t, mapLayout->projectOnMap(clickPos)); + } + } + break; + case Event::Resized: + resize(event.size.width, event.size.height); + return CollectiveAction(CollectiveAction::IDLE); + default: break; + } + } + return CollectiveAction(CollectiveAction::IDLE); +} + +Action WindowView::getAction() { + while (1) { + auto key = readkey(); + oldMessage = true; + if (!key) + return Action(ActionId::IDLE); + auto optionalAction = mapLayout->overrideAction(*key); + if (optionalAction) + return *optionalAction; + switch (key->code) { + case Keyboard::Escape : if (yesOrNoPrompt("Are you sure you want to quit?")) exit(0); break; + case Keyboard::Z: unzoom(key->shift, key->control); return Action(ActionId::IDLE); + case Keyboard::F1: legendOption = (LegendOption)(1 - (int)legendOption); return Action(ActionId::IDLE); + case Keyboard::Up: + case Keyboard::Numpad8: return Action(getDirActionId(*key), Vec2(0, -1)); + case Keyboard::Numpad9: return Action(getDirActionId(*key), Vec2(1, -1)); + case Keyboard::Right: + case Keyboard::Numpad6: return Action(getDirActionId(*key), Vec2(1, 0)); + case Keyboard::Numpad3: return Action(getDirActionId(*key), Vec2(1, 1)); + case Keyboard::Down: + case Keyboard::Numpad2: return Action(getDirActionId(*key), Vec2(0, 1)); + case Keyboard::Numpad1: return Action(getDirActionId(*key), Vec2(-1, 1)); + case Keyboard::Left: + case Keyboard::Numpad4: return Action(getDirActionId(*key), Vec2(-1, 0)); + case Keyboard::Numpad7: return Action(getDirActionId(*key), Vec2(-1, -1)); + case Keyboard::Return: + case Keyboard::Numpad5: if (key->shift) return Action(ActionId::EXT_PICK_UP); + else return Action(ActionId::PICK_UP); + case Keyboard::I: return Action(ActionId::SHOW_INVENTORY); + case Keyboard::D: return Action(key->shift ? ActionId::EXT_DROP : ActionId::DROP); + case Keyboard::Space: + case Keyboard::Period: return Action(ActionId::WAIT); + case Keyboard::A: return Action(ActionId::APPLY_ITEM); + case Keyboard::E: return Action(ActionId::EQUIPMENT); + case Keyboard::T: return Action(ActionId::THROW); + case Keyboard::M: return Action(ActionId::SHOW_HISTORY); + case Keyboard::H: return Action(ActionId::HIDE); + case Keyboard::P: return Action(ActionId::PAY_DEBT); + case Keyboard::C: return Action(ActionId::CHAT); + case Keyboard::U: return Action(ActionId::UNPOSSESS); + default: break; + } + } +} + diff --git a/window_view.h b/window_view.h new file mode 100644 index 000000000..2f44f575e --- /dev/null +++ b/window_view.h @@ -0,0 +1,114 @@ +#ifndef _WINDOW_VIEW +#define _WINDOW_VIEW + +#include + +#include "util.h" +#include "view.h" +#include "action.h" +#include "map_layout.h" + + +/** See view.h for documentation.*/ +class WindowView: public View { + public: + + virtual void initialize() override; + virtual void displaySplash(bool& ready) override; + + virtual void close() override; + + virtual void addMessage(const string& message) override; + virtual void addImportantMessage(const string& message) override; + virtual void clearMessages() override; + virtual void refreshView(const CreatureView*) override; + virtual void resetCenter() override; + virtual Optional chooseFromList(const string& title, const vector& options, int index = 0) override; + virtual Optional chooseDirection(const string& message) override; + virtual bool yesOrNoPrompt(const string& message) override; + virtual void animateObject(vector trajectory, ViewObject object) override; + + virtual void presentText(const string& title, const string& text) override; + virtual void presentList(const string& title, const vector& options, bool scrollDown) override; + virtual Optional getNumber(const string& title, int max) override; + + virtual Action getAction() override; + virtual CollectiveAction getClick() override; + virtual bool travelInterrupt() override; + virtual int getTimeMilli() override; + virtual void setTimeMilli(int) override; + virtual void stopClock() override; + virtual bool isClockStopped() override; + virtual void continueClock() override; + + static Vec2 projectOnBorders(Rectangle area, Vec2 pos); + private: + + void drawMap(); + void drawPlayerInfo(); + void drawBandInfo(); + void drawBuildings(GameInfo::BandInfo& info); + void drawMinions(GameInfo::BandInfo& info); + void drawKeeperHelp(); + Optional readkey(); + + void showMessage(const string& message); + void drawList(const string& title, const vector& options, int hightlight); + void refreshScreen(bool flipBuffer = true); + void refreshText(); + void drawAndClearBuffer(); + void drawObjectAbs(int x, int y, const ViewObject& object, sf::Color color, int sizeX, int sizeY); + void darkenObjectAbs(int x, int y); + void clearMessageBox(); + void unzoom(bool, bool); + void resize(int, int); + + bool considerScrollEvent(sf::Event&); + + int messageInd = 0; + const static unsigned int maxMsgLength = 90; + vector currentMessage = vector(3, ""); + bool oldMessage = false; + + enum class CollectiveOption { + BUILDINGS, + MINIONS, + LEGEND, + KEY_MAPPING, + }; + + CollectiveOption collectiveOption = CollectiveOption::BUILDINGS; + + enum class LegendOption { + OBJECTS, + KEY_MAPPING, + }; + + LegendOption legendOption = LegendOption::OBJECTS; + + MapLayout* mapLayout; + MapLayout* normalLayout; + MapLayout* tppLayout; + MapLayout* unzoomLayout; + MapLayout* worldLayout; + vector allLayouts; + + vector optionButtons; + vector roomButtons; + vector creatureGroupButtons; + vector creatureButtons; + Optional descriptionButton; + Optional teamButton; + Optional cancelTeamButton; + Optional impButton; + vector creatureNames; + string chosenCreature; + vector chosenCreatures; + + Vec2 lastMousePos; + Vec2 mouseOffset; + Vec2 center; +}; + + +#endif diff --git a/world.txt b/world.txt new file mode 100644 index 000000000..32c8de112 --- /dev/null +++ b/world.txt @@ -0,0 +1,175 @@ +Abara +Abarah +Abaranjan +Abarant +Abarantika +Abarantis +Abarat +Aldran +Aldrazan +Andalar +Andalarion +Andalas +Andalaspia +Antall +Ard +Atlands +Atreis +Avant +Avantadia +Avantastia +Avanth +Avanthae +Avanthana +Avanthas +Avantika +Azeros +Bellene +Blashyrion +Caeles +Caelest +Caelewan +Calas +Calashyrkh +Calasia +Capringdom +Coron +Coronos +Crematoril +Cruz +Cyber +Cyberrodin +Cyberron +Demiplanem +Demonia +Eberra +Edolaria +Edolarion +Eidolas +Ellest +Ellestia +Encanis +Falthea +Fanta Cruz +Fantaloor +Fantasia +Far-Toril +Fionarah +Fionaranth +Fionata +Gielion +Glorantika +Glorantis +Gloril +Hyboria +Hyborianna +Inkwood +Ithan +Ithandalar +Ithanna +Ithannarat +Ivalia +Keles +Kelesméra +Kelest +Kelestica +Kultha +Lag +Land +Lando +Landood +Landoranth +Layonaria +Lumireia +Lumireis +Mirron +Moonara +Moonarah +Moonata +Moonavar +Mundood +Mundorant +Mundover +Myrth +Myrthania +Myrthlands +Myrthsea +Mystarcion +Naran +Naranth +Narantha +Norra +Ostenwelt +Pando +Pandood +Parthana +Parthania +Parthland +Parthsea +Planem +Prime +Prydaeles +Prydaelest +Prydaelyn +R Heaven +Randlan +Randlantis +Riverland +Riverlands +Runeternia +Runeteros +Santadia +Santaloor +Sartoria +Sartoril +Scythaeus +Shand +Shanda +Shandlands +Shands +Shannaria +Spidessos +Sylvara +Sylvarah +Sylvarant +Sylvaranth +Sylvarat +Tanda +Tandalar +Tandalasia +Tandalazar +Telle +Telley +Tellezza +Tercani +Tercania +Tercanis +Terrath +Tethe'all +Tethe'alla +Tethea +Tetheria +Abara +Abarah +Thyria +Toril +Valdea +Valderland +Vallene +Vallezza +Velgartor +Velgartora +Whand +Whanda +Whandora +Whands +Whovillys +Wildea +Wildemar +Zamonata +ZanZara +ZanZaranth +ZanZarat +Zarah +Zendika +Zimia