diff --git a/ASCII-G.vcxproj b/ASCII-G.vcxproj
index 603b553..291e33f 100644
--- a/ASCII-G.vcxproj
+++ b/ASCII-G.vcxproj
@@ -127,13 +127,16 @@
+
+
+
diff --git a/ASCII-G.vcxproj.filters b/ASCII-G.vcxproj.filters
index bba9111..d24560e 100644
--- a/ASCII-G.vcxproj.filters
+++ b/ASCII-G.vcxproj.filters
@@ -30,6 +30,9 @@
Исходные файлы
+
+ Исходные файлы
+
@@ -47,5 +50,11 @@
Файлы заголовков
+
+ Файлы заголовков
+
+
+ Файлы заголовков
+
\ No newline at end of file
diff --git a/data/locations/location_1.txt b/data/locations/location_1.txt
index 26a2980..83c4c94 100644
--- a/data/locations/location_1.txt
+++ b/data/locations/location_1.txt
@@ -14,7 +14,7 @@ RIDDLE_ANSWER=hole
MAP_BEGIN
##########
#@.......#
-#....C...#
+#..N.C...#
#........#
#......D.#
##########
diff --git a/data/locations/location_2.txt b/data/locations/location_2.txt
index d6b500a..afba5b9 100644
--- a/data/locations/location_2.txt
+++ b/data/locations/location_2.txt
@@ -14,7 +14,7 @@ RIDDLE_ANSWER=magic
MAP_BEGIN
##########
#@.......#
-#..E.....#
+#..N..C..#
#........#
#......D.#
##########
diff --git a/data/locations/location_3.txt b/data/locations/location_3.txt
index 1bae725..befdbb1 100644
--- a/data/locations/location_3.txt
+++ b/data/locations/location_3.txt
@@ -16,6 +16,6 @@ MAP_BEGIN
#@.......#
#....N...#
#........#
-#......D.#
+#...C..D.#
##########
MAP_END
\ No newline at end of file
diff --git a/data/locations/location_4.txt b/data/locations/location_4.txt
index aca674e..a99ca3a 100644
--- a/data/locations/location_4.txt
+++ b/data/locations/location_4.txt
@@ -15,7 +15,7 @@ MAP_BEGIN
##########
#@.......#
#....C...#
-#..E.....#
+#..N.....#
#......D.#
##########
MAP_END
\ No newline at end of file
diff --git a/data/locations/location_5.txt b/data/locations/location_5.txt
index 471f11d..247e2b1 100644
--- a/data/locations/location_5.txt
+++ b/data/locations/location_5.txt
@@ -14,7 +14,7 @@ RIDDLE_ANSWER=keyboard
MAP_BEGIN
##########
#@.......#
-#....E...#
+#....N...#
#....C...#
#......D.#
##########
diff --git a/data/npcs/npc_1.txt b/data/npcs/npc_1.txt
new file mode 100644
index 0000000..213df69
--- /dev/null
+++ b/data/npcs/npc_1.txt
@@ -0,0 +1,23 @@
+ID=1
+NAME=Old Guard
+STATE=FRIENDLY
+HP=60
+DAMAGE=8
+
+DIALOG_TEXT=The guard looks tired. He asks: "What do you seek here?"
+OPTION_1=I seek knowledge.
+RESULT_1=GIVE_ITEM
+VALUE_1=101
+MESSAGE_1=The guard gives you a small healing potion.
+
+OPTION_2=Move away, old man.
+RESULT_2=BECOME_HOSTILE
+VALUE_2=0
+MESSAGE_2=The guard becomes angry and attacks you.
+
+OPTION_3=Can you open the next door?
+RESULT_3=OPEN_LOCATION
+VALUE_3=1
+MESSAGE_3=The guard unlocks the next location.
+
+END
\ No newline at end of file
diff --git a/data/npcs/npc_2.txt b/data/npcs/npc_2.txt
new file mode 100644
index 0000000..dc235f0
--- /dev/null
+++ b/data/npcs/npc_2.txt
@@ -0,0 +1,13 @@
+ID=2
+NAME=Dungeon Bandit
+STATE=HOSTILE
+HP=45
+DAMAGE=12
+
+DIALOG_TEXT=The bandit blocks your path.
+OPTION_1=Fight.
+RESULT_1=FIGHT
+VALUE_1=0
+MESSAGE_1=The fight begins.
+
+END
\ No newline at end of file
diff --git a/data/npcs/npc_3.txt b/data/npcs/npc_3.txt
new file mode 100644
index 0000000..71b0f9b
--- /dev/null
+++ b/data/npcs/npc_3.txt
@@ -0,0 +1,23 @@
+ID=3
+NAME=Archivist
+STATE=FRIENDLY
+HP=50
+DAMAGE=6
+
+DIALOG_TEXT=The archivist whispers: "Only those who understand patterns may pass."
+OPTION_1=Ask about the sequence.
+RESULT_1=OPEN_LOCATION
+VALUE_1=3
+MESSAGE_1=The archivist nods and unlocks the door.
+
+OPTION_2=Demand his notes.
+RESULT_2=BECOME_HOSTILE
+VALUE_2=0
+MESSAGE_2=The archivist becomes hostile.
+
+OPTION_3=Ask for help.
+RESULT_3=GIVE_ITEM
+VALUE_3=102
+MESSAGE_3=The archivist gives you a big healing potion.
+
+END
\ No newline at end of file
diff --git a/data/npcs/npc_4.txt b/data/npcs/npc_4.txt
new file mode 100644
index 0000000..ae711f1
--- /dev/null
+++ b/data/npcs/npc_4.txt
@@ -0,0 +1,23 @@
+ID=4
+NAME=Library Keeper
+STATE=FRIENDLY
+HP=70
+DAMAGE=10
+
+DIALOG_TEXT=The keeper stands near the shelves. "Knowledge has a price."
+OPTION_1=Respect the library.
+RESULT_1=OPEN_LOCATION
+VALUE_1=4
+MESSAGE_1=The keeper allows you to pass.
+
+OPTION_2=Steal a book.
+RESULT_2=BECOME_HOSTILE
+VALUE_2=0
+MESSAGE_2=The keeper attacks you for stealing.
+
+OPTION_3=Ask for supplies.
+RESULT_3=GIVE_ITEM
+VALUE_3=101
+MESSAGE_3=The keeper gives you a small healing potion.
+
+END
\ No newline at end of file
diff --git a/data/npcs/npc_5.txt b/data/npcs/npc_5.txt
new file mode 100644
index 0000000..4ca607d
--- /dev/null
+++ b/data/npcs/npc_5.txt
@@ -0,0 +1,23 @@
+ID=5
+NAME=Gate Warden
+STATE=HOSTILE
+HP=100
+DAMAGE=16
+
+DIALOG_TEXT=The warden blocks the final gate.
+OPTION_1=Fight.
+RESULT_1=FIGHT
+VALUE_1=0
+MESSAGE_1=The final battle begins.
+
+OPTION_2=Try to negotiate.
+RESULT_2=NOTHING
+VALUE_2=0
+MESSAGE_2=The warden ignores your words.
+
+OPTION_3=Run away.
+RESULT_3=NOTHING
+VALUE_3=0
+MESSAGE_3=You step back from the gate.
+
+END
\ No newline at end of file
diff --git a/include/game.h b/include/game.h
index 32d5cb3..2e9de09 100644
--- a/include/game.h
+++ b/include/game.h
@@ -2,6 +2,7 @@
#include "player.h"
#include "location.h"
#include "item.h"
+#include "npc.h"
#include
#include
@@ -15,6 +16,7 @@ private:
Player player;
std::vector locations;
std::vector- itemDatabase;
+ std::vector npcs;
void showMainMenu();
@@ -25,6 +27,10 @@ private:
Location* getCurrentLocation();
+ void interactWithNPC(Location& location);
+ void startCombat(NPC& npc);
+ void applyDialogResult(NPC& npc, const DialogOption& option);
+
void render();
void processCommand();
diff --git a/include/location.h b/include/location.h
index 7c30718..429d690 100644
--- a/include/location.h
+++ b/include/location.h
@@ -2,10 +2,8 @@
#include
#include
-struct Point {
- int x = 0;
- int y = 0;
-};
+#include "Point.h"
+#include "NPC.h"
struct Chest {
Point pos;
@@ -19,11 +17,6 @@ struct Enemy {
int hp = 50;
};
-struct NPC {
- Point pos;
- std::string name;
-};
-
struct Riddle {
int type = 0;
std::string text;
diff --git a/include/npc.h b/include/npc.h
new file mode 100644
index 0000000..edaf650
--- /dev/null
+++ b/include/npc.h
@@ -0,0 +1,48 @@
+#pragma once
+#include
+#include
+#include "point.h"
+
+enum class NPCState {
+ Friendly,
+ Hostile
+};
+
+enum class DialogResultType {
+ GiveItem,
+ BecomeHostile,
+ OpenLocation,
+ Fight,
+ Nothing
+};
+
+struct DialogOption {
+ std::string text;
+ DialogResultType result = DialogResultType::Nothing;
+ int value = 0;
+ std::string message;
+};
+
+class NPC {
+public:
+ int id = 0;
+ std::string name;
+
+ NPCState state = NPCState::Friendly;
+
+ int hp = 100;
+ int damage = 10;
+
+ Point position;
+
+ std::string dialogText;
+ std::vector options;
+
+ bool defeated = false;
+
+ bool loadFromFile(const std::string& path);
+
+private:
+ NPCState parseState(const std::string& value);
+ DialogResultType parseDialogResult(const std::string& value);
+};
\ No newline at end of file
diff --git a/include/point.h b/include/point.h
new file mode 100644
index 0000000..a49f285
--- /dev/null
+++ b/include/point.h
@@ -0,0 +1,6 @@
+#pragma once
+
+struct Point {
+ int x = 0;
+ int y = 0;
+};
\ No newline at end of file
diff --git a/src/game.cpp b/src/game.cpp
index aa352af..0317fae 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -182,7 +182,7 @@ void Game::handleTile(char tile) {
std::cout << "Enemy encountered. Combat will be added later.\n";
}
else if (tile == 'N') {
- std::cout << "NPC: Hello, traveler.\n";
+ interactWithNPC(*location);
}
else if (tile == 'D') {
if (!location->riddle.isSolved) {
@@ -314,4 +314,128 @@ void Game::openChest(Location& location) {
}
std::cout << "Chest not found. Something went wrong.\n";
+}
+
+
+void Game::applyDialogResult(NPC& npc, const DialogOption& option) {
+ if (option.result == DialogResultType::GiveItem) {
+ player.inventory.addItem(option.value);
+ std::cout << "Received item ID: " << option.value << "\n";
+ }
+ else if (option.result == DialogResultType::BecomeHostile) {
+ npc.state = NPCState::Hostile;
+ startCombat(npc);
+ }
+ else if (option.result == DialogResultType::OpenLocation) {
+ for (auto& location : locations) {
+ if (location.id == option.value) {
+ location.riddle.isSolved = true;
+ std::cout << "Location " << option.value << " has been unlocked.\n";
+ return;
+ }
+ }
+
+ std::cout << "Location " << option.value << " not found.\n";
+ }
+ else if (option.result == DialogResultType::Fight) {
+ startCombat(npc);
+ }
+ else {
+ std::cout << "Nothing happened.\n";
+ }
+}
+
+void Game::startCombat(NPC& npc) {
+ std::cout << "\nCombat started with " << npc.name << "!\n";
+
+ while (player.hp > 0 && npc.hp > 0) {
+ std::cout << "\nYour HP: " << player.hp << "\n";
+ std::cout << npc.name << " HP: " << npc.hp << "\n";
+
+ std::cout << "1. Attack\n";
+ std::cout << "2. Use small potion 101\n";
+ std::cout << "3. Run\n";
+ std::cout << "> ";
+
+ int choice;
+ std::cin >> choice;
+
+ if (choice == 1) {
+ int damage = player.getDamage(itemDatabase);
+ npc.hp -= damage;
+
+ std::cout << "You dealt " << damage << " damage.\n";
+
+ if (npc.hp <= 0) {
+ npc.defeated = true;
+ std::cout << "You defeated " << npc.name << ".\n";
+ return;
+ }
+ }
+ else if (choice == 2) {
+ player.usePotion(101, itemDatabase);
+ }
+ else if (choice == 3) {
+ std::cout << "You ran away.\n";
+ return;
+ }
+ else {
+ std::cout << "Invalid action.\n";
+ continue;
+ }
+
+ player.hp -= npc.damage;
+ std::cout << npc.name << " dealt " << npc.damage << " damage.\n";
+
+ if (player.hp <= 0) {
+ std::cout << "You died.\n";
+ isRunning = false;
+ return;
+ }
+ }
+}
+
+void Game::interactWithNPC(Location& location) {
+ for (auto& npc : location.npcs) {
+ if (npc.position.x == player.position.x && npc.position.y == player.position.y) {
+ if (npc.defeated) {
+ std::cout << "There is nobody useful here anymore.\n";
+ return;
+ }
+
+ std::cout << "\n=== " << npc.name << " ===\n";
+
+ if (npc.state == NPCState::Hostile) {
+ std::cout << npc.name << " is hostile.\n";
+ startCombat(npc);
+ return;
+ }
+
+ std::cout << npc.dialogText << "\n";
+
+ for (int i = 0; i < npc.options.size(); i++) {
+ std::cout << i + 1 << ". " << npc.options[i].text << "\n";
+ }
+
+ std::cout << "> ";
+
+ int choice;
+ std::cin >> choice;
+
+ if (choice < 1 || choice > npc.options.size()) {
+ std::cout << "Invalid dialog option.\n";
+ return;
+ }
+
+ const DialogOption& option = npc.options[choice - 1];
+
+ std::cout << option.message << "\n";
+
+ applyDialogResult(npc, option);
+
+ return;
+ }
+ }
+
+ std::cout << "NPC not found.\n";
}
\ No newline at end of file
diff --git a/src/location.cpp b/src/location.cpp
index f8b8ed7..a67eadb 100644
--- a/src/location.cpp
+++ b/src/location.cpp
@@ -64,7 +64,23 @@ bool Location::loadFromFile(const std::string& path) {
if (tile == 'C') {
Chest chest;
chest.pos = { x, y };
- chest.itemIds = { 101, 102 };
+
+ if (id == 1) {
+ chest.itemIds = { 101, 101 };
+ }
+ else if (id == 2) {
+ chest.itemIds = { 101, 201 };
+ }
+ else if (id == 3) {
+ chest.itemIds = { 102 };
+ }
+ else if (id == 4) {
+ chest.itemIds = { 102, 202 };
+ }
+ else if (id == 5) {
+ chest.itemIds = { 101, 102, 202 };
+ }
+
chests.push_back(chest);
}
else if (tile == 'E') {
@@ -76,9 +92,26 @@ bool Location::loadFromFile(const std::string& path) {
}
else if (tile == 'N') {
NPC npc;
- npc.pos = { x, y };
- npc.name = "Stranger";
- npcs.push_back(npc);
+
+ std::string npcPath;
+
+ if (id == 1) {
+ npcPath = "data/npcs/npc_1.txt";
+ }
+ else if (id == 2) {
+ npcPath = "data/npcs/npc_2.txt";
+ }
+ else if (id == 3) {
+ npcPath = "data/npcs/npc_3.txt";
+ }
+ else {
+ npcPath = "data/npcs/npc_1.txt";
+ }
+
+ if (npc.loadFromFile(npcPath)) {
+ npc.position = { x, y };
+ npcs.push_back(npc);
+ }
}
}
}
diff --git a/src/npc.cpp b/src/npc.cpp
new file mode 100644
index 0000000..a296b65
--- /dev/null
+++ b/src/npc.cpp
@@ -0,0 +1,81 @@
+#include "../include/NPC.h"
+#include
+#include
+
+NPCState NPC::parseState(const std::string& value) {
+ if (value == "FRIENDLY") return NPCState::Friendly;
+ if (value == "HOSTILE") return NPCState::Hostile;
+
+ return NPCState::Friendly;
+}
+
+DialogResultType NPC::parseDialogResult(const std::string& value) {
+ if (value == "GIVE_ITEM") return DialogResultType::GiveItem;
+ if (value == "BECOME_HOSTILE") return DialogResultType::BecomeHostile;
+ if (value == "OPEN_LOCATION") return DialogResultType::OpenLocation;
+ if (value == "FIGHT") return DialogResultType::Fight;
+
+ return DialogResultType::Nothing;
+}
+
+bool NPC::loadFromFile(const std::string& path) {
+ std::ifstream file(path);
+
+ if (!file.is_open()) {
+ std::cout << "Cannot open NPC file: " << path << "\n";
+ return false;
+ }
+
+ std::string line;
+
+ DialogOption option1;
+ DialogOption option2;
+ DialogOption option3;
+
+ while (std::getline(file, line)) {
+ if (line.empty()) {
+ continue;
+ }
+
+ if (line == "END") {
+ break;
+ }
+
+ size_t pos = line.find('=');
+
+ if (pos == std::string::npos) {
+ continue;
+ }
+
+ std::string key = line.substr(0, pos);
+ std::string value = line.substr(pos + 1);
+
+ if (key == "ID") id = std::stoi(value);
+ else if (key == "NAME") name = value;
+ else if (key == "STATE") state = parseState(value);
+ else if (key == "HP") hp = std::stoi(value);
+ else if (key == "DAMAGE") damage = std::stoi(value);
+ else if (key == "DIALOG_TEXT") dialogText = value;
+
+ else if (key == "OPTION_1") option1.text = value;
+ else if (key == "RESULT_1") option1.result = parseDialogResult(value);
+ else if (key == "VALUE_1") option1.value = std::stoi(value);
+ else if (key == "MESSAGE_1") option1.message = value;
+
+ else if (key == "OPTION_2") option2.text = value;
+ else if (key == "RESULT_2") option2.result = parseDialogResult(value);
+ else if (key == "VALUE_2") option2.value = std::stoi(value);
+ else if (key == "MESSAGE_2") option2.message = value;
+
+ else if (key == "OPTION_3") option3.text = value;
+ else if (key == "RESULT_3") option3.result = parseDialogResult(value);
+ else if (key == "VALUE_3") option3.value = std::stoi(value);
+ else if (key == "MESSAGE_3") option3.message = value;
+ }
+
+ if (!option1.text.empty()) options.push_back(option1);
+ if (!option2.text.empty()) options.push_back(option2);
+ if (!option3.text.empty()) options.push_back(option3);
+
+ return true;
+}
\ No newline at end of file