diff --git a/7DRL.exe b/7DRL.exe new file mode 100644 index 0000000..a30fe84 Binary files /dev/null and b/7DRL.exe differ diff --git a/7DRL.vcxproj b/7DRL.vcxproj index 8b9b998..bc215a7 100644 --- a/7DRL.vcxproj +++ b/7DRL.vcxproj @@ -116,7 +116,8 @@ true true true - D:\Programming\libtcod-1.15.1-x86_64-msvc\include; + C:\Users\Usuario\Source\Repos\7DRL\src\soloud\include;D:\Programming\SDL2\include;D:\Programming\libtcod-1.15.1-x86_64-msvc\include + WITH_SDL2;_CRT_SECURE_NO_WARNINGS;_MBCS;%(PreprocessorDefinitions) true @@ -126,13 +127,18 @@ + + + + + - - + + @@ -200,6 +206,7 @@ + @@ -208,14 +215,21 @@ + + + + + + + - + - + @@ -237,6 +251,7 @@ + diff --git a/7DRL.vcxproj.filters b/7DRL.vcxproj.filters index 2a810b5..ff041d8 100644 --- a/7DRL.vcxproj.filters +++ b/7DRL.vcxproj.filters @@ -210,18 +210,12 @@ Source Files - - Source Files - Source Files Source Files - - Source Files - Source Files @@ -237,6 +231,30 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -303,9 +321,6 @@ Header Files - - Header Files - Header Files @@ -318,9 +333,6 @@ Header Files - - Header Files - Header Files @@ -336,5 +348,35 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/7DRL.zip b/7DRL.zip new file mode 100644 index 0000000..d583fda Binary files /dev/null and b/7DRL.zip differ diff --git a/SAMPLES.txt b/SAMPLES.txt new file mode 100644 index 0000000..51e8d95 --- /dev/null +++ b/SAMPLES.txt @@ -0,0 +1,2 @@ +Some samples were taken from the ARMA 3 mod JSRS (Explosion sounds) and +mixed with other effects. diff --git a/Test.xp b/Test.xp index f0cc764..2e26405 100644 Binary files a/Test.xp and b/Test.xp differ diff --git a/alien_die.wav b/alien_die.wav new file mode 100644 index 0000000..b5bc3a5 Binary files /dev/null and b/alien_die.wav differ diff --git a/biter/attack.wav b/biter/attack.wav new file mode 100644 index 0000000..eba5795 Binary files /dev/null and b/biter/attack.wav differ diff --git a/biter/idle.wav b/biter/idle.wav new file mode 100644 index 0000000..c1122f7 Binary files /dev/null and b/biter/idle.wav differ diff --git a/collide.wav b/collide.wav new file mode 100644 index 0000000..9d5ecef Binary files /dev/null and b/collide.wav differ diff --git a/collide2.wav b/collide2.wav new file mode 100644 index 0000000..d88aa2a Binary files /dev/null and b/collide2.wav differ diff --git a/crawler/attack.wav b/crawler/attack.wav new file mode 100644 index 0000000..10b52d1 Binary files /dev/null and b/crawler/attack.wav differ diff --git a/crawler/idle.wav b/crawler/idle.wav new file mode 100644 index 0000000..2f8bdb4 Binary files /dev/null and b/crawler/idle.wav differ diff --git a/crawler/moving.wav b/crawler/moving.wav new file mode 100644 index 0000000..347f892 Binary files /dev/null and b/crawler/moving.wav differ diff --git a/diesel_off.wav b/diesel_off.wav new file mode 100644 index 0000000..b63779a Binary files /dev/null and b/diesel_off.wav differ diff --git a/explo_far_dist.wav b/explo_far_dist.wav new file mode 100644 index 0000000..3f987a2 Binary files /dev/null and b/explo_far_dist.wav differ diff --git a/explo_med_dist.wav b/explo_med_dist.wav new file mode 100644 index 0000000..f5f5860 Binary files /dev/null and b/explo_med_dist.wav differ diff --git a/explo_near_dist.wav b/explo_near_dist.wav new file mode 100644 index 0000000..3b5f31f Binary files /dev/null and b/explo_near_dist.wav differ diff --git a/four_way.wav b/four_way.wav new file mode 100644 index 0000000..210544d Binary files /dev/null and b/four_way.wav differ diff --git a/grenade.wav b/grenade.wav new file mode 100644 index 0000000..5a3bb0b Binary files /dev/null and b/grenade.wav differ diff --git a/grenade_land.wav b/grenade_land.wav new file mode 100644 index 0000000..9b1265f Binary files /dev/null and b/grenade_land.wav differ diff --git a/gunshot.wav b/gunshot.wav new file mode 100644 index 0000000..04fd6b3 Binary files /dev/null and b/gunshot.wav differ diff --git a/hurt.wav b/hurt.wav new file mode 100644 index 0000000..506d8a6 Binary files /dev/null and b/hurt.wav differ diff --git a/hurt_alien.wav b/hurt_alien.wav new file mode 100644 index 0000000..92d6452 Binary files /dev/null and b/hurt_alien.wav differ diff --git a/hurt_metal.wav b/hurt_metal.wav new file mode 100644 index 0000000..da4b862 Binary files /dev/null and b/hurt_metal.wav differ diff --git a/man_die.wav b/man_die.wav new file mode 100644 index 0000000..775727b Binary files /dev/null and b/man_die.wav differ diff --git a/metal_die.wav b/metal_die.wav new file mode 100644 index 0000000..434c548 Binary files /dev/null and b/metal_die.wav differ diff --git a/shock.wav b/shock.wav new file mode 100644 index 0000000..4225987 Binary files /dev/null and b/shock.wav differ diff --git a/src/Crewmember.cpp b/src/Crewmember.cpp new file mode 100644 index 0000000..a15a2e3 --- /dev/null +++ b/src/Crewmember.cpp @@ -0,0 +1,84 @@ +#include "Crewmember.h" + +static SoLoud::Wav* radio = nullptr; + + + +void Crewmember::speak(std::string text) +{ + if (radio == nullptr) + { + radio = new SoLoud::Wav(); + radio->load("radio.wav"); + } + + g_soloud->play(*radio); + + voice.setText((". . ." + text).c_str()); + g_soloud->play(voice, 1.6f); + + g_status->strings.push_back(name + ": " + text); +} + + +Crewmember::Crewmember() +{ + std::array first_names = + { + "Bob", + "John", + "Robert", + "Michael", + "William", + "David", + "Richard", + "Joseph", + "Guamedo", + "Thomas", + "Charles", + "Donald", + "Mark", + "Brian", + "Alex" + }; + + std::array last_names = + { + "Smith", + "Bobbins", + "Williams", + "Jones", + "Brown", + "Davis", + "Miller", + "Wilson", + "Moore", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + "Garcia", + "Martinez", + "Young" + }; + + name = first_names[rand() % first_names.size()] + " " + last_names[rand() % last_names.size()]; + + int wave = KW_TRIANGLE; + if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + wave = KW_PULSE; + } + else if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + wave = KW_WARBLE; + } + else if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + wave = KW_SQUARE; + } + + voice.setParams(g_random->getInt(900, 1900), g_random->getFloat(6.5f, 8.5f), g_random->getFloat(0.4f, 0.6f), wave); + +} \ No newline at end of file diff --git a/src/Crewmember.h b/src/Crewmember.h new file mode 100644 index 0000000..63bbfdc --- /dev/null +++ b/src/Crewmember.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include "defines.h" +#include +#include + +class Crewmember +{ +public: + SoLoud::Speech voice; + std::string name; + bool is_captain; + + + void speak(std::string text); + + Crewmember(); +}; + diff --git a/src/Drawing.h b/src/Drawing.h index 862cf28..496c93e 100644 --- a/src/Drawing.h +++ b/src/Drawing.h @@ -4,6 +4,7 @@ #include "defines.h" static SoLoud::Wav* click = nullptr; +static SoLoud::Wav* four_way = nullptr; class Drawing { @@ -183,4 +184,107 @@ class Drawing return clicked; } + + // 0 = Top + // 1 = Right + // 2 = Bottom + // 3 = Left + // x,y are coordiantes of the four choice thing, not text + static bool draw_four_choice(TCODConsole* target, int x, int y, int rx, int ry, int* dir, + const std::string& opt_u, + const std::string& opt_r, + const std::string& opt_d, + const std::string& opt_l) + { + if (four_way == nullptr) + { + four_way = new SoLoud::Wav(); + four_way->load("four_way.wav"); + } + + target->putChar(x + 1, y + 0, 201); + target->putChar(x + 2, y + 0, 205); + target->putChar(x + 3, y + 0, 187); + + target->putChar(x + 0, y + 1, 201); + target->putChar(x + 1, y + 1, 188); + target->putChar(x + 3, y + 1, 200); + target->putChar(x + 4, y + 1, 187); + + target->putChar(x + 0, y + 2, 186); + target->putChar(x + 2, y + 2, 7); + target->putChar(x + 4, y + 2, 186); + + target->putChar(x + 0, y + 3, 200); + target->putChar(x + 1, y + 3, 187); + target->putChar(x + 3, y + 3, 201); + target->putChar(x + 4, y + 3, 188); + + target->putChar(x + 1, y + 4, 200); + target->putChar(x + 2, y + 4, 205); + target->putChar(x + 3, y + 4, 188); + + if (*dir == 0) + { + target->putChar(x + 2, y + 1, 24); + } + else if (*dir == 1) + { + target->putChar(x + 3, y + 2, 26); + } + else if (*dir == 2) + { + target->putChar(x + 2, y + 3, 25); + } + else + { + target->putChar(x + 1, y + 2, 27); + } + + // Draw texts + target->setAlignment(TCOD_CENTER); + target->printf(x + 2, y - 1, opt_u.c_str()); + target->printf(x + 2, y + 5, opt_d.c_str()); + target->setAlignment(TCOD_RIGHT); + target->printf(x - 1, y + 2, opt_l.c_str()); + target->setAlignment(TCOD_LEFT); + target->printf(x + 5, y + 2, opt_r.c_str()); + + // Interaction + TCOD_mouse_t pos = TCODMouse::getStatus(); + + int pos_cx = pos.cx - rx; + int pos_cy = pos.cy - ry; + + int old_dir = *dir; + + if (pos.lbutton_pressed) + { + if (pos_cx == x + 2 && pos_cy == y + 1) + { + *dir = 0; + } + else if (pos_cx == x + 3 && pos_cy == y + 2) + { + *dir = 1; + } + else if (pos_cx == x + 2 && pos_cy == y + 3) + { + *dir = 2; + } + else if (pos_cx == x + 1 && pos_cy == y + 2) + { + *dir = 3; + } + } + + if (*dir != old_dir) + { + // Play sound + g_soloud->play(*four_way); + return true; + } + + return false; + } }; \ No newline at end of file diff --git a/src/Help.h b/src/Help.h index f126cf4..62ff5b5 100644 --- a/src/Help.h +++ b/src/Help.h @@ -1,24 +1,339 @@ #pragma once #include +#include class Help { public: - static constexpr const char* help_main = "\ --=Main screen=-\n\n\ -F11 - Open/Close this menu\n\ -ESC - Close Menus\n\ -Space - Pause\n\ -Left-click - Open Machines\n\ -Right-click - Contextual Menu\n\ --------------------------------------\n\ -What should I do?\n\n\ -Wait for orders from central command, you will receive them on the communicator\n\n\ --------------------------------------\n\ -How can I interact with the crew?\n\n\ -All orders are done via rightclicking, you can find further help on the \"Crewmember\" menu \ -that can be opened on the rightclick dropdown"; -}; + std::string cur_screen; + std::unordered_map screens; + + std::vector return_to; + + Help() + { +// Text template +// | + screens["Main Screen"] = "\ +Welcome to $174$$174$The Legend of U101$175$$175$.\n\ +This is the help screen which will guide you while you play the game, it can be opened\n\ +and closed with $yellow$F1$white$. \n\ +Items coloured in $blue$blue$white$ are links, which can be clicked to learn further.\n\ +\n\ +-@Tutorial@\n\ +-@Controls Overview@\n\ +"; + + screens["Tutorial"] = "\ +You start the game inside your submarine, your vehicle has just left one of the few\n\ +human bases under Jupiter-2's (Europa) ice crust, with the objective of cleaning the\n\ +area for further colonization. In order to complete this objective, you must travel\n\ +near nests, marked in the sonar as a $sonar$$15$$white$.\n\ +Once you are sufficiently close, you will get a prompt to disembark.\n\ +\n\ +Before any of that happens you will have to travel through the dangerous underground\n\ +icy caverns. To do so, you will need to carefully manage your crew, issuing orders and\n\ +controlling the many workstations available.\n\n\ +To select a crewmember $1$ ($2$ is you, the captain), $yellow$left click$white$ him. Once\n\ +selected, you can $yellow$right click$white$ to bring up the contextual menu, or\n\ +$yellow$middle click$white$ anywhere to move the crewmember there. See @Controlling Crew@.\n\n\ +To control a workstation, left-click on it. A crewmember must occupy the chair ($brown$$239$$white$)\n\ +in order for the workstation to be usable, and for orders issued to it to take place.\n\ +To exit the workstation, press $yellow$Escape$white$\n\n\ +@Tutorial 1: Overview of the Submarine@\n\ +@Tutorial 2: Preparing@\n\ +@Tutorial 3: Movement@\n\ +@Tutorial 4: Combat@\n\ +@Tutorial 5: Disembarking@\n\ +\n\ +You can pause the game anytime with $yellow$SPACE$white$ so feel free to take your time\n\ +Also, check the @Controls Overview@ to find every single keybind!"; + + screens["Tutorial 1: Overview of the Submarine"] = "\ +The leftmost part of the submarine is the torpedo room. The small ($dark$=$white$) symbols are\n\ +the torpedo tubes. You can order torpedos to be loaded by selecting a crewmember\n\ +right-clicking over a torpedo, and selecting $yellow$Load Torpedo$white$.\n\n\ +The next room are the living quarters.\n\n\ +The next two rooms hold two very important work stations, the topmost one is the @Sonar@\n\ +and the bottom one is the @Radio@\n\n\ +The next room contains the @Periscope@ (center), the @Maneuvering Station@(top)\n\ +and the @Targeting Station@(bottom).\n\n\ +The next room contains the @Diesel Engines@.\n\n\ +The last room contains the @Battery Station@ and a few extra torpedoes\n\n"; + + screens["Tutorial 2: Preparing"] = "\ +The submarine currently is nothing more that a barely floating piece of metal. You need to\n\ +prepare the multiple systems before you depart. First of all, you need electricity.\n\ +Head over to the @Battery Station@ and toggle on (by left-clicking) the switches:\n\ +- $174$$dark$BAT.A > MOTOR$white$$175$ (Allows moving with the electric engines)\n\ +- $174$$dark$BAT.B > BAT.A$white$$175$ (Uses the B battery to fuel the motor indirectly, extending range)\n\ +- $174$$dark$BAT.A > B.SHK$white$$175$ (Extends usage of the electric shock weapon)\n\n\ +Now you need to load some torpedos.\nLeft-click any crewmember and right-click one of the torpedos,\n\ +then select $yellow$Load Torpedo$white$. Do this until all tubes are loaded.\n\ +Then you will want to set atleast 2 crewmembers to Auto-Repair. The captain ($2$) is\n\ +always a good option. You can also set the radio-man and the diesel engine controller.\n\ +To do this, left-click a crewmember, right-click anywhere and select $yellow$Auto-Repair$white$\n\ +You are now ready to proceed. Click $yellow$R$white$ to return all crew to their positions.\n\n\ +You may have already received a radio message, to view it, click the @Radio@ and listen."; + + screens["Tutorial 3: Movement"] = "\ +You can move by manually using the @Maneouvering Station@, but it's easier and faster to\n\ +use the numpad keys, which allows full directional movement, including diagonals.\n\ +You can issue throttle commands with the numbers on top of the keyboard.\n\n\ +Head over to the sonar, and toggle on the $174$$dark$Active Sonar$white$$175$ switch.\n\ +You will now see the world surrounding you, and hear sonar pings.\n\ +You should toggle the sonar off if you want to carefully avoid some enemy.\n\ +Now go to the @Periscope@. It has a relatively short range (~200m), but it's always on\n\ +and shows the cave walls which the sonar may not warn you about. \n\ +Use the @Periscope@ whenever you are maneovering on a tight passage,\n\ +when approaching nests, or when fighting enemies from a close range.\n\n\ +Try moving in some direction free of walls (Use the numpad) on normal throttle:\n\ +- $yellow$1$white$ = Stop\n\ +- $yellow$2$white$ = Slow\n\ +- $yellow$3$white$ = Normal\n\ +- $yellow$4$white$ = Fast\n\ +- $yellow$5$white$ = Full Speed Ahead (You need two batteries assigned to MOTOR to do this)\n\ +\n\n\ +While you are moving, any battery assigned to MOTOR will deplete. You must be careful,\n\ +if you run out of electricity you could very easily get stuck, and lose the game.\n\ +In order to refill batteries you need to get to oxygen-rich zones,\n\ +marked in the sonar as a ($sonar$$177$$white$). Try maneouvering to one.\n\ +Once you reach the zone, stop and head over to the @Diesel Engines@. \n\ +You want to set atleast one of the generators to output to BATTS (Both batteries), and enable it.\n\ +You can set the other one to wathever you want.\n\ +Keep in mind that running both engines makes a lot of noise and may attract nearby enemies!\n\ +Feel free to start exploring the map, and keep on checking the radio!\n\n\ +Reminder: You can check your coordinates on the sonar. \n\ +They are called \"Coarse Coordinates\", \"Fine Coordinates\" are your sub-tile\n\ +position, not world-position."; + + screens["Tutorial 4: Combat"] = "\ +You may currently be under attack. First of all, pause the game and don't panic.\n\ +If you have prepared correctly, you will have torpedos loaded and ready to fire.\n\ +It may be wise to move one of the crewmembers to the torpedo room.\n\ +Make sure the sonar, targeting station, maneouvering station and periscope are crewed\n\ +during combat, as you will use them a lot. Make sure that the active sonar is toggled\n\ + on in order to be able to target your enemy even if you lose sight of him.\n\n\ +In order to destroy your objective, go to the @Targeting Station@, left-click the target\n\ +that you want to destroy, and, once it's selected, click $yellow$F$white$ to fire a torpedo.\n\ +Make sure you are aiming roughly towards your target, otherwise you may miss.\n\n\ +Your other weapon is the electric shock, activated with $yellow$E$white$. It consumes\n\ +electricity from the SHK battery, and does a considerable ammount of damage to targets\n\ +near your submarine. Useful against smaller enemies."; + + screens["Tutorial 5: Disembarking"] = "\ +Once you are sufficiently close to either a Nest ($sonar$$15$$white$) or a Station ($sonar$$234$$white$)\n\ +you will get a prompt to disembark on the upper-left corner of the screen.\n\ +Once you click the $yellow$G$white$ key, four of your crewmembers will go inside the target.\n\n\ +Your objective inside is to find and destroy the enemy nest, or the enemy \"commander\".\n\ +You can select any of your crewmembers by left-clicking them. Once selected, you can:\n\ +- Move: Using the $yellow$middle mouse button$white$\n\ +- Change Target: Using the $yellow$right mouse button$white$\n\ +- Throw Grenades: Using $yellow$SHIFT$white$ and the $right mouse button$\n\n\ +When you complete your objective, move every crewmember into the $sonar$Green$white$ zone.\n\n\n\ +Your crew can be killed, so be careful.\nAlso, remember you can always pause the game with $yellow$SPACE$white$!"; + + screens["Controls Overview"] = "\ +- $yellow$Numpad Numbers$white$: Issue heading orders\n\ +- $yellow$1$white$: Stop vehicle\n\ +- $yellow$2$white$: Move slowly\n\ +- $yellow$3$white$: Move normally\n\ +- $yellow$4$white$: move fast\n\ +- $yellow$5$white$: Full speed ahead (Only possible with 2 batteries or diesel engines active)\n\ +- $yellow$F$white$: Fire torpedo\n\ +- $yellow$R$white$: Return all crew to assigned positions\n\ +- $yellow$G$white$: Disembark if possible\n\ +- $yellow$E$white$: Electric Shock\n\ +- $yellow$ESCAPE$white$: Go back / Close Workbench / Close popup\n\ +- $yellow$SPACE$white$: Pause the game\n\n\ +-----------------------------------------------------\n\ +Special controls (No numpad or middle mouse button): \n\n\ +- $yellow$Vi keys$white$: Set heading\n\ +- $yellow$Ctrl-Leftclick$white$: Same as middle clicking\n\n\ +You don't need to enable anything, these controls are always enabled!"; + + screens["Controlling Crew"] = "\ +You can select crewmembers by left clicking them. Once they are selected you can bring up\n\ +the context menu by right-clicking. You can also move directly by pressing the\n\ +$yellow$middle mouse button$white$. In the context menu you can:\n\ +- Move Here: Moves the crew to given location\n\ +- Assign Location: The crew will go to selected location when $yellow$R$white$ is pressed\n\ +- Load Torpedo: Only shows when you right click over a torpedo\n\ +- Pump Water Out: Only shows when you right click a pump ($235$)\n\ +- Repair Tile: Only shows when you right click a damaged tile\n\ +- Toggle Auto-Repair: Allows the crew to automatically repair damaged tiles he detects\n\n\ +"; + + + cur_screen = "Main Screen"; + + } + + void draw() + { + Drawing::draw_window(TCODConsole::root, 0, 0, 89, HEIGHT - 1, cur_screen, true); + + TCODConsole::root->setDefaultForeground(TCODColor::lightGrey); + std::string& cur = screens[cur_screen]; + + int x = 1; + int y = 1; + + TCODConsole::root->setDefaultForeground(TCODColor::white); + + TCOD_mouse_t mouse = TCODMouse::getStatus(); + + if (g_key.vk == TCODK_ESCAPE) + { + if (return_to.size() != 0) + { + std::string ret = return_to[return_to.size() - 1]; + return_to.pop_back(); + + cur_screen = ret; + } + } + + for (int i = 0; i < cur.size(); i++) + { + bool inc = false; + + char c = cur[i]; + + if (c == ' ') + { + inc = true; + } + else if (c == '$') + { + int skip = 1; + char j = 1; + std::string str = ""; + while (j != '$') + { + j = cur[i + skip]; + str.push_back(j); + skip++; + } + + str.pop_back(); + i += skip - 1; + const char* strc = str.c_str(); + char* end; + int ch = strtol(strc, &end, 10); + if (end == strc) + { + // It was not a numeric, it was a color code + if (str == "yellow") + { + TCODConsole::root->setDefaultForeground(TCODColor::yellow); + } + else if (str == "white") + { + TCODConsole::root->setDefaultForeground(TCODColor::white); + } + else if (str == "blue") + { + TCODConsole::root->setDefaultForeground(TCODColor(128, 128, 255)); + } + else if (str == "sonar") + { + TCODConsole::root->setDefaultForeground(TCODColor(80, 255, 80)); + } + else if (str == "brown") + { + TCODConsole::root->setDefaultForeground(TCODColor(158, 134, 100)); + } + else if (str == "dark") + { + TCODConsole::root->setDefaultForeground(TCODColor(180, 180, 180)); + } + } + else + { + TCODConsole::root->putChar(x, y, ch); + x++; + } + } + else if(c == '@') + { + int skip = 1; + char j = 1; + std::string str = ""; + while (j != '@') + { + j = cur[i + skip]; + str.push_back(j); + skip++; + } + + str.pop_back(); + i+=str.size() + 1; + + bool hlight = false; + if (mouse.cx >= x && mouse.cx < x + str.size() && mouse.cy == y) + { + TCODConsole::root->setDefaultBackground(TCODColor::white); + TCODConsole::root->setDefaultForeground(TCODColor::black); + hlight = true; + } + else + { + TCODConsole::root->setDefaultForeground(TCODColor(128, 128, 255)); + } + + for (int j = 0; j < str.size(); j++) + { + if (str[j] == '_') + { + str[j] = ' '; + } + + TCODConsole::root->putChar(x + j, y, str[j]); + if (hlight) + { + TCODConsole::root->setCharBackground(x + j, y, TCODColor::white); + } + + } + + if (hlight && mouse.lbutton_pressed) + { + return_to.push_back(cur_screen); + cur_screen = str; + } + + TCODConsole::root->setDefaultBackground(TCODColor::black); + + x += str.size(); + + TCODConsole::root->setDefaultForeground(TCODColor::white); + + } + else if (c == '\n') + { + y++; + x = 1; + } + else if (c != '$' && c > 32) + { + TCODConsole::root->putChar(x, y, cur[i]); + inc = true; + } + + if (inc) + { + x++; + if (x >= 88) + { + y++; + x = 1; + } + } + } + + } +}; \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp index 0bdd6e9..47a4314 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -3,21 +3,27 @@ #include "Status.h" #include "flight/FlightScene.h" +#include "disembark/EmbarkScene.h" #include "soloud/include/soloud.h" #include "soloud/include/soloud_wav.h" +#include "flight/entity/Buildings.h" +#include "flight/Gamemaster.h" +#include "Popup.h" + SoLoud::Soloud* g_soloud; Status* g_status; TCOD_key_t g_key; TCODRandom* g_random; +Gamemaster* g_master; +Popup* g_popup; int main(int argc, char** argv) { TCODConsole::setCustomFont("rc11.png", TCOD_FONT_LAYOUT_ASCII_INROW); - TCODConsole::initRoot(WIDTH, HEIGHT, "7DRL", false, TCOD_RENDERER_GLSL); - + TCODConsole::initRoot(WIDTH, HEIGHT, "The Legend of U-101", false, TCOD_RENDERER_GLSL); g_soloud = new SoLoud::Soloud; g_soloud->init(); @@ -25,21 +31,118 @@ int main(int argc, char** argv) g_status = new Status(); g_random = new TCODRandom(); + g_master = new Gamemaster(); + g_popup = new Popup(); + + + FlightScene flight_scene = FlightScene(); + EmbarkScene embark_scene = EmbarkScene(); + + flight_scene.update(1.0f); + + g_master->flight_scene = &flight_scene; + g_master->embark_scene = &embark_scene; + g_master->embarked = false; + + g_master->init(); TCODSystem::setFps(60); + + Help help = Help(); + + bool help_open = false; + bool paused = false; + + bool show_fps = false; + + g_popup->show("Press the F1 key to show the in-game help menu!\n\n\ +You can press ESCAPE or ENTER to close these pop-ups."); + while (!TCODConsole::isWindowClosed()) { TCOD_mouse_t mouse; TCODSystem::checkForEvent(TCOD_EVENT_KEY_PRESS | TCOD_EVENT_MOUSE, &g_key, &mouse); - flight_scene.update(TCODSystem::getLastFrameLength()); + if (g_key.vk == TCODK_F1) + { + help_open = !help_open; + } + + if (g_key.vk == TCODK_SPACE) + { + paused = !paused; + } + + float dt = TCODSystem::getLastFrameLength(); + float rdt = dt; + if (paused || help_open || g_popup->is_shown) + { + rdt = 0.0f; + } - + g_master->update(rdt); + + + + if (g_popup->is_shown) + { + g_popup->update(dt); + } + TCODConsole::root->clear(); - flight_scene.render(); + if (!g_master->embarked) + { + flight_scene.render(); + } + else + { + embark_scene.render(); + } + + if (g_popup->is_shown) + { + g_popup->draw(); + } + + if (help_open) + { + help.draw(); + } + + if (g_key.vk == TCODK_F3) + { + show_fps = !show_fps; + } + + if (show_fps) + { + TCODConsole::root->printf(32, 0, "FPS: %i", (int)(1.0f / dt)); + } + + if (g_master->gameover) + { + TCODConsole::root->setAlignment(TCOD_CENTER); + TCODConsole::root->setDefaultForeground(TCODColor::lighterRed); + TCODConsole::root->printf(WIDTH / 2, HEIGHT / 2 - 5, "Game Over"); + TCODConsole::root->setDefaultForeground(TCODColor::white); + TCODConsole::root->printf(WIDTH / 2, HEIGHT / 2 - 4, "All crewmembers died"); + TCODConsole::root->setAlignment(TCOD_LEFT); + } + + + if (paused || help_open) + { + TCODConsole::root->setDefaultForeground(TCODColor::red); + TCODConsole::root->setAlignment(TCOD_RIGHT); + TCODConsole::root->printf(TCODConsole::root->getWidth() - 1, 0, "Paused"); + TCODConsole::root->setAlignment(TCOD_LEFT); + TCODConsole::root->setDefaultForeground(TCODColor::white); + } + + TCODConsole::flush(); } diff --git a/src/Popup.h b/src/Popup.h new file mode 100644 index 0000000..5e32fdd --- /dev/null +++ b/src/Popup.h @@ -0,0 +1,33 @@ +#pragma once +#include "Drawing.h" + + +class Popup +{ +public: + + bool is_shown; + std::string txt; + void show(const std::string& str) + { + is_shown = true; + txt = str; + } + + void update(float dt) + { + if (g_key.vk == TCODK_ESCAPE || g_key.vk == TCODK_ENTER) + { + is_shown = false; + } + } + + void draw() + { + Drawing::draw_window(TCODConsole::root, 20, 10, 50, 30, "Attention!", true); + TCODConsole::root->printRect(22, 12, 47, 27, txt.c_str()); + } + + +}; + diff --git a/src/defines.h b/src/defines.h index a0cf435..98d4641 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,7 +1,17 @@ #pragma once -#include "soloud.h" + #include "Status.h" + + +namespace SoLoud +{ + class Soloud; +} + +class Gamemaster; +class Popup; + #define WIDTH 90 #define HEIGHT 70 @@ -14,6 +24,9 @@ extern TCOD_key_t g_key; extern TCODRandom* g_random; +extern Gamemaster* g_master; +extern Popup* g_popup; + enum Direction { N, diff --git a/src/disembark/BuildingMap.cpp b/src/disembark/BuildingMap.cpp new file mode 100644 index 0000000..e3779c3 --- /dev/null +++ b/src/disembark/BuildingMap.cpp @@ -0,0 +1,263 @@ +#include "BuildingMap.h" +#include "../flight/Gamemaster.h" + + +void BuildingMap::dig(int x1, int y1, int x2, int y2) +{ + if (x2 < x1) + { + std::swap(x2, x1); + } + + if (y2 < y1) + { + std::swap(y2, y1); + } + + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) + { + if (x > 0 && y > 0 && x < width - 1 && y < height - 1) + { + tiles[y * width + x].is_wall = false; + } + } + } +} + +void BuildingMap::create_room(bool first, int x1, int y1, int x2, int y2) +{ + dig(x1, y1, x2, y2); + + if (first) + { + // Set spawn location + spawn_x0 = x1; + spawn_y0 = y1; + spawn_x1 = x2; + spawn_y1 = y2; + } + else + { + // Spawn stuff in the room + } +} + +void BuildingMap::draw(TCODConsole& target) +{ + int ch = '#'; + if (!organic) + { + ch = 240; + } + + TCODColor fore_b = TCODColor(200, 200, 200); + TCODColor fore_s = TCODColor(70, 70, 70); + if (!organic) + { + fore_b = TCODColor(180, 180, 255); + fore_s = TCODColor(60, 60, 100); + } + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + TCODColor fore; + TCODColor back; + + if (tiles[y * width + x].seen) + { + fore = fore_b; + back = TCODColor(20, 20, 20); + + if (tiles[y * width + x].alien_blood) + { + back = TCODColor(54, 0, 217); + } + else if (tiles[y * width + x].blood) + { + back = TCODColor(102, 0, 26); + } + } + else if (tiles[y * width + x].seen_before) + { + fore = fore_s; + back = TCODColor(0, 0, 0); + } + else + { + fore = TCODColor::black; + back = TCODColor::black; + } + + + if (tiles[y * width + x].is_wall) + { + target.setChar(x, y, ch); + } + else + { + fore.setValue(fore.getValue() * 0.5f); + target.setChar(x, y, '.'); + } + + target.setCharForeground(x, y, fore); + target.setCharBackground(x, y, back); + } + } + + for (int i = 0; i < monsters.size(); i++) + { + if (tiles[monsters[i].y * width + monsters[i].x].seen) + { + target.setChar(monsters[i].x, monsters[i].y, monsters[i].health > 0.0f ? monsters[i].get_symbol() : '*'); + target.setCharForeground(monsters[i].x, monsters[i].y, monsters[i].get_foreground()); + } + } + + +} + +BuildingMap::BuildingMap(int w, int h, bool organic) : tcod_map(w, h) +{ + this->organic = organic; + + width = w; + height = h; + tiles.resize(w * h); + + + // Generate the map + TCODBsp root = TCODBsp(0, 0, w, h); + + min_size = g_random->getInt(4, 8); + max_size = g_random->getInt(min_size, min_size + 8); + + root.splitRecursive(g_random, 8, max_size, max_size, + g_random->getFloat(1.3f, 1.8f), g_random->getFloat(1.3f, 1.8f)); + + BSPListener listener = BSPListener(this); + root.traverseInvertedLevelOrder(&listener, nullptr); + + if (organic) + { + // Do some random digging + int it = 0; + int to_dig = (int)(g_random->getFloat(0.2f, 0.5f) * width * height); + + int dx = g_random->getInt(0, width); + int dy = g_random->getInt(0, height); + + while (it < to_dig) + { + dx += g_random->getInt(-1, 1); + dy += g_random->getInt(-1, 1); + + if (dx > 0 && dy > 0 && dx < width - 1 && dy < height - 1) + { + tiles[dy * width + dx].is_wall = false; + it++; + } + else + { + dx = g_random->getInt(0, width); + dy = g_random->getInt(0, height); + } + + + } + } + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + tcod_map.setProperties(x, y, !tiles[y * width + x].is_wall, !tiles[y * width + x].is_wall); + } + } + + // Spawn bois + int wanted[MONSTER_COUNT] = { 0 }; + int placed[MONSTER_COUNT] = { 0 }; + + if (organic) + { + wanted[M_ARACHNID] = g_random->getInt(6, 14); + wanted[M_ANT] = g_random->getInt(4, 9); + wanted[M_GOLEM] = g_random->getInt(2, 5); + wanted[M_NEST] = 1; + } + else + { + wanted[M_ARACHNID] = g_random->getInt(0, 5); + wanted[M_ANT] = g_random->getInt(0, 5); + wanted[M_GOLEM] = g_random->getInt(0, 2); + wanted[M_ROBOT] = g_random->getInt(3, 10); + wanted[M_GRUNT] = g_random->getInt(1, 5); + } + + for (int i = 0; i < MONSTER_COUNT; i++) + { + while (placed[i] < wanted[i]) + { + int x = g_random->getInt(0, width - 1); + int y = g_random->getInt(0, height - 1); + if (x >= spawn_x0 && y >= spawn_y0 && x < spawn_x1 && y < spawn_y1) + { + continue; + } + + if (tiles[y * width + x].is_wall) + { + continue; + } + + placed[i]++; + EmbarkMonster m = EmbarkMonster((MonsterType)i, &tcod_map); + m.x = x; + m.y = y; + + monsters.push_back(m); + } + } + + + +} + +BuildingMap::~BuildingMap() +{ +} + +BSPListener::BSPListener(BuildingMap* map) +{ + this->map = map; + room_n = 0; +} + +bool BSPListener::visitNode(TCODBsp * node, void * user) +{ + if (node->isLeaf()) + { + int x, y, w, h; + w = g_random->getInt(map->min_size, node->w - 2); + h = g_random->getInt(map->min_size, node->h - 2); + x = g_random->getInt(node->x + 1, node->x + node->w - w - 1); + y = g_random->getInt(node->y + 1, node->y + node->h - h - 1); + map->create_room(room_n == 0, x, y, x + w - 1, y + h - 1); + + if (room_n != 0) + { + map->dig(last_x, last_y, x + w / 2, last_y); + map->dig(x + w / 2, last_y, x + w / 2, y + h / 2); + } + + last_x = x + w / 2; + last_y = y + h / 2; + room_n++; + } + + return true; +} diff --git a/src/disembark/BuildingMap.h b/src/disembark/BuildingMap.h new file mode 100644 index 0000000..20a360b --- /dev/null +++ b/src/disembark/BuildingMap.h @@ -0,0 +1,107 @@ +#pragma once +#include +#include "../defines.h" + +struct BuildingTile +{ + bool is_wall; + bool seen_before; + bool seen; + bool seen_by_someone; + + bool blood; + bool alien_blood; + + BuildingTile() + { + is_wall = true; + seen_before = false; + seen = false; + + blood = 0.0f; + alien_blood = 0.0f; + } +}; + +class BuildingMap; + +enum MonsterType +{ + M_ARACHNID, + M_ANT, + M_GOLEM, + M_NEST, + M_ROBOT, + M_GRUNT, + M_ANCIENT, + M_COMPUTER, + + MONSTER_COUNT +}; + +struct EmbarkMonster +{ + TCODPath* path; + float walk_interval; + + float walkt; + int x, y; + float health; + + float attackt; + float attack_interval; + float damage; + + bool seen; + + float t0, t1, t2; + + MonsterType type; + + EmbarkMonster(MonsterType type, TCODMap* map); + + int get_symbol(); + TCODColor get_foreground(); + +}; + + +class BSPListener : public ITCODBspCallback +{ +private: + BuildingMap* map; + int room_n; + int last_x, last_y; +public: + BSPListener(BuildingMap* map); + virtual bool visitNode(TCODBsp* node, void* user) override; + +}; + +class BuildingMap +{ +public: + + bool organic; + + std::vector monsters; + + int min_size, max_size; + + int spawn_x0, spawn_y0, spawn_x1, spawn_y1; + + void dig(int x1, int y1, int x2, int y2); + void create_room(bool first, int x1, int y1, int x2, int y2); + + int width, height; + + TCODMap tcod_map; + + std::vector tiles; + + void draw(TCODConsole& target); + + BuildingMap(int w, int h, bool organic = false); + ~BuildingMap(); +}; + diff --git a/src/disembark/EmbarkScene.cpp b/src/disembark/EmbarkScene.cpp new file mode 100644 index 0000000..d8ca0d5 --- /dev/null +++ b/src/disembark/EmbarkScene.cpp @@ -0,0 +1,1039 @@ +#include "EmbarkScene.h" +#include "../flight/Gamemaster.h" +#include "../flight/FlightScene.h" +#include "../flight/entity/Buildings.h" + +void EmbarkScene::splatter(int x, int y, bool human) +{ + int bx = x + g_random->getInt(-2, 2); + int by = y + g_random->getInt(-2, 2); + + if (bx > 0 && by > 0 && bx < map->width && by < map->height) + { + if (human) + { + map->tiles[by * map->width + bx].blood = true; + } + else + { + map->tiles[by * map->width + bx].alien_blood = true; + } + + } +} + +void EmbarkScene::update_entity(float dt, EmbarkMonster* monster) +{ + if (monster->health > 0.0f) + { + monster->walkt -= dt; + monster->t0 -= dt; + monster->t1 -= dt; + monster->t2 -= dt; + monster->attackt -= dt; + + if (monster->walkt <= 0.0f) + { + int nx, ny; + if (monster->path->walk(&nx, &ny, false)) + { + if (is_free_crew(nx, ny)) + { + monster->x = nx; + monster->y = ny; + } + else + { + // Attack crew at location + if (monster->attackt < 0.0f) + { + for (EmbarkCrew& c : crew) + { + if (c.x == nx && c.y == ny && c.health >= 0.0f) + { + c.hurt(monster->damage, this); + break; + } + } + monster->attackt = monster->attack_interval; + } + } + } + + monster->walkt = monster->walk_interval; + } + + if (monster->t0 < 0.0f) + { + // Go towards players upon being seen + if (monster->seen) + { + int tx, ty; + int idx = g_random->getInt(0, crew.size() - 1); + tx = crew[idx].x; + ty = crew[idx].y; + + monster->path->compute(monster->x, monster->y, tx, ty); + } + + if (monster->type == M_ARACHNID || monster->type == M_ROBOT) + { + monster->t0 = 3.0f; + } + else if (monster->type == M_GOLEM || monster->type == M_GRUNT) + { + monster->t0 = 6.0f; + } + else if (monster->type == M_ANCIENT) + { + monster->t0 = 10.0f; + } + else + { + monster->t0 = 5.0f; + } + + } + + } +} + +void EmbarkScene::spot(int x, int y, EmbarkCrew* crew) +{ + for (int i = 0; i < map->monsters.size(); i++) + { + if (map->monsters[i].x == x && map->monsters[i].y == y && !map->monsters[i].seen) + { + map->monsters[i].seen = true; + switch (map->monsters[i].type) + { + case M_ARACHNID: + crew->gc->speak("Spotted an arachnid."); + break; + case M_ANT: + crew->gc->speak("Spotted a huge ant."); + break; + case M_GOLEM: + crew->gc->speak("Spotted an alien golem."); + break; + case M_NEST: + crew->gc->speak("Spotted the alien nest!"); + break; + case M_ROBOT: + if (!g_master->seen_robot) + { + crew->gc->speak("Wait! The hell is that robot doing here?"); + g_master->seen_robot = true; + } + else + { + crew->gc->speak("Spotted a robot."); + } + break; + case M_GRUNT: + if (!g_master->seen_grunt) + { + crew->gc->speak("Fuck! We haven't seen this one before."); + g_master->seen_grunt = true; + } + else + { + crew->gc->speak("Spotted a humanoid."); + + } + break; + case M_ANCIENT: + crew->gc->speak("Fucking hell, destroy that thing!"); + break; + case M_COMPUTER: + crew->gc->speak("Spotted a computer! It's their commander!"); + break; + default: + crew->gc->speak("Ayy lmao!"); + break; + } + + } + } +} + +void EmbarkScene::handle_mouse() +{ + TCOD_mouse_t mouse = TCODMouse::getStatus(); + + bool found = false; + + EmbarkCrew* n_select = nullptr; + + for (int i = 0; i < crew.size(); i++) + { + if (mouse.cx == crew[i].x && mouse.cy == crew[i].y) + { + // Draw info + TCODConsole::root->printf(61, 61, crew[i].gc->name.c_str()); + TCODConsole::root->printf(62, 63, "Health: %i%%", (int)round(crew[i].health * 100.0f)); + TCODConsole::root->printf(62, 64, "Ammo: %i", crew[i].ammo); + TCODConsole::root->printf(62, 65, "Grenades: %i", crew[i].grenades); + + if (crew[i].hold_fire) + { + TCODConsole::root->printf(62, 67, "Holding Fire", crew[i].ammo); + } + else + { + TCODConsole::root->printf(62, 67, "Firing automatically", crew[i].ammo); + } + + n_select = &crew[i]; + + found = true; + } + } + + if ((mouse.lbutton_pressed && !(g_key.lctrl || g_key.rctrl))) + { + selected = n_select; + } + + if (selected && selected->health > 0.00001f) + { + bool was_set = false; + + TCODPath n_path = TCODPath(&map->tcod_map, 0.0f); + + + // Draw the visibility map + for (int x = 0; x < map->width; x++) + { + for (int y = 0; y < map->height; y++) + { + if (selected->view_map[y * map->width + x]) + { + TCODConsole::root->setCharBackground(x, y, TCODColor(50, 30, 30)); + } + + } + } + + if (is_free(mouse.cx, mouse.cy) && map->tiles[mouse.cy * map->width + mouse.cx].seen_before) + { + // Draw path + n_path.compute(selected->x, selected->y, mouse.cx, mouse.cy); + + int sx = selected->x; + int sy = selected->y; + while (n_path.walk(&sx, &sy, false)) + { + TCODConsole::root->setCharBackground(sx, sy, TCODColor::lightGrey); + } + + if (mouse.mbutton_pressed || (mouse.lbutton_pressed && (g_key.lctrl || g_key.rctrl))) + { + selected->gc->speak("Moving!"); + was_set = true; + selected->to_walk = new TCODPath(&map->tcod_map, 0.0f); + selected->to_walk->compute(selected->x, selected->y, mouse.cx, mouse.cy); + } + } + + if (mouse.rbutton_pressed) + { + for (int i = 0; i < map->monsters.size(); i++) + { + if (map->monsters[i].x == mouse.cx && map->monsters[i].y == mouse.cy) + { + if (selected->view_map[mouse.cy * map->width + mouse.cx]) + { + selected->target = &map->monsters[i]; + selected->gc->speak("Changed target!"); + break; + } + } + } + + } + + if (mouse.rbutton_pressed && g_key.shift) + { + // Throw grenade + if (selected->grenades > 0 && selected->view_map[mouse.cy * map->width + mouse.cx]) + { + selected->gc->speak("Grenade out!"); + selected->grenades--; + + // Spawn grenade + Grenade gnd = Grenade(); + gnd.x = mouse.cx; + gnd.y = mouse.cy; + gnd.timer = 2.0f; + gnd.exploded = false; + + g_soloud->play(grenade_land, 2.0f); + + grenades.push_back(gnd); + } + } + + } + + + + if (found) + { + TCODConsole::root->setCharBackground(mouse.cx, mouse.cy, TCODColor::white); + TCODConsole::root->setCharForeground(mouse.cx, mouse.cy, TCODColor::black); + } +} + +void EmbarkScene::update_crew(float dt, EmbarkCrew* crew) +{ + if (crew->health > 0.00001f) + { + crew->walk_t -= dt; + crew->fired_timer -= dt; + if (crew->fired_timer < 0.0f) + { + crew->fired_timer = 0.0f; + } + + if (crew->walk_t < 0.0f) + { + crew->walk_t = 0.4f; + if (crew->to_walk != nullptr) + { + int nx, ny; + if (!crew->to_walk->walk(&nx, &ny, true)) + { + delete crew->to_walk; + crew->to_walk = nullptr; + } + else + { + crew->x = nx; + crew->y = ny; + } + } + } + + if (crew->target == nullptr && !crew->hold_fire) + { + // Find a new target in field of view + for (EmbarkMonster& monster : map->monsters) + { + if (crew->view_map[monster.y * map->width + monster.x] && monster.health > 0.0f) + { + crew->target = &monster; + //crew->gc->speak("Acquired new target"); + } + } + } + + if (crew->target != nullptr) + { + if (crew->target->health <= 0.0f) + { + crew->target = nullptr; + } + else if (!crew->view_map[crew->target->y * map->width + crew->target->x]) + { + //crew->gc->speak("Target lost"); + crew->target = nullptr; + } + else if (crew->fire(crew->target->x, crew->target->y, dt, &gunshot)) + { + // Damage + if (crew->target->type == M_ROBOT || crew->target->type == M_GRUNT + || crew->target->type == M_ANCIENT || crew->target->type == M_COMPUTER) + { + } + else + { + splatter(crew->target->x, crew->target->y, false); + } + + crew->target->health -= 0.25f; + if (crew->target->health <= 0.0f) + { + if (crew->target->type == M_ROBOT || crew->target->type == M_GRUNT + || crew->target->type == M_ANCIENT || crew->target->type == M_COMPUTER) + { + g_soloud->play(die_metal, 2.0f); + } + else + { + g_soloud->play(die_alien, 2.0f); + } + + if (crew->target->type == M_NEST + || crew->target->type == M_COMPUTER + || crew->target->type == M_ANCIENT) + { + crew->gc->speak("Great job! Lets get out of here."); + } + else + { + crew->gc->speak("Target neutralized."); + } + + } + else + { + if (crew->target->type == M_ROBOT || crew->target->type == M_GRUNT + || crew->target->type == M_ANCIENT || crew->target->type == M_COMPUTER) + { + g_soloud->play(hurt_metal, 2.0f); + } + else + { + g_soloud->play(hurt_alien, 1.0f); + } + + } + } + } + } +} + +void EmbarkScene::update(float dt) +{ + for (int i = 0; i < map->monsters.size(); i++) + { + if ((map->monsters[i].type == M_NEST || + map->monsters[i].type == M_COMPUTER || + map->monsters[i].type == M_ANCIENT) && map->monsters[i].health <= 0.0f) + { + if(map->monsters[i].type == M_COMPUTER && !mission_done) + { + g_popup->show("The computer had a databank inside which was uploaded automatically to the\n\ +submarine. It will be scanned by the fatherland. Good job!"); + + std::string coords = ""; + for (FlightEntity* ent : g_master->flight_scene->map.entities) + { + if (ent->get_type() == E_STATION && !((Building*)ent)->is_explored()) + { + coords += std::to_string((int)floor(ent->get_x())); + coords += ", "; + coords += std::to_string((int)floor(ent->get_x())); + break; + } + } + + g_master->flight_scene->vehicle.radio->push_message("We decoded the data-bank and found\ +new coordinates: " + coords); + } + + if (map->monsters[i].type == M_ANCIENT && !mission_done) + { + g_popup->show("Upon killing the huge robot, lights started flickering in all walls.\n\ +Shortly after, a strong blast was heard. Something weird was going on outside, as the event was reported\ + by bases all over Jupiter-2. We had awoken something which was not meant to be perturbed.\ +\n\n\n\n\ +(To be continued, feel free to have fun exploring the world and fighting enemies!)"); + } + + mission_done = true; + } + } + + t += dt; + + /*if (g_key.vk == TCODK_F5) + { + finished = true; + + /*crew[0].health = 0.0f; + crew[1].health = 0.0f; + crew[2].health = 0.0f; + crew[3].health = 0.0f; + }*/ + + if (!is_init) + { + if (!map->organic && g_master->clear_station_count == 3) + { + + int it = 0; + + float max_dist = -100.0f; + int max_x = 0; + int max_y = 0; + + while (it < 1000) + { + int x = g_random->getInt(0, map->width - 1); + int y = g_random->getInt(0, map->height - 1); + float dx = x - map->spawn_x0; + float dy = y - map->spawn_y0; + float dist = sqrt(dx * dx + dy * dy); + + if (dist > max_dist && is_free(x, y)) + { + max_dist = dist; + max_x = x; + max_y = y; + } + + it++; + } + std::cout << "Spawn " << max_x << ", " << max_y << std::endl; + + EmbarkMonster m = EmbarkMonster(M_ANCIENT, &map->tcod_map); + m.x = max_x; + m.y = max_y; + map->monsters.push_back(m); + + for (auto it = map->monsters.begin(); it != map->monsters.end(); it++) + { + if (it->type == M_COMPUTER) + { + map->monsters.erase(it); + break; + } + } + } + + // Position crew + for (int i = 0; i < crew.size(); i++) + { + int its = 0; + int x = -1, y = -1; + while (!is_free(x, y) && its < 100) + { + x = g_random->getInt(map->spawn_x0, map->spawn_x1); + y = g_random->getInt(map->spawn_y0, map->spawn_y1); + its++; + } + + crew[i].x = x; + crew[i].y = y; + crew[i].view_map.resize(map->width * map->height); + } + + is_init = true; + } + + + if (map == nullptr) + { + return; + } + + blinkt -= dt; + if (blinkt < 0.0f) + { + blink = !blink; + blinkt = 0.5f; + } + + for (int i = 0; i < crew.size(); i++) + { + update_crew(dt, &crew[i]); + } + + for (int i = 0; i < map->monsters.size(); i++) + { + update_entity(dt, &map->monsters[i]); + } + + for (int i = 0; i < grenades.size(); i++) + { + grenades[i].timer -= dt; + if (grenades[i].timer <= 0.0f && !grenades[i].exploded) + { + // Damage around + grenades[i].exploded = true; + g_soloud->play(grenade, 3.0f); + + for (EmbarkMonster& mnst : map->monsters) + { + float dx = mnst.x - grenades[i].x; + float dy = mnst.y - grenades[i].y; + + float dist = sqrt(dx * dx + dy * dy); + + if (dist < 3.0f) + { + mnst.health -= 1.0f; + } + } + } + } + +} + +void EmbarkScene::render() +{ + if (map == nullptr) + { + return; + } + + for (int x = 0; x < map->width; x++) + { + for (int y = 0; y < map->height; y++) + { + map->tiles[y * map->width + x].seen_by_someone = false; + } + } + + // Compute visibility + for (int i = 0; i < crew.size(); i++) + { + map->tcod_map.computeFov(crew[i].x, crew[i].y, 18, true, FOV_DIAMOND); + + for (int x = 0; x < map->width; x++) + { + for (int y = 0; y < map->height; y++) + { + if (map->tcod_map.isInFov(x, y)) + { + if (!map->tiles[y * map->width + x].seen_before) + { + spot(x, y, &crew[i]); + } + + map->tiles[y * map->width + x].seen_by_someone = true; + map->tiles[y * map->width + x].seen_before = true; + crew[i].view_map[y * map->width + x] = true; + } + else + { + crew[i].view_map[y * map->width + x] = false; + } + } + } + } + + for (int x = 0; x < map->width; x++) + { + for (int y = 0; y < map->height; y++) + { + if (map->tiles[y * map->width + x].seen_by_someone) + { + map->tiles[y * map->width + x].seen = true; + } + else + { + map->tiles[y * map->width + x].seen = false; + } + } + } + + map->draw(*TCODConsole::root); + + // Check for all alive crew to be in square + int alive = 0; + int alive_in_square = 0; + for (EmbarkCrew& c : crew) + { + if (c.health > 0.0001f) + { + alive++; + + if (c.x >= map->spawn_x0 && c.y >= map->spawn_y0 && c.x <= map->spawn_x1 && c.y <= map->spawn_y1) + { + alive_in_square++; + } + } + } + + if (alive == 0) + { + finished = true; + } + + + if (mission_done) + { + float gf = (sin(t) + 1.0f) * 0.5f; + int g = (int)round(gf * 255); + TCODColor col = TCODColor(0, g, 0); + for (int x = map->spawn_x0; x <= map->spawn_x1; x++) + { + for (int y = map->spawn_y0; y <= map->spawn_y1; y++) + { + TCODConsole::root->setCharBackground(x, y, col); + } + } + + + if (alive_in_square == alive) + { + finished = true; + } + else + { + finished = false; + } + } + + + handle_mouse(); + + // Draw crew + for (int i = 0; i < crew.size(); i++) + { + if (&crew[i] == selected && blink) + { + TCODConsole::root->setChar(crew[i].x, crew[i].y, 'X'); + } + else + { + TCODConsole::root->setChar(crew[i].x, crew[i].y, crew[i].gc->is_captain ? 2 : 1); + } + + if (crew[i].fired_timer > 0.0f) + { + //TCODConsole::root->setCharForeground(crew[i].x, crew[i].y, TCODColor::yellow); + // Draw bullet path in background color + TCODLine line = TCODLine(); + line.init(crew[i].x, crew[i].y, crew[i].fx, crew[i].fy); + int x, y; + while (!line.step(&x, &y)) + { + TCODConsole::root->setCharBackground(x, y, TCODColor::lightYellow); + } + } + + + TCODConsole::root->setCharForeground(crew[i].x, crew[i].y, TCODColor::white); + + if (crew[i].health <= 0.4f && crew[i].health > 0.0f && !blink) + { + TCODConsole::root->setCharForeground(crew[i].x, crew[i].y, TCODColor::darkRed); + } + + if (crew[i].health <= 0.0f) + { + TCODConsole::root->setCharForeground(crew[i].x, crew[i].y, TCODColor::red); + } + + + + + } + + // Draw grenades + for (int i = 0; i < grenades.size(); i++) + { + if (grenades[i].timer <= 1.0f && !grenades[i].exploded) + { + TCODConsole::root->setChar(grenades[i].x, grenades[i].y, 147); + } + + if (grenades[i].timer >= -1.0f && grenades[i].exploded) + { + float itim = 1.0f + grenades[i].timer; + // Draw explosion circle + + for (int x = 0; x < map->width; x++) + { + for (int y = 0; y < map->height; y++) + { + float dx = x - grenades[i].x; + float dy = y - grenades[i].y; + + float dist = sqrt(dx * dx + dy * dy); + + if (dist < itim * 10.0f) + { + TCODConsole::root->setCharBackground(x, y, TCODColor::lightGrey); + TCODConsole::root->setChar(x, y, '+'); + TCODConsole::root->setCharForeground(x, y, TCODColor::white); + } + } + } + } + } + + // Draw the status box + for (int x = 0; x < WIDTH; x++) + { + TCODConsole::root->setChar(x, 60, 205); + TCODConsole::root->setCharForeground(x, 60, TCODColor::lightGrey); + } + + g_status->draw(0, 60, WIDTH, HEIGHT); + + // Draw the info box + + for (int y = 60; y < HEIGHT; y++) + { + TCODConsole::root->setChar(60, y, 186); + TCODConsole::root->setCharForeground(60, y, TCODColor::lightGrey); + } + + TCODConsole::root->setChar(60, 60, 203); + TCODConsole::root->setCharForeground(60, 60, TCODColor::lightGrey); + + + +} + +bool EmbarkScene::is_free(int x, int y, int ignore_ent) +{ + if (x < 0 || y < 0 || x >= map->width || y >= map->height) + { + return false; + } + + if (map->tiles[y * map->width + x].is_wall) + { + return false; + } + + for (int i = 0; i < crew.size(); i++) + { + if (crew[i].x == x && crew[i].y == y && crew[i].health > 0.00001f) + { + return false; + } + } + + for (int i = 0; i < map->monsters.size(); i++) + { + if (map->monsters[i].x == x && map->monsters[i].y == y && i != ignore_ent + && map->monsters[i].health > 0.00001f) + { + return false; + } + } + + return true; +} + +bool EmbarkScene::is_free_crew(int x, int y) +{ + if (x < 0 || y < 0 || x >= map->width || y >= map->height) + { + return false; + } + + if (map->tiles[y * map->width + x].is_wall) + { + return false; + } + + for (int i = 0; i < crew.size(); i++) + { + if (crew[i].x == x && crew[i].y == y && crew[i].health > 0.00001f) + { + return false; + } + } +} + +void EmbarkScene::restart() +{ + crew.clear(); + grenades.clear(); + selected = nullptr; + finished = false; + t = 0.0f; + mission_done = false; + is_init = false; + map = nullptr; +} + +EmbarkScene::EmbarkScene() +{ + finished = false; + mission_done = false; + + map = nullptr; + is_init = false; + blinkt = 0.0f; + + gunshot.load("gunshot.wav"); + grenade.load("grenade.wav"); + grenade_land.load("grenade_land.wav"); + hurt.load("hurt.wav"); + hurt_alien.load("hurt_alien.wav"); + hurt_metal.load("hurt_metal.wav"); + + die_alien.load("alien_die.wav"); + die_man.load("man_die.wav"); + die_metal.load("metal_die.wav"); +} + + +EmbarkScene::~EmbarkScene() +{ +} + +EmbarkMonster::EmbarkMonster(MonsterType type, TCODMap* map) +{ + path = new TCODPath(map, 0.0f); + + this->seen = false; + this->type = type; + this->walkt = 0.0f; + t0 = 0.0f; + t1 = 0.0f; + t2 = 0.0f; + + switch (type) + { + case M_ARACHNID: + health = 0.5f; + walk_interval = 0.05f; + damage = 0.1f; + attack_interval = 0.8f; + break; + case M_ANT: + health = 0.8f; + walk_interval = 0.15f; + damage = 0.25f; + attack_interval = 0.8f; + break; + case M_GOLEM: + health = 5.0f; + walk_interval = 0.6f; + damage = 0.3f; + attack_interval = 2.0f; + break; + case M_NEST: + health = 8.0f; + walk_interval = 10000000000.0f; + damage = 0.0f; + break; + case M_ROBOT: + health = 1.5f; + walk_interval = 0.25f; + damage = 0.1f; + attack_interval = 0.5f; + break; + case M_GRUNT: + health = 3.0f; + walk_interval = 0.5f; + damage = 0.5f; + attack_interval = 1.5f; + break; + case M_ANCIENT: + health = 11.0f; + walk_interval = 0.7f; + damage = 0.8f; + attack_interval = 1.0f; + break; + case M_COMPUTER: + health = 8.0f; + walk_interval = 10000000000.0f; + damage = 0.0f; + break; + } +} + +int EmbarkMonster::get_symbol() +{ + switch (type) + { + case M_ARACHNID: + return 'x'; + case M_ANT: + return 12; + case M_GOLEM: + return 227; + case M_NEST: + return 15; + case M_ROBOT: + return 232; + case M_GRUNT: + return 228; + case M_ANCIENT: + return 234; + case M_COMPUTER: + return 241; + } +} + +TCODColor EmbarkMonster::get_foreground() +{ + if (type == M_ROBOT || type == M_GRUNT || type == M_COMPUTER) + { + return TCODColor(102, 155, 255); + } + else if (type == M_ANCIENT) + { + return TCODColor(205, 133, 220); + } + else + { + return TCODColor(140, 102, 255); + } + +} + +void EmbarkCrew::hurt(float dmg, EmbarkScene* scene) +{ + // Attack + health -= dmg; + if (health < 0.0f) + { + health = 0.0f; + + scene->splatter(x, y, true); + scene->splatter(x, y, true); + g_soloud->play(scene->die_man, 2.0f); + } + else + { + scene->splatter(x, y, true); + g_soloud->play(scene->hurt, 2.0f); + } + + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + gc->speak("I'm Hurt!"); + } +} + +bool EmbarkCrew::fire(int tx, int ty, float dt, SoLoud::Wav * sound) +{ + float dx = tx - x; + float dy = ty - y; + float dist = sqrt(dx * dx + dy * dy); + + float fail_chance = 0.6f; + + if (dist <= 1.0f) + { + fail_chance = 0.1f; + } + else if (dist <= 3.0f) + { + fail_chance = 0.3f; + } + + + int rx = tx; int ry = ty; + if (g_random->getFloat(0.0f, 1.0f) < fail_chance) + { + // Miss (may actually not miss, but very low prob) + rx += g_random->getInt(-2, 2); + ry += g_random->getInt(-2, 2); + } + + + fire_timer -= dt; + if (fire_timer < 0.0f) + { + g_soloud->play(*sound, 1.0f); + + TCODConsole::root->setCharForeground(x, y, TCODColor::yellow); + + // Do the actual shot + + fire_timer = g_random->getFloat(1.0f, 3.0f); + fired_timer = g_random->getFloat(0.3f, 0.5f); + fx = rx; + fy = ry; + + if (rx == tx && ry == ty) + { + return true; + } + } + + return false; +} diff --git a/src/disembark/EmbarkScene.h b/src/disembark/EmbarkScene.h new file mode 100644 index 0000000..fd2036d --- /dev/null +++ b/src/disembark/EmbarkScene.h @@ -0,0 +1,115 @@ +#pragma once +#include "BuildingMap.h" +#include "../Crewmember.h" + +class EmbarkScene; + +struct EmbarkCrew +{ + Crewmember* gc; + EmbarkMonster* target; + + int x, y; + + float health; + + bool hold_fire; + int ammo; + int grenades; + + float reload_timer; + float fire_timer; + + TCODPath* to_walk; + float walk_t; + + float fired_timer; + int fx, fy; + + std::vector view_map; + + void hurt(float dmg, EmbarkScene* scene); + + EmbarkCrew() + { + health = 1.0f; + hold_fire = false; + ammo = 8; + grenades = g_random->getInt(1, 3); + reload_timer = 0.0f; + gc = nullptr; + walk_t = 0.0f; + to_walk = nullptr; + target = nullptr; + fire_timer = 0.0f; + fired_timer = 0.0f; + } + + bool fire(int tx, int ty, float dt, SoLoud::Wav* sound); + +}; + +struct Grenade +{ + int x, y; + float timer; + bool exploded; + +}; + +class EmbarkScene +{ +public: + + bool finished; + + float t; + + bool mission_done; + + void splatter(int x, int y, bool human); + + void update_entity(float dt, EmbarkMonster* monster); + + void spot(int x, int y, EmbarkCrew* crew); + + bool blink; + float blinkt; + + + EmbarkCrew* selected; + + void handle_mouse(); + void update_crew(float dt, EmbarkCrew* crew); + + SoLoud::Wav gunshot; + SoLoud::Wav grenade; + SoLoud::Wav grenade_land; + SoLoud::Wav hurt; + SoLoud::Wav hurt_alien; + SoLoud::Wav hurt_metal; + SoLoud::Wav die_alien; + SoLoud::Wav die_man; + SoLoud::Wav die_metal; + + + bool is_init; + + std::vector crew; + std::vector grenades; + + BuildingMap* map; + + void update(float dt); + void render(); + + bool is_free(int x, int y, int ignore_ent = -1); + + bool is_free_crew(int x, int y); + + void restart(); + + EmbarkScene(); + ~EmbarkScene(); +}; + diff --git a/src/flight/FlightMap.cpp b/src/flight/FlightMap.cpp index ca186b1..899ff84 100644 --- a/src/flight/FlightMap.cpp +++ b/src/flight/FlightMap.cpp @@ -1,5 +1,6 @@ #include "FlightMap.h" - +#include "entity/Buildings.h" +#include "entity/Monsters.h" static float grad_func(float x) { @@ -84,12 +85,52 @@ MapTile FlightMap::get_subtile(float x, float y) return tile; } -std::vector& FlightMap::get_entities() +std::vector& FlightMap::get_entities() { return entities; } -FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), sub_tile_noise(2) +/* +int FlightMap::get_entity_symbol(EntityType type) +{ + switch (type) + { + case E_NEST: return 15; + case E_STATION: return 234; + case E_BASE: return 127; + case E_CRAWLER: return 157; + case E_WORM: return 126; + case E_GUARDIAN: return 236; + case E_SERPENT: return 21; + case E_BOMB: return 155; + case E_BITER: return 224; + case E_SUBMARINE: return '!'; + default: return '?'; + } +} +*/ + +template +void create_entities(std::vector* target, FlightMap* map, FlightScene* scene) +{ + int n = 0; + while (n < NUM) + { + int x = g_random->getInt(0, map->width - 1); + int y = g_random->getInt(0, map->height - 1); + + if (map->tiles[y * map->width + x] != WALL) + { + T* crawler = new T(); + crawler->set_position((float)x + 0.5f, (float)y + 0.5f); + crawler->init(map, scene); + target->push_back(crawler); + n++; + } + } +} + +FlightMap::FlightMap(int width, int height, size_t seed, FlightScene* scene) : vmap(width, height), sub_tile_noise(2) { this->width = width; this->height = height; @@ -105,11 +146,10 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), } } - TCODRandom rng = TCODRandom(seed); int dx = width / 2, dy = height / 2; int open = 0; - int target_open = rng.getFloat(0.3, 0.5) * width * height; + int target_open = g_random->getFloat(0.3, 0.5) * width * height; int max_steps = 100000; int steps = 0; @@ -121,9 +161,9 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), open++; } - if (rng.getFloat(0.0, 1.0) >= 0.5) + if (g_random->getFloat(0.0, 1.0) >= 0.5) { - if (rng.getFloat(0.0, 1.0) >= 0.5) + if (g_random->getFloat(0.0, 1.0) >= 0.5) { dx++; } @@ -134,7 +174,7 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), } else { - if (rng.getFloat(0.0, 1.0) >= 0.5) + if (g_random->getFloat(0.0, 1.0) >= 0.5) { dy++; } @@ -180,7 +220,7 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), } } - TCODNoise noise = TCODNoise(2, TCOD_NOISE_PERLIN); + TCODNoise noise = TCODNoise(2, g_random, TCOD_NOISE_PERLIN); // Generate air map for (int x = 0; x < width; x++) @@ -200,11 +240,11 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), int stations = 0; - while (stations < 4) + while (stations < 3) { // Place stations far away from the center - int x = rng.getInt(0, width - 1); - int y = rng.getInt(0, height - 1); + int x = g_random->getInt(0, width - 1); + int y = g_random->getInt(0, height - 1); float xf = (float)(x - width / 2) / (float)(width / 2); float yf = (float)(y - height / 2) / (float)(height / 2); @@ -215,7 +255,10 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), { tiles[y * width + x] = STATION; stations++; - entities.push_back(Entity(E_STATION, (float)x + 0.5f, (float)x + 0.5f)); + EntityStation* n = new EntityStation(); + n->set_position((float)x + 0.5f, (float)y + 0.5f); + n->init(this, scene); + entities.push_back(n); } } @@ -226,14 +269,17 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), while (nests < 15) { // Place stations far away from the center - int x = rng.getInt(0, width - 1); - int y = rng.getInt(0, height - 1); + int x = g_random->getInt(0, width - 1); + int y = g_random->getInt(0, height - 1); if (tiles[y * width + x] != WALL) { tiles[y * width + x] = NEST; nests++; - entities.push_back(Entity(E_NEST, (float)x + 0.5f, (float)x + 0.5f)); + EntityNest* n = new EntityNest(); + n->set_position((float)x + 0.5f, (float)y + 0.5f); + n->init(this, scene); + entities.push_back(n); } } @@ -243,15 +289,18 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), while (bases < 5) { // Place stations far away from the center - int x = rng.getInt(0, width - 1); - int y = rng.getInt(0, height - 1); + int x = g_random->getInt(0, width - 1); + int y = g_random->getInt(0, height - 1); if (tiles[y * width + x] != WALL) { tiles[y * width + x] = BASE; bases++; lbases.push_back(std::make_pair(x, y)); - entities.push_back(Entity(E_BASE, (float)x + 0.5f, (float)x + 0.5f)); + EntityBase* n = new EntityBase(); + n->set_position((float)x + 0.5f, (float)y + 0.5f); + n->init(this, scene); + entities.push_back(n); } } @@ -259,9 +308,63 @@ FlightMap::FlightMap(int width, int height, size_t seed) : vmap(width, height), tiles[(height / 2) * height + (width / 2)] = BASE; lbases.push_back(std::make_pair(width / 2, height / 2)); - entities.push_back(Entity(E_BASE, (float)(width / 2) + 0.5f, (float)(height / 2) + 0.5f)); + EntityBase* n = new EntityBase(); + n->set_position((float)(width / 2) + 0.5f, (float)(height / 2) + 0.5f); + n->init(this, scene); + entities.push_back(n); + + // Spawn some entities across the map + if (width > 20) + { + create_entities(&entities, this, scene); + create_entities(&entities, this, scene); + create_entities(&entities, this, scene); + } + + + for (FlightEntity* ent : entities) + { + if (ent->get_type() == E_NEST) + { + // Spawn a guardian near it + int guardians = g_random->getInt(-1, 2); + int g = 0; + while (g < guardians) + { + // Place stations far away from the center + float ox = g_random->getFloat(-3.0f, 3.0f); + float oy = g_random->getInt(-3.0f, 3.0f); + + ox += ent->get_x(); + oy += ent->get_y(); + + if (get_tile((int)floor(ox), (int)floor(oy)) != WALL) + { + EntityGuardian* ent = new EntityGuardian(); + ent->init(this, scene); + ent->set_position(ox, oy); + entities.push_back(ent); + + g++; + } + + } + } + } + + /* + REMEMBER TO ADD SOUND!!! + EntityBomb* ent = new EntityBomb(); + ent->init(this, scene); + ent->set_position(51.0f, 50.5f); + entities.push_back(ent);*/ + + /*EntitySerpent* ent = new EntitySerpent(); + ent->init(this, scene); + ent->set_position(51.0f, 50.5f); + entities.push_back(ent); + */ - // Create all default entities } FlightMap::~FlightMap() diff --git a/src/flight/FlightMap.h b/src/flight/FlightMap.h index e0272ae..b52ea81 100644 --- a/src/flight/FlightMap.h +++ b/src/flight/FlightMap.h @@ -1,6 +1,9 @@ #pragma once #include #include +#include "entity/FlightEntity.h" + +class FlightScene; enum MapTile { @@ -12,31 +15,12 @@ enum MapTile BASE //< Fatherland base, not derelict station }; -enum EntityType -{ - E_NEST, - E_STATION, - E_BASE -}; - -struct Entity -{ - EntityType type; - float x, y; - - Entity(EntityType type, float x, float y) - { - this->type = type; - this->x = x; - this->y = y; - } -}; class FlightMap { public: - std::vector entities; + std::vector entities; TCODNoise sub_tile_noise; @@ -62,9 +46,9 @@ class FlightMap MapTile get_subtile(float x, float y); - std::vector& get_entities(); + std::vector& get_entities(); - FlightMap(int width, int height, size_t seed); + FlightMap(int width, int height, size_t seed, FlightScene* scene); ~FlightMap(); }; diff --git a/src/flight/FlightScene.cpp b/src/flight/FlightScene.cpp index 42271f2..c1c4cac 100644 --- a/src/flight/FlightScene.cpp +++ b/src/flight/FlightScene.cpp @@ -1,15 +1,34 @@ #include "FlightScene.h" - +#include "entity/Buildings.h" void FlightScene::update(float dt) { + underwater.setVolume(0.6f); + t += dt; - if (g_key.vk == TCODK_F11) + for (int i = 0; i < map.entities.size(); i++) { - help_open = !help_open; + if (map.entities[i]->is_alive()) + { + map.entities[i]->update(dt); + } + } + + for (auto it = expl_effects.begin(); it != expl_effects.end();) + { + it->t -= dt; + + if (it->t < 0.0f) + { + it = expl_effects.erase(it); + } + else + { + it++; + } } @@ -35,46 +54,58 @@ We are now going to send the location of all our outposts.\n"; sent_start_message = true; } - if (g_key.vk == TCODK_KP8) + if (g_key.vk == TCODK_KP8 || g_key.c == 'k') { vehicle.move_order(N); } - if (g_key.vk == TCODK_KP2) + if (g_key.vk == TCODK_KP2 || g_key.c == 'j') { vehicle.move_order(S); } - if (g_key.vk == TCODK_KP6) + if (g_key.vk == TCODK_KP6 || g_key.c == 'l') { vehicle.move_order(E); } - if (g_key.vk == TCODK_KP4) + if (g_key.vk == TCODK_KP4 || g_key.c == 'h') { vehicle.move_order(W); } - if (g_key.vk == TCODK_KP1) + if (g_key.vk == TCODK_KP1 || g_key.c == 'b') { vehicle.move_order(SW); } - if (g_key.vk == TCODK_KP7) + if (g_key.vk == TCODK_KP7 || g_key.c == 'y') { vehicle.move_order(NW); } - if (g_key.vk == TCODK_KP9) + if (g_key.vk == TCODK_KP9 || g_key.c == 'u') { vehicle.move_order(NE); } - if (g_key.vk == TCODK_KP3) + if (g_key.vk == TCODK_KP3 || g_key.c == 'n') { vehicle.move_order(SE); } + if (g_key.c == 'r') + { + for (int i = 0; i < vehicle.crew.size(); i++) + { + vehicle.crew[i].path_to(vehicle.crew[i].cx, vehicle.crew[i].cy, *vehicle.tcod_map); + + if (vehicle.crew[i].gc->is_captain) + { + vehicle.crew[i].gc->speak("Everybody return to positions!"); + } + } + } if (g_key.vk == TCODK_1) { @@ -102,14 +133,68 @@ We are now going to send the location of all our outposts.\n"; } + if (g_key.c == 'f') + { + vehicle.targeting->fire_torpedo(); + } + + if (g_key.c == 'e') + { + // Electric shock + vehicle.electric_shock(); + } + + for (FlightEntity* ent : map.entities) + { + if (ent->get_type() == E_NEST || ent->get_type() == E_STATION) + { + float dx = vehicle.x - ent->get_x(); + float dy = vehicle.y - ent->get_y(); + float dist = sqrt(dx * dx + dy * dy); + + if (dist < 0.2f && !((Building*)ent)->is_explored()) + { + possible_embark = ent; + break; + } + } + /*if (ent->get_type() == E_NEST && !((Building*)ent)->is_explored()) + { + possible_embark = ent; + }*/ + } + if (possible_embark != nullptr) + { + if (g_key.c == 'g') + { + embark_target = possible_embark; + ((Building*)possible_embark)->explore(); + } + } } void FlightScene::render() { - TCODConsole::root->setDefaultBackground(TCODColor(4, 24, 30)); + float expl_power = 0.0f; + for (int i = 0; i < expl_effects.size(); i++) + { + if (expl_effects[i].t >= 0.0f) + { + expl_power += expl_effects[i].t / expl_effects[i].dist; + } + } + + if (expl_power > 1.0f) + { + expl_power = 1.0f; + } + + int expl_poweri = (int)(expl_power * 100.0f); + + TCODConsole::root->setDefaultBackground(TCODColor(4 + expl_poweri, 24 + expl_poweri, 30 + expl_poweri)); TCODConsole::root->clear(); TCODConsole::root->setDefaultBackground(TCODColor::black); @@ -130,32 +215,101 @@ void FlightScene::render() // Draw window vehicle.draw_window(TCODConsole::root); - - if (help_open) + + if (possible_embark != nullptr && vehicle.blink) + { + TCODConsole::root->setDefaultForeground(TCODColor::lightYellow); + TCODConsole::root->printf(0, 0, "Embark is possible"); + TCODConsole::root->printf(0, 1, "Press 'g' to embark!"); + TCODConsole::root->setDefaultForeground(TCODColor::white); + } + +} + +void FlightScene::explosion(float x, float y, float power) +{ + for (int i = 0; i < map.entities.size(); i++) { - Drawing::draw_window(TCODConsole::root, 0, 0, 40, HEIGHT - 1, "Help", true); + float dx = map.entities[i]->get_x() - x; + float dy = map.entities[i]->get_y() - y; + + float dist = sqrt(dx * dx + dy * dy); - TCODConsole::root->setDefaultForeground(TCODColor::lightGrey); - TCODConsole::root->printRect(1, 1, 39, HEIGHT - 2, help_str.c_str()); + if (dist < 0.1f) + { + dist = 0.1f; + } + + float damage = power / dist; + + if (damage > 1.0f && map.entities[i]->is_alive()) + { + map.entities[i]->damage(damage - 0.25f); + } } + // Play sound + float dx = vehicle.x - x; + float dy = vehicle.y - y; + float dist = sqrt(dx * dx + dy * dy); + if (dist < 0.15f) + { + dist = 0.15f; + } + if (dist >= 1.5f) + { + g_soloud->play(explo_far_dist, 2.0f); + } + else if (dist >= 0.6f) + { + g_soloud->play(explo_med_dist, 2.0f); + } + else + { + g_soloud->play(explo_near_dist, 1.0f); + if (dist <= 0.4f) + { + g_soloud->play(explo_hit, 2.5f); + // Damage the vehicle + vehicle.damage(power * 25.0f / (dist / 0.15f)); + } + } + + ExplosionEffect fx; + fx.x = x; + fx.y = y; + fx.t = power * 2.0f; + fx.dist = dist; + expl_effects.push_back(fx); +} + +void FlightScene::shut_up() +{ + underwater.setVolume(0.0f); } -FlightScene::FlightScene() : map(map_size, map_size, 11234), vehicle(&map) +FlightScene::FlightScene() : map(map_size, map_size, 11234, this), vehicle(&map) { + embark_target = nullptr; + underwater.load("underwater.wav"); underwater.setLooping(true); + explo_far_dist.load("explo_far_dist.wav"); + explo_med_dist.load("explo_med_dist.wav"); + explo_near_dist.load("explo_near_dist.wav"); + explo_hit.load("collide.wav"); + this->soloud = g_soloud; this->status = g_status; soloud->play(underwater); - help_str = Help::help_main; + vehicle.scene = this; } diff --git a/src/flight/FlightScene.h b/src/flight/FlightScene.h index 3e1055d..3dbba68 100644 --- a/src/flight/FlightScene.h +++ b/src/flight/FlightScene.h @@ -10,13 +10,20 @@ #include "../Help.h" #include "../vehicle/Vehicle.h" -constexpr int map_size = 500; +constexpr int map_size = 100; +struct ExplosionEffect +{ + float x, y, t, dist; +}; class FlightScene { public: + FlightEntity* embark_target; + FlightEntity* possible_embark; + float t = 0.0f; bool sent_start_message = false; @@ -24,20 +31,27 @@ class FlightScene SoLoud::Soloud* soloud; SoLoud::Wav underwater; + SoLoud::Wav explo_near_dist; + SoLoud::Wav explo_far_dist; + SoLoud::Wav explo_med_dist; + SoLoud::Wav explo_hit; FlightMap map; Vehicle vehicle; bool help_open; - std::string help_str; + std::vector expl_effects; Status* status; void update(float dt); void render(); + void explosion(float x, float y, float power); + + void shut_up(); FlightScene(); ~FlightScene(); diff --git a/src/flight/Gamemaster.cpp b/src/flight/Gamemaster.cpp index a2e033f..f64544b 100644 --- a/src/flight/Gamemaster.cpp +++ b/src/flight/Gamemaster.cpp @@ -1,15 +1,261 @@ #include "Gamemaster.h" +#include "FlightScene.h" +#include "../disembark/EmbarkScene.h" +#include "entity/Buildings.h" +void Gamemaster::update(float dt) +{ + if (!gameover) + { + if (!embarked) + { + flight_scene->update(dt); + } + else + { + embark_scene->update(dt); + } + } + if (embarked) + { + flight_scene->shut_up(); -void Gamemaster::update(float dt) + if (embark_scene->finished) + { + if (g_master->clear_nest_count == 2 && g_master->clear_station_count == 0) + { + std::string coords = ""; + for (FlightEntity* ent : g_master->flight_scene->map.entities) + { + if (ent->get_type() == E_STATION && !((Building*)ent)->is_explored()) + { + coords += std::to_string((int)floor(ent->get_x())); + coords += ", "; + coords += std::to_string((int)floor(ent->get_x())); + break; + } + } + + flight_scene->vehicle.radio->push_message("\ +Greetings from the fatherland. We have received information from other submarines about strange and massive\ + structures. You may have already seen some of them. They look like a tower on the sonar.\n\n\ +One of these has been reported on coordinates: " + coords + "\n\n\ +You should continue clearing nests, but it's of our interest to get more information about these structures.\n\ +We don't know what dangers may lie there, be careful, commander."); + + } + else if (g_master->clear_nest_count == 3) + { + flight_scene->vehicle.radio->push_message("\ +Greetings from the fatherland. Our leader has given orders to all submarines to explore these huge structures.\n\ +Keep your vehicle focused on this task.\n\ +Proceed with caution, commander."); + } + + embarked = false; + int alive = 0; + bool reassign_captain = false; + std::vector dead; + // Handle crew changes (crew could have died) + for (EmbarkCrew& c : embark_scene->crew) + { + if (c.health <= 0.0f) + { + // Remove from vehicle + auto& vcrew = flight_scene->vehicle.crew; + dead.push_back(c.gc->name); + for (auto it = vcrew.begin(); it != vcrew.end(); ) + { + if (it->gc == c.gc) + { + if (it->gc->is_captain) + { + reassign_captain = true; + } + it = vcrew.erase(it); + } + else + { + it++; + } + } + + // Remove from crew + for (auto it = crew.begin(); it != crew.end(); ) + { + if (*it == c.gc) + { + it = crew.erase(it); + } + else + { + it++; + } + } + + } + else + { + alive++; + } + } + + if (alive > 0) + { + + std::string memory; + + if (dead.size() == 0) + { + memory = "Thanksfully, all the expedition crew survived."; + } + else + { + std::string name = "expeditioners were"; + if (dead.size() == 1) + { + name = "expeditioner was"; + } + + memory = "Sadly, " + std::to_string(dead.size()) + " " + name + " lost. May their names be remembered: \n\n"; + for (int i = 0; i < dead.size(); i++) + { + memory += dead[i]; + memory += '\n'; + } + } + + g_popup->show("The crew made its way back into the\ + submarine through the hole they had originally blown up.\n\n" + memory); + + + } + else + { + g_popup->show("No response was received from the expedition crew, vitals were lost. May they rest in peace."); + } + + if (flight_scene->vehicle.crew.size() == 0) + { + gameover = true; + } + else + { + if (reassign_captain) + { + flight_scene->vehicle.crew[0].gc->is_captain = true; + } + } + + + } + } + else + { + if (flight_scene->embark_target != nullptr) + { + if (flight_scene->embark_target->get_type() == E_NEST) + { + g_popup->show("Four of the crewmembers left the vehicle in a tiny auxiliary submarine, carrying\ + a few hundred kilograms of explosives to breach a hole. They set the bomb and detonated it, opening a reasonably\ + sized hole in the fleshy wall of the nest. The crew quickly inserted a clamp as the hole was closing rapidly.\n\ +As the flesh around the hole healed, they prepared to jump inside.\n\n\ +The explosion has been intense, the aliens inside are probably not too happy!"); + + g_master->clear_nest_count++; + } + else if (flight_scene->embark_target->get_type() == E_STATION) + { + if (g_master->clear_station_count < 2) + { + g_popup->show("Four of the crewmembers left the vehicle in a tiny auxiliary submarine, and approached\ + what seemed to be the entrance of the huge structure. It spanned a few kilometers vertically, reaching the\ + topmost ice crust, and touching the rock layer below the ocean.\n\ +Upon reaching the entrance, they realized that it was clogged up, so they set a small\ + breaching charge and blew a small hole. The interior of the structure, unlike its exterior, was perfectly clean.\ + The walls were covered in instruments and lights, which illuminated the room with a faint blue colour.\n\n\ +The explosion was probably heard around, so prepare for action!"); + + } + else + { + g_popup->show("Four of the crewmembers left the vehicle in a tiny auxiliary submarine, and approached\ + what seemed to be the entrance of the huge structure. It spanned a few kilometers vertically, reaching the\ + topmost ice crust, and touching the rock layer below the ocean.\n\n\ +Unlike the previous stations they had explored, this one was way cleaner, runes engraved in the rock were clearly visible.\n\ +Upon reaching the entrance, they realized that it was clogged up, so they set a small\ + breaching charge and blew a small hole. The interior of the structure, unlike its exterior, was perfectly clean.\ + The walls were covered in instruments and lights, which illuminated the room with a faint blue colour.\n\n\ +The explosion was probably heard around, so prepare for action!"); + } + + + g_master->clear_station_count++; + std::cout << "c = " << g_master->clear_station_count << std::endl; + } + + embark_scene->restart(); + Building* as_building = (Building*)flight_scene->embark_target; + + embark_scene->map = &as_building->get_bmap(); + + // Obtain 4 expedition crew + int cnum = 1; + EmbarkCrew captain = EmbarkCrew(); + captain.gc = flight_scene->vehicle.get_captain()->gc; + embark_scene->crew.push_back(captain); + for (int i = 0; i < crew.size(); i++) + { + bool found = false; + + for (int j = 0; j < embark_scene->crew.size(); j++) + { + if (crew[i] == embark_scene->crew[j].gc) + { + found = true; + break; + } + } + if (!found) + { + + cnum++; + if (cnum > 4) + { + break; + } + + EmbarkCrew ec = EmbarkCrew(); + ec.gc = crew[i]; + embark_scene->crew.push_back(ec); + } + } + + embarked = true; + flight_scene->embark_target = nullptr; + + embark_scene->update(0.0f); + } + } +} + +void Gamemaster::init() { + // Load crew from the vehicle + for (int i = 0; i < flight_scene->vehicle.crew.size(); i++) + { + crew.push_back(flight_scene->vehicle.crew[i].gc); + } } -Gamemaster::Gamemaster(Vehicle* veh) +Gamemaster::Gamemaster() { - vehicle = veh; + seen_robot = false; + seen_grunt = false; + clear_station_count = 0; + clear_nest_count = 0; } Gamemaster::~Gamemaster() diff --git a/src/flight/Gamemaster.h b/src/flight/Gamemaster.h index f6b77e8..4c5741a 100644 --- a/src/flight/Gamemaster.h +++ b/src/flight/Gamemaster.h @@ -1,5 +1,6 @@ #pragma once #include "../vehicle/Vehicle.h" +#include "../Popup.h" // The gamemaster spawns enemies and generates orders from high command, driving the lore forward // Lore: @@ -13,21 +14,40 @@ // High command will show opposition to this, eventually turning you against the other // submarines + +class FlightScene; +class EmbarkScene; + class Gamemaster { private: - Vehicle* vehicle; - public: + bool gameover = false; + bool gamewin = false; + + std::vector crew; + + bool embarked; + + FlightScene* flight_scene; + EmbarkScene* embark_scene; + + bool seen_robot; + bool seen_grunt; + + // The ancient is never seen + // Activates once we discover intelligent life bool is_fatherland_enemy = false; int clear_station_count = 0; + int clear_nest_count = 0; void update(float dt); + void init(); - Gamemaster(Vehicle* veh); + Gamemaster(); ~Gamemaster(); }; diff --git a/src/flight/entity/Buildings.cpp b/src/flight/entity/Buildings.cpp new file mode 100644 index 0000000..f14ada7 --- /dev/null +++ b/src/flight/entity/Buildings.cpp @@ -0,0 +1,60 @@ +#include "Buildings.h" +#include "../FlightScene.h" + +void EntityNest::update(float dt) +{ + timer -= dt; + float px = get_scene()->vehicle.x; + float py = get_scene()->vehicle.y; + + float dx = px - get_x(); + float dy = py - get_y(); + + float dist = sqrt(dx * dx + dy * dy); + float thresold = 1.0f + get_scene()->vehicle.noise * 5.0f; + + if (dist <= thresold) + { + // Send attackers + if (attackers > 0 && timer <= 0.0f && g_random->getFloat(0.0f, 1.0f) >= 0.98f) + { + attackers--; + + if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + // Biter + } + else + { + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + // Bomb + } + else + { + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + // Crawler + } + else + { + // Worm + } + } + } + + timer = 5.0f; + + } + } +} + +EntityType EntityNest::get_type() +{ + return E_NEST; +} + +int EntityNest::get_symbol() +{ + return 15; +} diff --git a/src/flight/entity/Buildings.h b/src/flight/entity/Buildings.h new file mode 100644 index 0000000..3d90e7c --- /dev/null +++ b/src/flight/entity/Buildings.h @@ -0,0 +1,104 @@ +#pragma once + +#include "FlightEntity.h" +#include "../../disembark/BuildingMap.h" + + +class Building : public FlightEntity +{ +public: + + BuildingMap bmap; + + + bool explored; + + bool is_explored() + { + return explored; + } + + void explore() + { + explored = true; + } + + BuildingMap& get_bmap() + { + return bmap; + } + + Building(int x, int y, bool organic = false) : bmap(x, y, organic) + { + explored = false; + } +}; + +// A building does very little +class EntityNest : public Building +{ +public: + + int attackers = 0; + float timer; + + virtual void update(float dt) override; + + virtual EntityType get_type() override; + + virtual int get_symbol() override; + + + EntityNest() : Building(80, 50, true) + { + attackers = g_random->getInt(1, 4); + timer = 0.0f; + } +}; + +class EntityStation : public Building +{ +public: + virtual void update(float dt) override + { + + } + + virtual EntityType get_type() override + { + return E_STATION; + } + + virtual int get_symbol() override + { + return 234; + } + + EntityStation() : Building(80, 50) + { + + } + +}; + +class EntityBase : public FlightEntity +{ +public: + virtual void update(float dt) override + { + + } + + virtual EntityType get_type() override + { + return E_BASE; + } + + virtual int get_symbol() override + { + return 127; + } + + +}; + diff --git a/src/flight/entity/EntityTorpedo.cpp b/src/flight/entity/EntityTorpedo.cpp new file mode 100644 index 0000000..f252ba0 --- /dev/null +++ b/src/flight/entity/EntityTorpedo.cpp @@ -0,0 +1,124 @@ +#include "EntityTorpedo.h" +#include "../FlightScene.h" + +void EntityTorpedo::damage(float power) +{ + if (is_alive()) + { + alive = false; + + // Explosion, damage all nearby entities and play sound + // depending on player distance (we use explosion function) + get_scene()->explosion(get_x(), get_y(), 0.5f); + } +} + +EntityTorpedo::EntityTorpedo(FlightEntity* target) +{ + this->target = target; + alive = true; +} + +EntityTorpedo::~EntityTorpedo() +{ +} + + +inline float wrap_angle(float angle) +{ + float two_pi = 2.0f * PI; + return angle - two_pi * floor(angle / two_pi); +} + +void EntityTorpedo::update(float dt) +{ + lived += dt; + safety -= dt; + + float tx, ty; + if(target == nullptr) + { + tx = get_scene()->vehicle.x; + ty = get_scene()->vehicle.y; + } + else + { + tx = target->get_x(); + ty = target->get_y(); + } + + float x = get_x(); + float y = get_y(); + + float dx = tx - x; + float dy = ty - y; + + float heading_wanted = atan2(dy, dx) + PI / 2.0f; + + float real_wanted = wrap_angle(heading_wanted); + float real_vehicle = wrap_angle(heading); + + float dir = 1.0f; + float diff = real_wanted - real_vehicle; + + if (fabs(diff) <= 0.005) + { + heading = heading_wanted; + } + else + { + if (fabs(diff) > PI) + { + dir = -1.0f; + } + + if (diff < 0) + { + dir = -dir; + } + + + heading = real_vehicle + dir * dt * maneouver; + } + + if (lived >= lifetime) + { + maneouver = 0.0f; + } + + x += sin(heading) * dt * velocity; + y -= cos(heading) * dt * velocity; + + set_position(x, y); + + float dist = sqrt(dx * dx + dy * dy); + + float thresold = 0.1f; + if (abs(diff) > 0.3f) + { + thresold = 0.2f; + } + + if (dist <= thresold && safety <= 0.0f && g_random->getFloat(0.0f, 1.0f) >= 0.8f) + { + damage(1.0f); + } + + // Check collisions against walls + if (get_map()->get_subtile(get_x(), get_y()) == WALL) + { + damage(1.0f); + } + + +} + +EntityType EntityTorpedo::get_type() +{ + return E_TORPEDO; +} + +int EntityTorpedo::get_symbol() +{ + return 94; +} diff --git a/src/flight/entity/EntityTorpedo.h b/src/flight/entity/EntityTorpedo.h new file mode 100644 index 0000000..bd110e7 --- /dev/null +++ b/src/flight/entity/EntityTorpedo.h @@ -0,0 +1,48 @@ +#pragma once + +#include "FlightEntity.h" + +// If no entity is given (nullptr) +// the torpedo will go towards the player +class EntityTorpedo : public FlightEntity +{ +private: + + FlightEntity* target; + +public: + + float heading; + float velocity; + float maneouver; + float safety; + float lifetime; + + float lived; + + bool alive; + + virtual bool is_alive() + { + return alive; + } + + + virtual void damage(float power); + + + EntityTorpedo() + { + target = nullptr; + alive = true; + } + + EntityTorpedo(FlightEntity* target); + ~EntityTorpedo(); + + // Inherited via FlightEntity + virtual void update(float dt) override; + virtual EntityType get_type() override; + virtual int get_symbol() override; +}; + diff --git a/src/flight/entity/FlightEntity.h b/src/flight/entity/FlightEntity.h new file mode 100644 index 0000000..9259a3a --- /dev/null +++ b/src/flight/entity/FlightEntity.h @@ -0,0 +1,147 @@ +#pragma once + +#include "../../defines.h" +#include + +class FlightMap; +class FlightScene; + +enum EntityType +{ + E_NEST, + E_STATION, + E_BASE, + + // Monsters + + // The crawler is weak but moves pretty fast + // It can only live in high oxygen tiles, and cannot + // move out of them + // Bad ears + E_CRAWLER, + // Opposite of the crawler, strong as hell but slow + // Lives anywhere and has good ears. Your usual enemy + E_WORM, + + // Medium speed, medium strength. Usually stays near nests + // Spawns bombs and biters + E_GUARDIAN, + + // Slow and very strong + // Can spawn bombs, biters and crawlers + // Very very rare + E_SERPENT, + // Fast moving, very weak. Explodes once near the submarine or destroyed + E_BOMB, + // Fast moving, very weak. Gets close to the submarine, and tries to cause + // damage + E_BITER, + // Allied (or not) submarines. Spawn at bases and move to random points in the map + E_SUBMARINE, + + // Torpedoes must be given a target by their creator, which they will try and hit + // then they will explode + E_TORPEDO, + + +}; + +class FlightEntity +{ +private: + + FlightMap* map; + FlightScene* scene; + + float x, y; + bool visible; + + bool identified; + +public: + + + + virtual void update(float dt) = 0; + virtual EntityType get_type() = 0; + virtual int get_symbol() = 0; + + virtual bool is_alive() + { + return true; + } + + // By default is does nothing + virtual void damage(float power) + { + + } + + std::pair get_position() + { + return std::make_pair(x, y); + } + + void set_position(float x, float y) + { + this->x = x; + this->y = y; + } + + float get_x() + { + return this->x; + } + + float get_y() + { + return this->y; + } + + FlightMap* get_map() + { + return map; + } + + FlightScene* get_scene() + { + return scene; + } + + void init(FlightMap* map, FlightScene* scene) + { + this->map = map; + this->scene = scene; + } + + bool is_visible() + { + return visible; + } + + void set_visible(bool v) + { + visible = v; + + if (v == false) + { + identified = false; + } + } + + void identify() + { + identified = true; + } + + bool is_identified() + { + return identified; + } + + virtual std::string get_name() + { + return "Forgot name"; + } + +}; \ No newline at end of file diff --git a/src/flight/entity/Monsters.cpp b/src/flight/entity/Monsters.cpp new file mode 100644 index 0000000..72762e3 --- /dev/null +++ b/src/flight/entity/Monsters.cpp @@ -0,0 +1,495 @@ +#include "Monsters.h" +#include "../FlightScene.h" +#include "../FlightMap.h" + + + +void EntityCrawler::update(float dt) +{ + Monster::update(dt); + + if (c_x == -1) + { + c_x = (int)floor(get_x()); + c_y = (int)floor(get_x()); + } + + speed_penalty -= dt * get_speed_penalty_rate(); + if (speed_penalty < 0.0f) + { + speed_penalty = 0.0f; + } + + if (persecuting) + { + c_x = (int)floor(get_x()); + c_y = (int)floor(get_x()); + + auto player = get_player_pos(); + go_to(player.first, player.second); + + velocity = max(0.25f - speed_penalty * 10.0f, 0.0f); + + if (distance_to_player() <= 0.05f && speed_penalty == 0.0f) + { + // Attack and slowdown + g_soloud->play(attack, 2.0f); + + do_damage(); + + speed_penalty = 1.0f; + } + } + else + { + velocity = 0.1f; + + // Move idly, if we hear the player we have a chance to attack + if (in_destination()) + { + idle(5, c_x, c_y); + } + + if (distance_to_player() <= 1.0f + get_player_noise() * 4.0f && g_random->getFloat(0.0f, 1.0f) >= 0.98f + || autofind()) + { + persecuting = true; + g_soloud->play(persecute, 0.6f); + } + } +} + +void EntityCrawler::do_damage() +{ + if (g_random->getFloat(0.0f, 1.0f) >= 0.6f) + { + // Damage + g_soloud->play(impact, 2.0f); + get_player()->damage(g_random->getFloat(1.3f, 2.0f)); + } +} + + +void EntityWorm::update(float dt) +{ + Monster::update(dt); + + auto player = get_player_pos(); + + if (dive) + { + velocity = 0.5f; + go_to(player.first, player.second); + + if (distance_to_player() <= 0.15f) + { + dive = false; + g_soloud->play(attack_sound, 2.0f); + get_player()->damage(g_random->getFloat(1.8f, 3.2f)); + + } + } + else + { + if (persecuting) + { + // Worms are free to go through walls! + rand_timer -= dt; + if (rand_timer <= 0.0f) + { + float offset_x = g_random->getFloat(-1.8f, 1.8f); + float offset_y = g_random->getFloat(-1.8f, 1.8f); + move_to(player.first + offset_x, player.second + offset_y); + rand_timer = 3.0f; + } + + + + + velocity = 0.1f; + + if (distance_to_player() <= 1.3f && distance_to_player() >= 0.6f + && g_random->getFloat(0.0f, 1.0f) >= 0.994f) + { + dive = true; + g_soloud->play(dive_sound, 2.0f); + } + } + else + { + velocity = 0.1f; + + // Move idly, if we hear the player we have a chance to attack + if (in_destination()) + { + idle(5, (int)get_x(), (int)get_y()); + } + + if (distance_to_player() <= 1.0f + get_player_noise() * 7.0f && g_random->getFloat(0.0f, 1.0f) >= 0.95f) + { + persecuting = true; + //g_soloud->play(persecute, 0.6f); + } + } + } +} + + + +void Monster::path_to(int x, int y) +{ + if (current_path != nullptr) + { + int dx, dy; + current_path->getDestination(&dx, &dy); + + if (dx == x && dy == y) + { + return; + } + } + + TCODPath* n_path = new TCODPath(&get_map()->vmap, 0.0f); + + auto t = get_map_position(); + + if (n_path->compute(t.first, t.second, x, y)) + { + current_path = n_path; + just_started = true; + } + else + { + x_target = -1.0f; + y_target = -1.0f; + delete n_path; + } + + +} + + +void Monster::update(float dt) +{ + // Update basic movement + if (!in_destination()) + { + float x = get_x(); + float y = get_y(); + + float dx = x_target - x; + float dy = y_target - y; + float l = sqrt(dx * dx + dy * dy); + float xn = dx / l; + float yn = dy / l; + + if (l <= get_speed() * 0.01f) + { + set_position(x_target, y_target); + } + else + { + set_position(x + xn * get_speed() * dt, y + yn * get_speed() * dt); + } + } + + + if (current_path != nullptr) + { + auto t = get_map_position(); + + if (just_started) + { + if (std::abs(get_x() - t.first - 0.5f) <= 0.2f && + std::abs(get_y() - t.second - 0.5f) <= 0.2f) + { + // We are sufficiently close to the center of the tile + just_started = false; + step(); + } + else + { + // Move to center of tile + move_to((float)t.first + 0.5f, (float)t.second + 0.5f); + + if (in_destination()) + { + just_started = false; + step(); + } + } + } + else + { + // Move to center of next tile + if (in_destination()) + { + step(); + } + } + + } +} + +std::pair Monster::get_map_position() +{ + std::pair pos = get_position(); + + return std::make_pair((int)floor(pos.first), (int)floor(pos.second)); +} + +void Monster::step() +{ + if (current_path != nullptr) + { + int nx, ny; + if (!current_path->walk(&nx, &ny, true)) + { + delete current_path; + current_path = nullptr; + return; + } + + move_to((float)nx + 0.5f, (float)ny + 0.5f); + } +} + +void Monster::move_to(float x, float y) +{ + x_target = x; + y_target = y; +} + +bool Monster::in_destination() +{ + if (x_target == -1.0f) + { + return true; + } + else + { + return x_target == get_x() && y_target == get_y(); + } +} + +float Monster::go_to(float px, float py) +{ + float x = get_x(); + float y = get_y(); + + if (floor(px) == floor(x) && floor(py) == floor(y)) + { + // Same tile, simple path + move_to(px, py); + } + else + { + // Other tile, path + path_to((int)floor(px), (int)floor(py)); + } + + float dx = px - x; + float dy = py - y; + return sqrt(dx * dx + dy * dy); +} + +void Monster::idle(int radius, int cx, int cy) +{ + int t_x = g_random->getInt(cx - radius, cx + radius); + int t_y = g_random->getInt(cy - radius, cy + radius); + + path_to(t_x, t_y); +} + +float Monster::distance_to(float x, float y) +{ + float dx = get_x() - x; + float dy = get_y() - y; + return sqrt(dx * dx + dy * dy); +} + +float Monster::distance_to_player() +{ + float px = get_scene()->vehicle.x; + float py = get_scene()->vehicle.y; + return distance_to(px, py); +} + +std::pair Monster::get_player_pos() +{ + return std::make_pair(get_scene()->vehicle.x, get_scene()->vehicle.y); +} + +float Monster::get_player_noise() +{ + return get_scene()->vehicle.noise; +} + +Vehicle* Monster::get_player() +{ + return &get_scene()->vehicle; +} + +void EntityBiter::do_damage() +{ + if (g_random->getFloat(0.0f, 1.0f) >= 0.3f) + { + // Damage + g_soloud->play(impact, 1.6f); + get_player()->damage(1.2f); + } +} + +void EntityBomb::do_damage() +{ + damage(1.0f); + get_scene()->explosion(get_x(), get_y(), 0.1f); +} + +void EntityGuardian::update(float dt) +{ + Monster::update(dt); + + velocity = 0.02f; + + if (c_x == -1.0f) + { + c_x = get_x(); + c_y = get_y(); + } + + float r = 3.0f; + + move_timer -= dt; + if (move_timer <= 0.0f) + { + float ox = g_random->getFloat(-r, r); + float oy = g_random->getFloat(-r, r); + move_to(c_x + ox, c_y + oy); + move_timer = 10.0f; + } + + // Difference to base + float dxb = get_player()->x - c_x; + float dyb = get_player()->y - c_y; + + float base_dist = sqrt(dxb * dxb + dyb * dyb); + + if (base_dist <= r) + { + // Send hell + spawn_timer -= dt; + + if (spawn_timer <= 0.0f) + { + FlightEntity* ent; + if (g_random->getFloat(0.0f, 1.0f) >= 0.45f) + { + // Biter + ent = new EntityBiter(); + } + else + { + // Bomb + ent = new EntityBomb(); + } + + ent->set_position(get_x(), get_y()); + ent->init(get_map(), get_scene()); + get_map()->entities.push_back(ent); + + spawn_timer = g_random->getFloat(10.0f, 35.0f); + } + } +} + +void EntitySerpent::update(float dt) +{ + Monster::update(dt); + + engage_timer -= dt; + + if (engaging) + { + velocity = 0.25f; + + fight_timer -= dt; + if (fight_timer < 0.0f) + { + float px = get_player()->x; + float py = get_player()->y; + float ox = g_random->getFloat(-1.7f, 1.7f); + float oy = g_random->getFloat(-1.7f, 1.7f); + + if (std::abs(ox) < 0.4f) + { + ox = ox > 0.0f ? 0.35f : -0.4f; + } + + if (std::abs(oy) < 0.4f) + { + oy = oy > 0.0f ? 0.4f : -0.4f; + } + + move_to(px + ox, py + oy); + fight_timer = 4.0f; + } + + spawn_timer -= dt; + if (spawn_timer < 0.0f) + { + FlightEntity* ent; + if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + if (g_random->getFloat(0.0f, 1.0f) >= 0.5f) + { + // Biter + ent = new EntityBiter(); + } + else + { + // Bomb + ent = new EntityBomb(); + } + + } + else + { + // Crawler + ent = new EntityCrawler(); + } + + ent->set_position(get_x(), get_y()); + ent->init(get_map(), get_scene()); + get_map()->entities.push_back(ent); + + spawn_timer = g_random->getFloat(5.0f, 30.0f); + } + + if (engage_timer < 0.0f) + { + engaging = false; + } + } + else + { + velocity = 0.15f; + + // Move idly, if we hear the player we have a chance to attack + if (in_destination()) + { + idle(5, (int)get_x(), (int)get_y()); + } + + if (engage_timer <= -60.0f) + { + if (distance_to_player() <= 1.0f + get_player_noise() * 7.0f && g_random->getFloat(0.0f, 1.0f) >= 0.95f) + { + engaging = true; + //g_soloud->play(persecute, 0.6f); + engage_timer = 60.0f; + } + } + + } +} diff --git a/src/flight/entity/Monsters.h b/src/flight/entity/Monsters.h new file mode 100644 index 0000000..8f0543a --- /dev/null +++ b/src/flight/entity/Monsters.h @@ -0,0 +1,365 @@ +#pragma once + +#include "FlightEntity.h" +#include + +class Vehicle; + +// Implements basic stuff for the others +class Monster : public FlightEntity +{ +public: + + float health = 1.0f; + + TCODPath* current_path; + + bool just_started; + + float x_target, y_target; + + // Map coordinates, moves through the center + // of tiles and never diagonally to avoid going into walls + void path_to(int x, int y); + + virtual void update(float dt) override; + + virtual EntityType get_type() = 0; + + virtual int get_symbol() = 0; + + std::pair get_map_position(); + + virtual float get_speed() = 0; + + void step(); + + // Use only within a tile, it does no pathing and + // will happily go through walls! + void move_to(float x, float y); + bool in_destination(); + + // Returns distance to point + float go_to(float x, float y); + + // Moves randomly in points within radius of a given point + void idle(int radius, int cx, int cy); + + float distance_to(float x, float y); + + float distance_to_player(); + + std::pair get_player_pos(); + + float get_player_noise(); + + + Vehicle* get_player(); + + Monster() + { + current_path = nullptr; + x_target = -1.0f; + y_target = -1.0f; + } + + + virtual bool is_alive() override + { + return health > 0.0f; + } + + virtual void damage(float power) override + { + health -= power; + } + +}; + + +class EntityCrawler : public Monster +{ +public: + + SoLoud::Wav attack; + SoLoud::Wav impact; + SoLoud::Wav persecute; + + float velocity; + + bool persecuting; + + float speed_penalty; + + int c_x = -1, c_y = -1; + + virtual void update(float dt) override; + + virtual float get_speed_penalty_rate() + { + return 0.1f; + } + + virtual bool autofind() + { + return false; + } + + virtual void do_damage(); + + + virtual std::string get_name() override + { + return "Crawler"; + } + + virtual EntityType get_type() override + { + return E_CRAWLER; + } + + virtual int get_symbol() override + { + return 157; + } + + virtual float get_speed() override + { + return velocity; + } + + EntityCrawler() + { + persecuting = false; + speed_penalty = 0.0f; + attack.load("crawler/attack.wav"); + impact.load("collide2.wav"); + persecute.load("crawler/idle.wav"); + health = 1.5f; + }; + +}; + +class EntityBiter : public EntityCrawler +{ +public: + + virtual std::string get_name() override + { + return "Biter"; + } + + virtual EntityType get_type() override + { + return E_BITER; + } + + virtual int get_symbol() override + { + return 224; + } + + virtual float get_speed_penalty_rate() + { + return 0.05f; + } + + virtual bool autofind() + { + return true; + } + + virtual void do_damage() override; + + EntityBiter() + { + health = 0.5f; + attack.load("biter/attack.wav"); + persecute.load("biter/idle.wav"); + } +}; + +// TODO: Custom sounds to the BOMB +class EntityBomb : public EntityCrawler +{ +public: + + virtual std::string get_name() override + { + return "Bomb"; + } + + virtual EntityType get_type() override + { + return E_BOMB; + } + + virtual int get_symbol() override + { + return 155; + } + + virtual float get_speed_penalty_rate() + { + return 0.05f; + } + + virtual bool autofind() + { + return true; + } + + virtual void do_damage() override; + + EntityBomb() + { + health = 0.5f; + attack.load("biter/attack.wav"); + persecute.load("biter/idle.wav"); + } +}; + +// The worm moves randomly across the whole map +// and attacks, it's strong +class EntityWorm : public Monster +{ +public: + + float velocity; + bool persecuting; + bool dive; + + SoLoud::Wav dive_sound; + SoLoud::Wav attack_sound; + + float rand_timer = 0.0f; + + virtual std::string get_name() override + { + return "Worm"; + } + + virtual int get_symbol() + { + return 126; + } + virtual EntityType get_type() override + { + return E_WORM; + } + + virtual float get_speed() override + { + return velocity; + } + + virtual void update(float dt) override; + + EntityWorm() + { + health = 2.0f; + attack_sound.load("worm/attack.wav"); + dive_sound.load("worm/dive.wav"); + rand_timer = 0.0f; + } + +}; + +// Guardians refuse to go very far away from their nest +class EntityGuardian : public Monster +{ +public: + + float velocity; + float move_timer; + float spawn_timer; + + float c_x, c_y; + + virtual void update(float dt) override; + + virtual std::string get_name() override + { + return "Guardian"; + } + + virtual EntityType get_type() override + { + return E_GUARDIAN; + } + + virtual int get_symbol() override + { + return 236; + } + + virtual float get_speed() override + { + return velocity; + } + + EntityGuardian() + { + health = 4.0f; + c_x = -1.0f; + c_y = -1.0f; + move_timer = 0.0f; + } +}; + +// Serpents move across the map +class EntitySerpent : public Monster +{ +public: + + float velocity; + float spawn_timer; + float engage_timer; + float fight_timer; + bool engaging; + + virtual void update(float dt) override; + + + virtual std::string get_name() override + { + return "Giant Serpent"; + } + + virtual EntityType get_type() override + { + return E_SERPENT; + } + + virtual int get_symbol() override + { + return 21; + } + + virtual float get_speed() override + { + return velocity; + } + + virtual void damage(float pow) override + { + Monster::damage(pow); + + if (!engaging) + { + engaging = true; + engage_timer = 60.0f; + } + + } + + EntitySerpent() + { + health = 10.0f; + engaging = false; + spawn_timer = 0.0f; + engage_timer = -1000.0f; + fight_timer = 0.0f; + } + +}; \ No newline at end of file diff --git a/src/vehicle/Crewmember.cpp b/src/vehicle/Crewmember.cpp deleted file mode 100644 index 84e20f1..0000000 --- a/src/vehicle/Crewmember.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "Crewmember.h" -#include "workbench/Workbench.h" - -static SoLoud::Wav* radio = nullptr; - - -void Crewmember::speak(std::string text) -{ - if (radio == nullptr) - { - radio = new SoLoud::Wav(); - radio->load("radio.wav"); - } - - g_soloud->play(*radio); - - voice.setText((". . ." + text).c_str()); - g_soloud->play(voice, 6.0f); - - g_status->strings.push_back(name + ": " + text); -} - -Crewmember::Crewmember(std::string name, int voice_pitch, float voice_speed, int voice_oscillator) -{ - path = nullptr; - path_t = 0.0f; - this->name = name; - - voice = SoLoud::Speech(); - voice.setParams(voice_pitch, voice_speed, 0.5f, voice_oscillator); -} - -Crewmember::Crewmember() -{ - path = nullptr; - path_t = 0.0f; - TCODRandom rng = TCODRandom(); - // Procedural - - std::array first_names = - { - "James", - "John", - "Robert", - "Michael", - "William", - "David", - "Richard", - "Joseph", - "Guamedo", - "Thomas", - "Charles", - "Donald", - "Mark", - "Brian", - "Alex" - }; - - std::array last_names = - { - "Smith", - "Johnson", - "Williams", - "Jones", - "Brown", - "Davis", - "Miller", - "Wilson", - "Moore", - "Taylor", - "Anderson", - "Thomas", - "Jackson", - "White", - "Garcia", - "Martinez", - "Young" - }; - - name = first_names[rand() % first_names.size()] + " " + last_names[rand() % last_names.size()]; - - int wave = KW_TRIANGLE; - if (rng.getFloat(0.0f, 1.0f) >= 0.5f) - { - wave = KW_PULSE; - } - else if(rng.getFloat(0.0f, 1.0f) >= 0.5f) - { - wave = KW_WARBLE; - } - else if(rng.getFloat(0.0f, 1.0f) >= 0.5f) - { - wave = KW_SQUARE; - } - - voice.setParams(rng.getInt(900, 1900), rng.getFloat(7.0f, 10.0f), rng.getFloat(0.4f, 0.6f), wave); -} - -bool Crewmember::can_work_in(Workbench * bench) -{ - /*for (size_t i = 0; i < assigned.size(); i++) - { - if (bench == assigned[i]) - { - return true; - } - } - - return false; - */ - - return !is_captain; -} - -void Crewmember::path_to(int dx, int dy, TCODMap& map) -{ - if (path != nullptr) - { - delete path; - path = nullptr; - } - path = new TCODPath(&map); - if (!path->compute(x, y, dx, dy)) - { - delete path; - path = nullptr; - } -} diff --git a/src/vehicle/FlightCrew.cpp b/src/vehicle/FlightCrew.cpp new file mode 100644 index 0000000..4940d4e --- /dev/null +++ b/src/vehicle/FlightCrew.cpp @@ -0,0 +1,45 @@ +#include "FlightCrew.h" +#include "workbench/Workbench.h" + + + +FlightCrew::FlightCrew(Crewmember* c) +{ + this->gc = c; + repair_order = false; + path = nullptr; + path_t = 0.0f; + // Procedural + +} + +bool FlightCrew::can_work_in(Workbench * bench) +{ + /*for (size_t i = 0; i < assigned.size(); i++) + { + if (bench == assigned[i]) + { + return true; + } + } + + return false; + */ + + return true; +} + +void FlightCrew::path_to(int dx, int dy, TCODMap& map) +{ + if (path != nullptr) + { + delete path; + path = nullptr; + } + path = new TCODPath(&map); + if (!path->compute(x, y, dx, dy)) + { + delete path; + path = nullptr; + } +} diff --git a/src/vehicle/Crewmember.h b/src/vehicle/FlightCrew.h similarity index 56% rename from src/vehicle/Crewmember.h rename to src/vehicle/FlightCrew.h index 8d19e6f..554c608 100644 --- a/src/vehicle/Crewmember.h +++ b/src/vehicle/FlightCrew.h @@ -4,7 +4,7 @@ #include "soloud_speech.h" #include "soloud_wav.h" #include "../defines.h" - +#include "../Crewmember.h" #include #include @@ -12,27 +12,23 @@ class Workbench; - - -class Crewmember +class FlightCrew { public: - SoLoud::Speech voice; - - std::string name; + Crewmember* gc; int x, y; - bool is_captain; + // Where we go when called to return to positions + int cx, cy; // The first one is priority (index 0) std::vector assigned; - void speak(std::string text); - Crewmember(std::string name, int voice_pitch, float voice_speed, int voice_oscillator); - Crewmember(); + + FlightCrew(Crewmember* crew); bool can_work_in(Workbench* bench); @@ -40,4 +36,14 @@ class Crewmember TCODPath* path; float path_t = 0.0f; + + bool has_torpedo = false; + bool wants_torpedo = false; + + int repair_x = -1, repair_y = -1; + + bool is_repairing = false; + bool is_pumping = false; + + bool repair_order; }; \ No newline at end of file diff --git a/src/vehicle/Vehicle.cpp b/src/vehicle/Vehicle.cpp index 98d161e..8012f52 100644 --- a/src/vehicle/Vehicle.cpp +++ b/src/vehicle/Vehicle.cpp @@ -1,9 +1,568 @@ #include "Vehicle.h" +#include "../flight/FlightScene.h" +VehicleTile& Vehicle::get_tile_for_water(int x, int y) +{ + if (x < 0 || y < 0 || x >= width || y >= height) + { + return dummy_tile; + } + else + { + return tiles[y * width + x]; + } +} + +void Vehicle::update_water_flow(float dt) +{ + water_tick -= dt; + if (water_tick < 0.0f) + { + water_level = 0.0f; + water_flow = 0.0f; + // Update water flow + // We set flowing here + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (tiles[y * width + x].is_outside) + { + tiles[y * width + x].health = 1.0f; + } + + water_level += tiles[y * width + x].water; + + tiles[y * width + x].really_blocks_water = tiles[y * width + x].blocks_water; + + if (!tiles[y * width + x].is_outside) + { + tiles[y * width + x].prev_water = tiles[y * width + x].water; + } + + if (tiles[y * width + x].blocks_water) + { + if (tiles[y * width + x].health <= 0.5f) + { + tiles[y * width + x].really_blocks_water = false; + } + + bool found = false; + + for (int i = 0; i < crew.size(); i++) + { + if (crew[i].x == x && crew[i].y == y) + { + tiles[y * width + x].really_blocks_water = false; + found = true; + break; + } + } + + if (!found && tiles[y * width + x].health >= 0.5f) + { + tiles[y * width + x].water = 0.0f; + } + } + } + } + + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + VehicleTile& c = get_tile_for_water(x + 0, y + 0); + VehicleTile& u = get_tile_for_water(x + 0, y - 1); + VehicleTile& r = get_tile_for_water(x + 1, y + 0); + VehicleTile& d = get_tile_for_water(x + 0, y + 1); + VehicleTile& l = get_tile_for_water(x - 1, y + 0); + + if (c.health <= 0.5f) + { + water_flow += 1.0f - c.health; + if (g_random->getFloat(0.0f, 0.5f) > c.health) + { + c.water += g_random->getFloat(0.001f, 1.0f - c.health) * dt * 0.5f; + } + } + + if (!c.is_outside && !c.really_blocks_water) + { + for (int i = 0; i < 4; i++) + { + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + if (!(u.really_blocks_water && u.health >= 0.5f) && u.prev_water < c.prev_water) + { + u.water += min(WATER_FLOW_RATE * dt, c.water); + c.water -= min(WATER_FLOW_RATE * dt, c.water); + } + } + else + { + if (!(l.really_blocks_water && l.health >= 0.5f) && l.prev_water < c.prev_water) + { + l.water += min(WATER_FLOW_RATE * dt, c.water); + c.water -= min(WATER_FLOW_RATE * dt, c.water); + } + } + } + else + { + if (g_random->getFloat(0.0f, 1.0f) > 0.5f) + { + if (!(r.really_blocks_water && r.health >= 0.5f) && r.prev_water < c.prev_water) + { + r.water += min(WATER_FLOW_RATE * dt, c.water); + c.water -= min(WATER_FLOW_RATE * dt, c.water); + } + } + else + { + if (!(d.really_blocks_water && d.health >= 0.5f) && d.prev_water < c.prev_water) + { + d.water += min(WATER_FLOW_RATE * dt, c.water); + c.water -= min(WATER_FLOW_RATE * dt, c.water); + } + } + } + + } + + + } + } + } + + water_tick = 0.05f; + g_soloud->setVolume(waterflow_h, min(water_flow / 10.0f, 2.0f)); + } + + +} + +void Vehicle::update_crew(float dt) +{ + for (int i = 0; i < crew.size(); i++) + { + crew[i].path_t -= dt; + + if (crew[i].path != nullptr && crew[i].path_t <= 0.0f) + { + int nx, ny; + crew[i].path->walk(&crew[i].x, &crew[i].y, true); + + crew[i].path_t = 0.1f; + } + + if (crew[i].has_torpedo) + { + // Find free torpedo slot + int slot = -1; + for (int j = 0; j < torpedo_slots.size(); j++) + { + if (torpedo_slots[j].has_torpedo == false && + (torpedo_slots[j].crew_coming == i || torpedo_slots[j].crew_coming == -1)) + { + slot = j; + break; + } + } + + if (slot != -1) + { + torpedo_slots[slot].crew_coming = i; + + crew[i].path_to(torpedo_slots[slot].x, torpedo_slots[slot].y, *tcod_map); + + if (crew[i].x == torpedo_slots[slot].x && crew[i].y == torpedo_slots[slot].y) + { + torpedo_slots[slot].crew_coming = -1; + torpedo_slots[slot].has_torpedo = true; + crew[i].has_torpedo = false; + + crew[i].gc->speak("Torpedo loaded, sir!"); + } + } + else + { + // Drop the torpedo + Torpedo tp = Torpedo(); + tp.x = crew[i].x; + tp.y = crew[i].y; + crew[i].has_torpedo = false; + torpedoes.push_back(tp); + } + } + else + { + if (crew[i].repair_order && !crew[i].is_repairing) + { + bool found = false; + + int tries = 0; + + while(!found && tries < 30) + { + int x = g_random->getInt(0, width - 1); + int y = g_random->getInt(0, height - 1); + + if (tiles[y * width + x].health < 1.0f) + { + crew[i].repair_x = x; + crew[i].repair_y = y; + found = true; + } + + tries++; + } + } + + if (crew[i].wants_torpedo) + { + int to_remove = -1; + for (int j = 0; j < torpedoes.size(); j++) + { + if (crew[i].x >= torpedoes[j].x && crew[i].x <= torpedoes[j].x + 4 && crew[i].y == torpedoes[j].y) + { + to_remove = j; + crew[i].wants_torpedo = false; + crew[i].has_torpedo = true; + break; + } + } + + if (to_remove != -1) + { + torpedoes.erase(torpedoes.begin() + to_remove); + } + } + + if (tiles[crew[i].y * width + crew[i].x].ch == 235) + { + crew[i].is_pumping = true; + + tiles[crew[i].y * width + crew[i].x].water -= WATER_PUMP_RATE * dt; + if (tiles[crew[i].y * width + crew[i].x].water < 0.0f) + { + tiles[crew[i].y * width + crew[i].x].water = 0.0f; + crew[i].is_pumping = false; + } + } + else + { + crew[i].is_pumping = false; + } + + if (crew[i].repair_x != -1) + { + if (crew[i].x >= crew[i].repair_x - 2 + && crew[i].y >= crew[i].repair_y - 2 + && crew[i].x <= crew[i].repair_x + 2 + && crew[i].y <= crew[i].repair_y + 2) + { + crew[i].is_repairing = true; + + // Repair + VehicleTile& to_repair = tiles[crew[i].repair_y * width + crew[i].repair_x]; + + to_repair.health += REPAIR_RATE * dt; + + if (to_repair.health >= 1.0f) + { + to_repair.health = 1.0f; + crew[i].repair_x = -1; + crew[i].repair_y = -1; + if (!crew[i].repair_order) + { + crew[i].gc->speak("Tile repaired!"); + } + + crew[i].is_repairing = false; + + if (crew[i].repair_order) + { + // Go to workstation + crew[i].path_to(crew[i].cx, crew[i].cy, *tcod_map); + } + } + } + else + { + TCODPath t_path = TCODPath(tcod_map); + // Path to any free tile + for (int ox = -2; ox < 2; ox++) + { + for (int oy = -2; oy < 2; oy++) + { + if (t_path.compute(crew[i].x, crew[i].y, crew[i].repair_x + ox, crew[i].repair_y + oy)) + { + crew[i].path_to(crew[i].repair_x + ox, crew[i].repair_y + oy, *tcod_map); + break; + } + } + } + } + } + } + } +} + +void Vehicle::update_bubbles(float dt) +{ + + + while (bubbles.size() <= 50) + { + float y = g_random->getFloat(0.0f, 40.0f); + Bubble b = Bubble(); + + b.x = 0.0f; + b.y = y; + + bubbles.push_back(b); + } + + for (auto it = bubbles.begin(); it != bubbles.end();) + { + it->t += dt; + it->x += dt * velocity * 10.0f; + + if (g_random->getFloat(0.0f, 1.0f) >= 0.99f) + { + it->y += g_random->getInt(-1, 1); + } + + + it->b = (sin(it->t * it->f) + 1.0f) * 30.0f; + + if (it->x >= WIDTH) + { + it = bubbles.erase(it); + } + else + { + it++; + } + } +} + +void Vehicle::do_context_menu(TCODConsole* target, int ox, int oy, TCOD_mouse_t pos, int torpedo_hlight) +{ + if (selected != nullptr && pos.rbutton_pressed) + { + in_context_menu = true; + ctx_x = pos.cx - ox; + ctx_y = pos.cy - oy; + + if (ctx_x < 0 || ctx_x >= width || ctx_y < 0 || ctx_y >= height) + { + in_context_menu = false; + } + } + + if (selected != nullptr) + { + if (pos.mbutton_pressed || (pos.lbutton_pressed && (g_key.lctrl || g_key.rctrl))) + { + selected->path_to(pos.cx - ox, pos.cy - oy, *tcod_map); + selected->is_repairing = false; + selected->is_pumping = false; + } + } + + if (in_context_menu) + { + int rx = ctx_x + ox; + int ry = ctx_y + oy; + + if (blink) + { + target->setChar(rx, ry, 'X'); + target->setCharForeground(ctx_x + ox, ctx_y + oy, TCODColor::white); + } + + std::vector menu_items; + if (!tiles[ctx_y * width + ctx_x].blocks_player) + { + menu_items.push_back("Move Here"); + menu_items.push_back("Assign Location"); + } + + if (torpedo_hlight != -1) + { + menu_items.push_back("Load Torpedo"); + } + + if (tiles[ctx_y * width + ctx_x].ch == 235) + { + menu_items.push_back("Pump Water Out"); + } + + // We can repair tiles in a 2x2 range + if (tiles[ctx_y * width + ctx_x].health < 1.0f) + { + menu_items.push_back("Repair Tile"); + } + + if (selected->repair_order) + { + menu_items.push_back("Stop Auto-Repairing"); + } + else + { + menu_items.push_back("Auto-Repair"); + } + + + + menu_items.push_back(selected->gc->name); + + + + int menu_x, menu_y; + int menu_w = 0, menu_h = menu_items.size() + 1; + + for (int i = 0; i < menu_items.size(); i++) + { + if (menu_items[i].size() >= menu_w) + { + menu_w = menu_items[i].size(); + } + } + + menu_w += 3; + + // Draw menu, choose direction carefully + if (rx >= WIDTH - menu_w - 1) + { + menu_x = rx - menu_w - 1; + } + else + { + menu_x = rx + 1; + } + + if (ry >= HEIGHT - menu_h - 1) + { + menu_y = ry - menu_h - 1; + } + else + { + menu_y = ry; + } + + for (int x = menu_x; x < menu_x + menu_w + 1; x++) + { + for (int y = menu_y; y < menu_y + menu_h + 1; y++) + { + target->setChar(x, y, ' '); + target->setCharForeground(x, y, TCODColor::white); + target->setCharBackground(x, y, TCODColor::black); + } + } + + Drawing::draw_rectangle(target, menu_x, menu_y, menu_w, menu_h, false); + + target->setAlignment(TCOD_CENTER); + + for (int i = 0; i < menu_items.size(); i++) + { + bool highlight = false; + + if (pos.cx >= menu_x && pos.cx <= menu_x + menu_w + && pos.cy == menu_y + 1 + i) + { + highlight = true; + } + + if (highlight) + { + target->setDefaultForeground(TCODColor::black); + target->setDefaultBackground(TCODColor::white); + } + + for (int x0 = 1; x0 < menu_w; x0++) + { + target->setCharBackground(menu_x + x0, menu_y + i + 1, target->getDefaultBackground()); + } + + target->printf(menu_x + menu_w / 2, menu_y + 1 + i, menu_items[i].c_str()); + + target->setDefaultForeground(TCODColor::white); + target->setDefaultBackground(TCODColor::black); + + if (highlight && (pos.lbutton_pressed && !(g_key.lctrl || g_key.rctrl))) + { + if (menu_items[i] == "Move Here") + { + selected->path_to(ctx_x, ctx_y, *tcod_map); + selected->is_repairing = false; + selected->is_pumping = false; + } + else if (menu_items[i] == "Load Torpedo") + { + selected->path_to(ctx_x, ctx_y, *tcod_map); + selected->wants_torpedo = true; + selected->is_repairing = false; + selected->is_pumping = false; + } + else if (menu_items[i] == "Pump Water Out") + { + selected->path_to(ctx_x, ctx_y, *tcod_map); + } + else if (menu_items[i] == "Repair Tile") + { + selected->repair_x = ctx_x; + selected->repair_y = ctx_y; + } + else if (menu_items[i] == "Auto-Repair") + { + selected->repair_order = true; + } + else if (menu_items[i] == "Stop Auto-Repairing") + { + selected->repair_order = false; + } + else if (menu_items[i] == "Assign Location") + { + selected->cx = ctx_x; + selected->cy = ctx_y; + } + } + + } + + if ((pos.lbutton_pressed && !(g_key.lctrl || g_key.rctrl))) + { + in_context_menu = false; + } + + target->setAlignment(TCOD_LEFT); + } +} + + + +FlightCrew * Vehicle::get_captain() +{ + for (int i = 0; i < crew.size(); i++) + { + if (crew[i].gc->is_captain) + { + return &crew[i]; + } + } + + return nullptr; +} void Vehicle::update(float dt) { + for (size_t i = 0; i < workbenches.size(); i++) { if (workbenches[i]->update(dt)) @@ -16,8 +575,36 @@ void Vehicle::update(float dt) auto old = get_tile(); bool was_breathing = breathing; - x += sin(angle) * velocity * dt * 0.15f; - y -= cos(angle) * velocity * dt * 0.15f; + float slowdown = 1.0f - min(water_level * 0.025f, 1.0f); + + //std::cout << slowdown << std::endl; + + float dx = sin(angle) * velocity * dt * 0.2f * possible_speed; + float dy = -cos(angle) * velocity * dt * 0.2f * possible_speed; + + if (in_map->get_subtile(x + dx, y + dy) == WALL) + { + if (velocity > 0.2f) + { + damage(1.5f); + } + + velocity = 0.0f; + } + else + { + x += dx; + y += dy; + } + + noise = 0.0f; + + // Collect noise + noise += velocity * 0.6f; + noise += machines->eng0_running ? 0.6f : 0.0f; + noise += machines->eng1_running ? 0.6f : 0.0f; + noise += sonar->sonar_active ? 0.3f : 0.0f; + noise /= 1.5f; if (get_tile() != old) { @@ -30,7 +617,7 @@ void Vehicle::update(float dt) { if (maneouver->is_crewed()) { - maneouver->get_crewman()->speak("We have entered an oxygen rich zone."); + maneouver->get_crewman()->gc->speak("We have entered an oxygen rich zone."); } } } @@ -41,67 +628,30 @@ void Vehicle::update(float dt) { if (maneouver->is_crewed()) { - maneouver->get_crewman()->speak("We have exited the oxygen rich zone."); + maneouver->get_crewman()->gc->speak("We have exited the oxygen rich zone."); } } } } float base_vol = 0.8f; - float tri = max(-fabs(2.0f * velocity - 1.0f) + 1.0f, 0.0f); float tri2 = max(-fabs(2.0f * velocity - 2.0f) + 1.0f, 0.0f); g_soloud->setVolume(engines_low_h, tri * 0.25 * base_vol); g_soloud->setVolume(engines_high_h, tri2 * 0.8f * base_vol); g_soloud->setVolume(moving_slow_h, tri * 0.4f * base_vol); - g_soloud->setVolume(moving_fast_h, velocity * base_vol); - - - flip_timer -= velocity * dt * 10.0f; - - if (flip_timer <= 0.0f) - { - tiles[m0y * width + m0x].ch = tiles[m0y * width + m0x].ch == 192 ? 218 : 192; - tiles[m1y * width + m1x].ch = tiles[m1y * width + m1x].ch == 192 ? 218 : 192; - - flip_timer = 1.0f; - } - - - - while (bubbles.size() <= 50) - { - float y = g_random->getFloat(0.0f, 40.0f); - Bubble b = Bubble(); - - b.x = 0.0f; - b.y = y; - - bubbles.push_back(b); - } - - for (auto it = bubbles.begin(); it != bubbles.end();) - { - it->t += dt; - it->x += dt * velocity * 10.0f; + g_soloud->setVolume(moving_fast_h, velocity * base_vol); - if (g_random->getFloat(0.0f, 1.0f) >= 0.99f) - { - it->y += g_random->getInt(-1, 1); - } + flip_timer -= velocity * dt * 10.0f; - it->b = (sin(it->t * it->f) + 1.0f) * 30.0f; + if (flip_timer <= 0.0f) + { + tiles[m0y * width + m0x].ch = tiles[m0y * width + m0x].ch == 192 ? 218 : 192; + tiles[m1y * width + m1x].ch = tiles[m1y * width + m1x].ch == 192 ? 218 : 192; - if (it->x >= WIDTH) - { - it = bubbles.erase(it); - } - else - { - it++; - } + flip_timer = 1.0f; } blinkt -= dt; @@ -111,16 +661,30 @@ void Vehicle::update(float dt) blink = !blink; } - for (int i = 0; i < crew.size(); i++) - { - crew[i].path_t -= dt; + + update_bubbles(dt); + update_crew(dt); + update_water_flow(dt); - if (crew[i].path != nullptr && crew[i].path_t <= 0.0f) - { - int nx, ny; - crew[i].path->walk(&crew[i].x, &crew[i].y, true); +} - crew[i].path_t = 0.1f; +void draw_torpedo(TCODConsole* target, int x, int y, int ox, int oy, bool hlight) +{ + target->setChar(x + ox, y + oy, 238); + target->setChar(x + 1 + ox, y + oy, TCOD_CHAR_DHLINE); + target->setChar(x + 2 + ox, y + oy, TCOD_CHAR_DHLINE); + target->setChar(x + 3 + ox, y + oy, TCOD_CHAR_DHLINE); + target->setChar(x + 4 + ox, y + oy, 232); + target->setCharForeground(x + ox, y + oy, TCODColor(140, 70, 0)); + target->setCharForeground(x + 1 + ox, y + oy, TCODColor(158, 158, 158)); + target->setCharForeground(x + 2 + ox, y + oy, TCODColor(158, 158, 158)); + target->setCharForeground(x + 3 + ox, y + oy, TCODColor(158, 158, 158)); + target->setCharForeground(x + 4 + ox, y + oy, TCODColor(100, 100, 100)); + if (hlight) + { + for (int i = 0; i < 5; i++) + { + target->setCharBackground(x + ox + i, y + oy, TCODColor::white); } } } @@ -143,37 +707,117 @@ void Vehicle::draw(TCODConsole* target, int ox, int oy) { if (!(tiles[y * width + x].is_outside && tiles[y * width + x].ch != ']')) { - target->setChar(x + ox, y + oy, tiles[y * width + x].ch); - target->setCharForeground(x + ox, y + oy, tiles[y * width + x].fore); - target->setCharBackground(x + ox, y + oy, tiles[y * width + x].back); + if (tiles[y * width + x].water >= 0.4f && tiles[y * width + x].ch != 235) + { + target->setChar(x + ox, y + oy, 247); + target->setCharForeground(x + ox, y + oy, TCODColor(0, 89, 178)); + target->setCharBackground(x + ox, y + oy, TCODColor(0, 32, 64)); + } + else if (tiles[y * width + x].water > 0.25f && tiles[y * width + x].ch != 235) + { + target->setChar(x + ox, y + oy, 126); + target->setCharForeground(x + ox, y + oy, TCODColor(0, 89, 178)); + target->setCharBackground(x + ox, y + oy, tiles[y * width + x].back); + } + else if (tiles[y * width + x].water > 0.0f && tiles[y * width + x].ch != 235 + && tiles[y * width + x].health == 1.0f) + { + target->setChar(x + ox, y + oy, 126); + target->setCharForeground(x + ox, y + oy, TCODColor(51, 153, 255)); + target->setCharBackground(x + ox, y + oy, tiles[y * width + x].back); + } + else + { + target->setChar(x + ox, y + oy, tiles[y * width + x].ch); + target->setCharForeground(x + ox, y + oy, tiles[y * width + x].fore); + target->setCharBackground(x + ox, y + oy, tiles[y * width + x].back); + + if (tiles[y * width + x].health <= 0.05f) + { + target->setChar(x + ox, y + oy, tiles[y * width + x].blocks_player ? 8 : ','); + target->setCharForeground(x + ox, y + oy, TCODColor(158, 158, 158)); + target->setCharBackground(x + ox, y + oy, TCODColor(80, 80, 80)); + } + else if (tiles[y * width + x].health <= 0.25f) + { + target->setChar(x + ox, y + oy, tiles[y * width + x].blocks_player ? 10 : '%'); + target->setCharForeground(x + ox, y + oy, TCODColor(158, 158, 158)); + } + else if (tiles[y * width + x].health <= 0.65f) + { + target->setChar(x + ox, y + oy, tiles[y * width + x].blocks_player ? 15 : '+'); + target->setCharForeground(x + ox, y + oy, TCODColor(158, 158, 158)); + } + } } } } + int torpedo_hlight = -1; + // Draw torpedoes for (int i = 0; i < torpedoes.size(); i++) { Torpedo& tp = torpedoes[i]; - target->setChar(tp.x + ox, tp.y + oy, 238); - target->setChar(tp.x + 1 + ox, tp.y + oy, TCOD_CHAR_DHLINE); - target->setChar(tp.x + 2 + ox, tp.y + oy, TCOD_CHAR_DHLINE); - target->setChar(tp.x + 3 + ox, tp.y + oy, TCOD_CHAR_DHLINE); - target->setChar(tp.x + 4 + ox, tp.y + oy, 232); - target->setCharForeground(tp.x + ox, tp.y + oy, TCODColor(140, 70, 0)); - target->setCharForeground(tp.x + 1 + ox, tp.y + oy, TCODColor(158, 158, 158)); - target->setCharForeground(tp.x + 2 + ox, tp.y + oy, TCODColor(158, 158, 158)); - target->setCharForeground(tp.x + 3 + ox, tp.y + oy, TCODColor(158, 158, 158)); - target->setCharForeground(tp.x + 4 + ox, tp.y + oy, TCODColor(100, 100, 100)); + + bool hlight = false; + if (selected != nullptr) + { + int x = ctx_x; + int y = ctx_y; + + if (!in_context_menu) + { + x = pos.cx - ox; + y = pos.cy - oy; + } + + if (x >= tp.x && x <= tp.x + 4 && y == tp.y) + { + hlight = true; + torpedo_hlight = i; + } + + + } + + + draw_torpedo(target, tp.x, tp.y, ox, oy, hlight); + + } + + for (int i = 0; i < torpedo_slots.size(); i++) + { + if (torpedo_slots[i].has_torpedo) + { + target->setChar(torpedo_slots[i].x + ox, torpedo_slots[i].y + oy, 238); + target->setCharForeground(torpedo_slots[i].x + ox, torpedo_slots[i].y + oy, TCODColor(140, 70, 0)); + } } // Draw crew bool selected_crew = false; for (int i = 0; i < crew.size(); i++) { - Crewmember& c = crew[i]; + FlightCrew& c = crew[i]; - target->setChar(c.x + ox, c.y + oy, c.is_captain ? 2 : 1); + int ch = c.gc->is_captain ? 2 : 1; + + if (blink) + { + if (c.is_pumping) + { + ch = 'P'; + } + + if (c.is_repairing) + { + ch = 'R'; + } + } + + target->setChar(c.x + ox, c.y + oy, ch); target->setCharForeground(c.x + ox, c.y + oy, TCODColor(240, 240, 240)); if (pos.cx - ox == c.x && pos.cy - oy == c.y) @@ -181,7 +825,7 @@ void Vehicle::draw(TCODConsole* target, int ox, int oy) target->setCharBackground(c.x + ox, c.y + oy, TCODColor(200, 200, 200)); target->setCharForeground(c.x + ox, c.y + oy, TCODColor(128, 128, 128)); - if (pos.lbutton_pressed && !in_context_menu) + if ((pos.lbutton_pressed && !(g_key.lctrl || g_key.rctrl)) && !in_context_menu) { selected = &crew[i]; selected_crew = true; @@ -197,7 +841,7 @@ void Vehicle::draw(TCODConsole* target, int ox, int oy) } - if (pos.lbutton_pressed && !selected_crew && !in_context_menu) + if ((pos.lbutton_pressed && !(g_key.lctrl || g_key.rctrl)) && !selected_crew && !in_context_menu) { selected = nullptr; } @@ -224,7 +868,7 @@ void Vehicle::draw(TCODConsole* target, int ox, int oy) target->setCharBackground(v2.first + ox, v2.second + oy, TCODColor(200, 200, 200)); } - if (pos.lbutton_pressed && !selected_crew && !in_context_menu) + if ((pos.lbutton_pressed && !(g_key.lctrl || g_key.rctrl)) && !selected_crew && !in_context_menu) { if (workbenches[i]->is_crewed()) { @@ -242,136 +886,7 @@ void Vehicle::draw(TCODConsole* target, int ox, int oy) } // Context menu stuff - if (selected != nullptr && pos.rbutton_pressed) - { - in_context_menu = true; - ctx_x = pos.cx - ox; - ctx_y = pos.cy - oy; - - if (ctx_x < 0 || ctx_x >= width || ctx_y < 0 || ctx_y >= height) - { - in_context_menu = false; - } - } - - if (selected != nullptr) - { - if (pos.mbutton_pressed) - { - selected->path_to(pos.cx - ox, pos.cy - oy, *tcod_map); - } - } - - if (in_context_menu) - { - int rx = ctx_x + ox; - int ry = ctx_y + oy; - - if (blink) - { - target->setChar(rx, ry, 'X'); - target->setCharForeground(ctx_x + ox, ctx_y + oy, TCODColor::white); - } - - std::vector menu_items; - if (!tiles[ctx_y * width + ctx_x].blocks_player) - { - menu_items.push_back("Move Here"); - } - - menu_items.push_back("View Crewmember"); - - - - int menu_x, menu_y; - int menu_w = 0, menu_h = menu_items.size() + 1; - - for (int i = 0; i < menu_items.size(); i++) - { - if (menu_items[i].size() >= menu_w) - { - menu_w = menu_items[i].size(); - } - } - - menu_w += 3; - - // Draw menu, choose direction carefully - if (rx >= WIDTH - menu_w - 1) - { - menu_x = rx - menu_w - 1; - } - else - { - menu_x = rx + 1; - } - - if (ry >= HEIGHT - menu_h - 1) - { - menu_y = ry - menu_h - 1; - } - else - { - menu_y = ry; - } - - for (int x = menu_x; x < menu_x + menu_w + 1; x++) - { - for (int y = menu_y; y < menu_y + menu_h + 1; y++) - { - target->setChar(x, y, ' '); - target->setCharForeground(x, y, TCODColor::white); - target->setCharBackground(x, y, TCODColor::black); - } - } - - Drawing::draw_rectangle(target, menu_x, menu_y, menu_w, menu_h, false); - - target->setAlignment(TCOD_CENTER); - - for (int i = 0; i < menu_items.size(); i++) - { - bool highlight = false; - - if (pos.cx >= menu_x && pos.cx <= menu_x + menu_w - && pos.cy == menu_y + 1 + i) - { - highlight = true; - } - - if (highlight) - { - target->setDefaultForeground(TCODColor::black); - target->setDefaultBackground(TCODColor::white); - } - - for (int x0 = 1; x0 < menu_w; x0++) - { - target->setCharBackground(menu_x + x0, menu_y + i + 1, target->getDefaultBackground()); - } - - target->printf(menu_x + menu_w / 2, menu_y + 1 + i, menu_items[i].c_str()); - - target->setDefaultForeground(TCODColor::white); - target->setDefaultBackground(TCODColor::black); - - if (highlight && pos.lbutton_pressed) - { - if (menu_items[i] == "Move Here") - { - selected->path_to(ctx_x, ctx_y, *tcod_map); - } - } - - } - - if (pos.lbutton_pressed) - { - in_context_menu = false; - } - - target->setAlignment(TCOD_LEFT); - } + do_context_menu(target, ox, oy, pos, torpedo_hlight); } @@ -441,7 +956,7 @@ void Vehicle::move_order(Direction dir) - maneouver->get_crewman()->speak(Speech::positive() + Speech::heading() + dir_name); + get_captain()->gc->speak("Go " + dir_name + "!"); } } @@ -452,22 +967,22 @@ void Vehicle::speed_order(Speed speed) std::string name; if (speed == STOP) { - name = "Stopping!"; + name = "Stop!"; maneouver->wanted_velocity = 0.0f; } else if (speed == SLOW) { - name = "Moving slowly!"; + name = "Move slow!"; maneouver->wanted_velocity = 0.25f; } else if (speed == MEDIUM) { - name = "Moving normally!"; + name = "Move normally!"; maneouver->wanted_velocity = 0.5f; } else if (speed == FAST) { - name = "Moving fast!"; + name = "Move fast!"; maneouver->wanted_velocity = 0.75f; } else @@ -476,14 +991,105 @@ void Vehicle::speed_order(Speed speed) maneouver->wanted_velocity = 1.0f; } - maneouver->get_crewman()->speak(name); + get_captain()->gc->speak(name); + } +} + +void Vehicle::damage(float power, int x, int y) +{ + if (power < 1.0f) + { + power = 1.0f; + } + + if (x == -1) + { + x = g_random->getInt(0, width); + } + + if (y == -1) + { + y = g_random->getInt(0, height); + } + + int poweri = (int)ceil(power); + + float xf = (float)x; + float yf = (float)y; + + for (int x0 = x - poweri; x0 < x + poweri; x0++) + { + for (int y0 = y - poweri; y0 < y + poweri; y0++) + { + float x0f = (float)x0; + float y0f = (float)y0; + + float dx = x0f - xf; + float dy = y0f - yf; + + float dist = dx * dx + dy * dy; + + float chance = power / dist; + + if (g_random->getFloat(0.0f, 1.0f) <= chance) + { + if (x0 >= 0 && y0 >= 0 && x0 < width - 1 && y0 < height - 1) + { + tiles[y0 * width + x0].health -= 1.0f / sqrt(dist); + if (tiles[y0 * width + x0].health < 0.0f) + { + tiles[y0 * width + x0].health = 0.0f; + } + } + } + } + } +} + +void Vehicle::electric_shock() +{ + if (battery_aux >= 0.25f / 3.0f) + { + battery_aux -= 0.25f / 3.0f - 0.0001f; + g_soloud->play(shock, 2.0f); + + ExplosionEffect fx = ExplosionEffect(); + fx.x = x; + fx.y = y; + fx.t = 0.3f; + fx.dist = 1.0f; + + scene->expl_effects.push_back(fx); + + for (int i = 0; i < in_map->entities.size(); i++) + { + float dx = in_map->entities[i]->get_x() - x; + float dy = in_map->entities[i]->get_y() - y; + + float dist = sqrt(dx * dx + dy * dy); + + if (dist < 0.35f) + { + in_map->entities[i]->damage(0.5f); + } + } } } Vehicle::Vehicle(FlightMap* map) { + water_tick = 0.25f; + + torpedo_0 = false; + torpedo_1 = false; + torpedo_2 = false; + battery_a = 1.0f; + battery_b = 1.0f; + battery_aux = 0.25f; + diesel = 1.0f; + engines_on_diesel = false; blink = true; blinkt = 0.5f; @@ -513,11 +1119,15 @@ Vehicle::Vehicle(FlightMap* map) engines_low.load("engines_low.wav"); engines_low.setLooping(true); moving_fast.load("moving_fast.wav"); moving_fast.setLooping(true); moving_slow.load("moving_slow.wav"); moving_slow.setLooping(true); + waterflow.load("waterflow.wav"); waterflow.setLooping(true); engines_high_h = g_soloud->play(engines_high); engines_low_h = g_soloud->play(engines_low); moving_fast_h = g_soloud->play(moving_fast); moving_slow_h = g_soloud->play(moving_slow); + waterflow_h = g_soloud->play(waterflow); + + shock.load("shock.wav"); this->in_map = map; @@ -591,14 +1201,15 @@ Vehicle::Vehicle(FlightMap* map) // Load crewmember in chairs and assign them the occupation if (tile->ch == 239) { - Crewmember c = Crewmember(); + FlightCrew c = FlightCrew(new Crewmember()); c.x = tx; c.y = ty; - c.is_captain = tile->fore == TCODColor(222, 211, 195); + c.gc->is_captain = tile->fore == TCODColor(222, 211, 195); + c.cx = tx; c.cy = ty; Workbench* work = nullptr; - if (c.is_captain) + if (c.gc->is_captain) { // Captain seat } @@ -642,14 +1253,14 @@ Vehicle::Vehicle(FlightMap* map) } else { - work = new Listening(); + work = new Targeting(); // Listening station work->tiles.push_back(std::make_pair(tx, ty + 1)); work->tiles.push_back(std::make_pair(tx + 1, ty + 1)); work->tiles.push_back(std::make_pair(tx - 1, ty + 1)); - listening = work; + targeting = (Targeting*)work; } } else if (loaded_sub.getChar(x - 1, y) == 155) @@ -670,7 +1281,7 @@ Vehicle::Vehicle(FlightMap* map) work->tiles.push_back(std::make_pair(tx - 1, ty + 0)); work->tiles.push_back(std::make_pair(tx - 1, ty + 1)); - machines = work; + machines = (Machines*)work; } else if(loaded_sub.getChar(x, y + 1) == '{') { @@ -723,6 +1334,8 @@ Vehicle::Vehicle(FlightMap* map) { m1x = tx; m1y = ty; } + + tile->health = 1.0f; if (loaded_sub.getCharBackground(x, y) == TCODColor(4, 24, 30)) { @@ -738,6 +1351,7 @@ Vehicle::Vehicle(FlightMap* map) tile->blocks_player = false; tile->blocks_light = false; tile->blocks_water = false; + // Load properties if ((tile->ch >= 179 && tile->ch <= 218) || tile->ch == '=') @@ -746,7 +1360,19 @@ Vehicle::Vehicle(FlightMap* map) tile->blocks_player = true; tile->blocks_light = true; tile->blocks_water = true; - tile->health = 100.0f; + } + + if (tile->ch == '=') + { + tile->blocks_player = false; + + TorpedoSlot slot = TorpedoSlot(); + slot.x = tx; + slot.y = ty; + slot.has_torpedo = false; + slot.crew_coming = -1; + + torpedo_slots.push_back(slot); } if (tile->ch == 'o') @@ -755,7 +1381,6 @@ Vehicle::Vehicle(FlightMap* map) tile->blocks_player = false; tile->blocks_light = true; tile->blocks_water = true; - tile->health = 60.0f; } } } @@ -774,6 +1399,8 @@ Vehicle::Vehicle(FlightMap* map) tcod_map->setProperties(x, y, transparent, walkable); } } + + dummy_tile.blocks_water = true; } diff --git a/src/vehicle/Vehicle.h b/src/vehicle/Vehicle.h index 889cd51..e9e3f73 100644 --- a/src/vehicle/Vehicle.h +++ b/src/vehicle/Vehicle.h @@ -2,28 +2,33 @@ #include #include "libtcod.h" #include "../defines.h" -#include "Crewmember.h" +#include "FlightCrew.h" #include "../Speech.h" #include "../flight/FlightMap.h" #include "workbench/Sonar.h" #include "workbench/Radio.h" -#include "workbench/Listening.h" +#include "workbench/Targeting.h" #include "workbench/Machines.h" #include "workbench/Maneouver.h" #include "workbench/Battery.h" #include "workbench/Periscope.h" +class FlightScene; struct VehicleTile { + // Health always goes from 0-1 float health; float water; + float prev_water; + int ch; TCODColor fore; TCODColor back; + bool really_blocks_water; bool blocks_water; bool blocks_player; bool blocks_light; @@ -36,15 +41,61 @@ struct Torpedo int x, y; }; +struct TorpedoSlot +{ + int x, y; + bool has_torpedo; + int crew_coming; +}; class Vehicle { +private: + + VehicleTile dummy_tile; + VehicleTile& get_tile_for_water(int x, int y); + + void update_water_flow(float dt); + void update_crew(float dt); + void update_bubbles(float dt); + + void do_context_menu(TCODConsole* target, int ox, int oy, TCOD_mouse_t pos, int torpedo_hlight); + public: + FlightScene* scene; + + static constexpr float WATER_FLOW_RATE = 4.0f; + static constexpr float WATER_PUMP_RATE = 2.0f; + static constexpr float REPAIR_RATE = 0.25f; + + // Total added water level, having too much water slows the submarine + // considerably + float water_level; + // Water added per second + float water_flow; + + float noise; + + float water_tick; + + float battery_a; + float battery_b; + float battery_aux; + float diesel; + + bool engines_on_diesel; + + float possible_speed; + + bool torpedo_0; + bool torpedo_1; + bool torpedo_2; + TCODMap* tcod_map; - Crewmember* selected; + FlightCrew* selected; bool in_context_menu; int ctx_x, ctx_y; @@ -80,17 +131,23 @@ class Vehicle std::vector bubbles; + std::vector torpedo_slots; + float flip_timer; SoLoud::Wav engines_high; SoLoud::Wav engines_low; SoLoud::Wav moving_fast; SoLoud::Wav moving_slow; + SoLoud::Wav waterflow; + + SoLoud::Wav shock; SoLoud::handle engines_high_h; SoLoud::handle engines_low_h; SoLoud::handle moving_fast_h; SoLoud::handle moving_slow_h; + SoLoud::handle waterflow_h; Workbench* workbench_open; @@ -106,13 +163,17 @@ class Vehicle // they only have visual and physical (water) effects std::vector tiles; std::vector torpedoes; - std::vector crew; + std::vector crew; std::vector workbenches; - Workbench *periscope, *machines, *battery, *listening; + Workbench *periscope, *battery; Sonar* sonar; Radio* radio; Maneouver* maneouver; + Machines* machines; + Targeting* targeting; + + FlightCrew* get_captain(); int m0x, m0y, m1x, m1y; @@ -123,6 +184,11 @@ class Vehicle void move_order(Direction dir); void speed_order(Speed speed); + void damage(float power, int x = - 1, int y = - 1); + + // Consumes half the SHK battery + void electric_shock(); + std::pair get_tile() { return std::make_pair((int)floor(x), (int)floor(y)); diff --git a/src/vehicle/workbench/Battery.cpp b/src/vehicle/workbench/Battery.cpp index 168b40b..2d27de7 100644 --- a/src/vehicle/workbench/Battery.cpp +++ b/src/vehicle/workbench/Battery.cpp @@ -1,9 +1,27 @@ #include "Battery.h" +#include "../Vehicle.h" +static void charge_meter(TCODConsole* target, int x, int y, float charge, float max = 1.0f) +{ + Drawing::draw_rectangle(target, x, y, 30, 2); + + float v = charge / max; + for (int i = 0; i < v * 29; i++) + { + target->setChar(x + i + 1, y + 1, 254); + } +} -Battery::Battery() +Battery::Battery() : console(50, 50) { + ba_to_bb = false; + bb_to_ba = false; + ba_to_aux = false; + bb_to_aux = false; + ba_to_eng = false; + bb_to_eng = false; + } @@ -13,14 +31,172 @@ Battery::~Battery() bool Battery::update(float dt) { + get_vehicle()->possible_speed = 0.0f; + + float a_consume = 0.0f; + float b_consume = 0.0f; + float aux_consume = 0.0f; + + if (get_vehicle()->battery_a > 0.0f) + { + if (ba_to_eng) + { + get_vehicle()->possible_speed += 0.75f; + a_consume += ENGINE_RATE * get_vehicle()->velocity; + } + + if (ba_to_bb) + { + if (get_vehicle()->battery_b < 1.0f - CHARGE_RATE * dt) + { + a_consume += CHARGE_RATE; + b_consume -= CHARGE_RATE; + } + } + + if (ba_to_aux) + { + if (get_vehicle()->battery_aux < 0.25f - CHARGE_RATE * dt) + { + a_consume += CHARGE_RATE; + aux_consume -= CHARGE_RATE; + } + } + } + else + { + get_vehicle()->battery_a = 0.0f; + } + + if (get_vehicle()->battery_b > 0.0f) + { + if (bb_to_eng) + { + get_vehicle()->possible_speed += 0.75f; + b_consume += ENGINE_RATE * get_vehicle()->velocity; + } + + if (bb_to_ba) + { + if (get_vehicle()->battery_a < 1.0f - CHARGE_RATE * dt) + { + b_consume += CHARGE_RATE; + a_consume -= CHARGE_RATE; + } + } + + if (bb_to_aux) + { + if (get_vehicle()->battery_aux < 0.25f - CHARGE_RATE * dt) + { + b_consume += CHARGE_RATE; + aux_consume -= CHARGE_RATE; + } + } + } + else + { + get_vehicle()->battery_b = 0.0f; + } + + if (get_vehicle()->battery_aux > 0.0f) + { + if (aux_to_a) + { + if (get_vehicle()->battery_a < 1.0f - CHARGE_RATE * dt) + { + aux_consume += CHARGE_RATE; + a_consume -= CHARGE_RATE; + } + } + + if (aux_to_b) + { + if (get_vehicle()->battery_b < 1.0f - CHARGE_RATE * dt) + { + aux_consume += CHARGE_RATE; + b_consume -= CHARGE_RATE; + } + } + + if (aux_to_eng) + { + get_vehicle()->possible_speed += 0.25f; + aux_consume += ENGINE_RATE * 0.5f * get_vehicle()->velocity; + } + } + else + { + get_vehicle()->battery_aux = 0.0f; + } + + get_vehicle()->battery_a -= a_consume * dt; + get_vehicle()->battery_b -= b_consume * dt; + get_vehicle()->battery_aux -= aux_consume * dt; + + if (get_vehicle()->battery_a >= 1.0f) + { + get_vehicle()->battery_a = 1.0f; + } + + if (get_vehicle()->battery_b >= 1.0f) + { + get_vehicle()->battery_b = 1.0f; + } + + if (get_vehicle()->battery_aux >= 0.25f) + { + get_vehicle()->battery_aux = 0.25f; + } + + if (get_vehicle()->possible_speed >= 1.0f) + { + get_vehicle()->possible_speed = 1.0f; + } + + get_vehicle()->possible_speed = get_vehicle()->engines_on_diesel ? 1.0f : get_vehicle()->possible_speed; + return false; } void Battery::draw(int rx, int ry) { -} + console.clear(); -TCODConsole * Battery::get_console() -{ - return nullptr; + int by = 34; + + Drawing::draw_switch(&console, 7, by + 0, rx, ry, &ba_to_bb, "BAT.A > BAT.B"); + Drawing::draw_switch(&console, 7, by + 5, rx, ry, &ba_to_aux, "BAT.A > B.SHK"); + Drawing::draw_switch(&console, 7, by + 10, rx, ry, &ba_to_eng, "BAT.A > MOTOR"); + + Drawing::draw_switch(&console, 23, by + 0, rx, ry, &bb_to_ba, "BAT.B > BAT.A"); + Drawing::draw_switch(&console, 23, by + 5, rx, ry, &bb_to_aux, "BAT.B > B.SHK"); + Drawing::draw_switch(&console, 23, by + 10, rx, ry, &bb_to_eng, "BAT.B > MOTOR"); + + Drawing::draw_switch(&console, 39, by + 0, rx, ry, &aux_to_a, "B.SHK > BAT.A"); + Drawing::draw_switch(&console, 39, by + 5, rx, ry, &aux_to_b, "B.SHK > BAT.B"); + Drawing::draw_switch(&console, 39, by + 10, rx, ry, &aux_to_eng, "B.SHK > MOTOR"); + + console.hline(0, by - 4, 50); + console.vline(16, by - 4, 20); + console.vline(32, by - 4, 20); + + console.setChar(16, by - 4, 194); + console.setChar(32, by - 4, 194); + + // Draw indicators + + console.printf(2, 2, "BAT. ID"); + console.printf(20, 2, "CHARGE"); + console.printf(43, 2, "TEMP"); + + console.printf(2, 6, "BAT.A"); + charge_meter(&console, 9, 5, get_vehicle()->battery_a); + + console.printf(2, 10, "BAT.B"); + charge_meter(&console, 9, 9, get_vehicle()->battery_b); + + console.printf(2, 14, "B.SHK"); + charge_meter(&console, 9, 13, get_vehicle()->battery_aux, 0.25f); } + diff --git a/src/vehicle/workbench/Battery.h b/src/vehicle/workbench/Battery.h index fba0ad8..4165c5e 100644 --- a/src/vehicle/workbench/Battery.h +++ b/src/vehicle/workbench/Battery.h @@ -6,21 +6,45 @@ #include "libtcod.h" #include "Workbench.h" +#include "../../Drawing.h" + class Battery : public Workbench { +private: + public: + + static constexpr float ENGINE_RATE = 0.007f; + static constexpr float CHARGE_RATE = 0.050f; + + bool ba_to_bb; + bool bb_to_ba; + bool ba_to_aux; + bool bb_to_aux; + bool ba_to_eng; + bool bb_to_eng; + + bool aux_to_a; + bool aux_to_b; + bool aux_to_eng; + + TCODConsole console; + Battery(); ~Battery(); // Inherited via Workbench virtual bool update(float dt) override; virtual void draw(int rx, int ry) override; - virtual TCODConsole * get_console() override; + virtual TCODConsole* get_console() override + { + return &console; + } virtual std::string get_name() override { - return "Battery Control Panel"; + return "Battery Station"; } }; diff --git a/src/vehicle/workbench/Listening.cpp b/src/vehicle/workbench/Listening.cpp deleted file mode 100644 index 2969ad6..0000000 --- a/src/vehicle/workbench/Listening.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "Listening.h" - - - -Listening::Listening() -{ -} - - -Listening::~Listening() -{ -} - -bool Listening::update(float dt) -{ - return false; -} - -void Listening::draw(int rx, int ry) -{ -} - -TCODConsole * Listening::get_console() -{ - return nullptr; -} diff --git a/src/vehicle/workbench/Listening.h b/src/vehicle/workbench/Listening.h deleted file mode 100644 index 1981963..0000000 --- a/src/vehicle/workbench/Listening.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include -#include "../../Date.h" -#include "libtcod.h" - -#include "Workbench.h" - -class Listening : public Workbench -{ -public: - Listening(); - ~Listening(); - - // Inherited via Workbench - virtual bool update(float dt) override; - virtual void draw(int rx, int ry) override; - virtual TCODConsole * get_console() override; - - virtual std::string get_name() override - { - return "Listening Station"; - } -}; - diff --git a/src/vehicle/workbench/Machines.cpp b/src/vehicle/workbench/Machines.cpp index ca58e92..d40c3ec 100644 --- a/src/vehicle/workbench/Machines.cpp +++ b/src/vehicle/workbench/Machines.cpp @@ -1,9 +1,20 @@ #include "Machines.h" +#include "../Vehicle.h" - -Machines::Machines() +Machines::Machines() : console(40, 50) { + eng0_running = false; + eng1_running = false; + eng0_out = BOTH_BATTERIES; + eng1_out = ENGINES; + + diesel.load("diesel.wav"); + diesel.setLooping(true); + + diesel_off.load("diesel_off.wav"); + + diesel_h = g_soloud->play(diesel, 0.0f); } @@ -13,14 +24,142 @@ Machines::~Machines() bool Machines::update(float dt) { + if (!get_vehicle()->breathing) + { + if (eng0_running || eng1_running) + { + // Play starving sound + g_soloud->play(diesel_off, 2.0f); + } + + eng0_running = false; + eng1_running = false; + } + + if (eng0_running || eng1_running) + { + g_soloud->setVolume(diesel_h, 1.0f); + } + else + { + g_soloud->setVolume(diesel_h, 0.0f); + } + + float to_bat_a = 0.0f; + float to_bat_b = 0.0f; + float to_engines = 0.0f; + + if (eng0_running) + { + if (eng0_out == BATTERY_A) + { + to_bat_a += 0.5f; + } + else if (eng0_out == BATTERY_B) + { + to_bat_b += 0.5f; + } + else if (eng0_out == BOTH_BATTERIES) + { + to_bat_a += 0.25f; + to_bat_b += 0.25f; + } + else + { + to_engines += 0.5f; + } + } + + if (eng1_running) + { + if (eng1_out == BATTERY_A) + { + to_bat_a += 0.5f; + } + else if (eng1_out == BATTERY_B) + { + to_bat_b += 0.5f; + } + else if (eng1_out == BOTH_BATTERIES) + { + to_bat_a += 0.25f; + to_bat_b += 0.25f; + } + else + { + to_engines += 0.5f; + } + } + + get_vehicle()->battery_a += to_bat_a * dt * RATE; + get_vehicle()->battery_b += to_bat_b * dt * RATE; + + if (get_vehicle()->battery_a > 1.0f) + { + get_vehicle()->battery_a = 1.0f; + } + + if (get_vehicle()->battery_b > 1.0f) + { + get_vehicle()->battery_b = 1.0f; + } + + + get_vehicle()->engines_on_diesel = to_engines != 0.0f; + return false; } void Machines::draw(int rx, int ry) { -} + console.clear(); -TCODConsole * Machines::get_console() -{ - return nullptr; + console.setAlignment(TCOD_CENTER); + + if (get_vehicle()->breathing) + { + console.setDefaultForeground(TCODColor::lightGreen); + console.printf(console.getWidth() / 2 - 1, 2, "OXYGEN AVAILABLE"); + console.printf(console.getWidth() / 2 - 1, 4, "ENGINES READY"); + } + else + { + console.setDefaultForeground(TCODColor::lightRed); + console.printf(console.getWidth() / 2 - 1, 2, "OXYGEN UNAVAILABLE"); + console.printf(console.getWidth() / 2 - 1, 4, "ENGINES CANNOT RUN"); + } + + console.setDefaultForeground(TCODColor::white); + + console.setAlignment(TCOD_LEFT); + + console.hline(0, 6, console.getWidth()); + + int y0 = 12; + console.setAlignment(TCOD_CENTER); + console.printf(console.getWidth() / 2 - 1, y0 - 5, "Generator A"); + console.setAlignment(TCOD_LEFT); + + if (Drawing::draw_switch(&console, 9, y0 + 3, rx, ry, &eng0_running, "Generator Active") && eng0_running == false) + { + g_soloud->play(diesel_off, 2.0f); + } + + Drawing::draw_four_choice(&console, 26, y0, rx, ry, (int*)&eng0_out, "BAT A", "BATTS", "BAT B", "MOTOR"); + + y0 = console.getHeight() / 2 + 6; + + console.setAlignment(TCOD_CENTER); + console.printf(console.getWidth() / 2 - 1, y0 - 5, "Generator B"); + console.setAlignment(TCOD_LEFT); + + if (Drawing::draw_switch(&console, 9, y0 + 3, rx, ry, &eng1_running, "Generator Active") && eng0_running == false) + { + g_soloud->play(diesel_off, 2.0f); + } + + Drawing::draw_four_choice(&console, 26, y0, rx, ry, (int*)&eng1_out, "BAT A", "BATTS", "BAT B", "MOTOR"); + + + console.hline(0, console.getHeight() / 2, console.getWidth()); } diff --git a/src/vehicle/workbench/Machines.h b/src/vehicle/workbench/Machines.h index b68641a..c3d6e3a 100644 --- a/src/vehicle/workbench/Machines.h +++ b/src/vehicle/workbench/Machines.h @@ -6,21 +6,55 @@ #include "libtcod.h" #include "Workbench.h" +#include "../../Drawing.h" + + +// If you unload a battery while it is loading +// it heats up and could cause a failure +// so strategic loading is neccesary +enum MachineOutput +{ + BATTERY_A, + BOTH_BATTERIES, + BATTERY_B, + ENGINES +}; class Machines : public Workbench { +private: + public: + + constexpr static float RATE = 0.04f; + + SoLoud::Wav diesel; + SoLoud::handle diesel_h; + + SoLoud::Wav diesel_off; + + bool eng0_running; + bool eng1_running; + + MachineOutput eng0_out; + MachineOutput eng1_out; + + TCODConsole console; + Machines(); ~Machines(); // Inherited via Workbench virtual bool update(float dt) override; virtual void draw(int rx, int ry) override; - virtual TCODConsole * get_console() override; + virtual TCODConsole* get_console() override + { + return &console; + } virtual std::string get_name() override { - return "Machine Control"; + return "Diesel Engines"; } }; diff --git a/src/vehicle/workbench/Maneouver.cpp b/src/vehicle/workbench/Maneouver.cpp index 8ae32ce..ae3277b 100644 --- a/src/vehicle/workbench/Maneouver.cpp +++ b/src/vehicle/workbench/Maneouver.cpp @@ -47,7 +47,7 @@ bool Maneouver::update(float dt) { if (is_crewed()) { - get_crewman()->speak(Speech::speed_done()); + get_crewman()->gc->speak(Speech::speed_done()); } said_speed = true; @@ -76,7 +76,7 @@ bool Maneouver::update(float dt) { if (is_crewed()) { - get_crewman()->speak(Speech::heading_done()); + get_crewman()->gc->speak(Speech::heading_done()); } said = true; @@ -97,8 +97,34 @@ bool Maneouver::update(float dt) } - get_vehicle()->angle = real_vehicle + dir * dt * 1.0f; + float possible_maneouver = 1.0f - get_vehicle()->water_level / 50.0f; + if (possible_maneouver < 0.2f) + { + possible_maneouver = 0.2f; + } + + get_vehicle()->angle = real_vehicle + dir * dt * 0.7f * possible_maneouver; + + } + + if (get_vehicle()->velocity > get_vehicle()->possible_speed && wanted_velocity > get_vehicle()->possible_speed + && get_vehicle()->velocity != 0.0) + { + get_vehicle()->velocity = get_vehicle()->possible_speed + 0.0000001f; + if (!said_max) + { + if (is_crewed()) + { + get_crewman()->gc->speak("Engine power too low!"); + said_max = true; + } + + } + } + else + { + said_max = false; } return false; diff --git a/src/vehicle/workbench/Maneouver.h b/src/vehicle/workbench/Maneouver.h index 017aa44..5f26950 100644 --- a/src/vehicle/workbench/Maneouver.h +++ b/src/vehicle/workbench/Maneouver.h @@ -22,6 +22,7 @@ class Maneouver : public Workbench bool said; bool said_speed; + bool said_max; TCODConsole console; diff --git a/src/vehicle/workbench/Periscope.cpp b/src/vehicle/workbench/Periscope.cpp index b7b3aec..c073dce 100644 --- a/src/vehicle/workbench/Periscope.cpp +++ b/src/vehicle/workbench/Periscope.cpp @@ -1,6 +1,6 @@ #include "Periscope.h" #include "../Vehicle.h" - +#include "../../flight/FlightScene.h" Periscope::Periscope() : console(49, 49) { @@ -36,7 +36,7 @@ void Periscope::draw(int rx, int ry) TCODMap view_map = TCODMap(w, h); - std::vector& entities = get_vehicle()->in_map->get_entities(); + std::vector& entities = get_vehicle()->in_map->get_entities(); float vx = get_vehicle()->x; float vy = get_vehicle()->y; @@ -81,30 +81,18 @@ void Periscope::draw(int rx, int ry) // Draw entities and features for (int i = 0; i < entities.size(); i++) - { - - - float exf = entities[i].x - vx; - float eyf = entities[i].y - vy; + { + if (entities[i]->is_alive()) + { + float exf = entities[i]->get_position().first - vx; + float eyf = entities[i]->get_position().second - vy; - int ex = (int)floor(exf * (float)w * 0.5f + 24.0f); - int ey = (int)floor(eyf * (float)h * 0.5f + 24.0f); + int ex = (int)floor(exf * (float)w * 0.5f + 24.0f); + int ey = (int)floor(eyf * (float)h * 0.5f + 24.0f); - if (ex >= 0 && ex < w && ey >= 0 && ey < h) - { - switch (entities[i].type) + if (ex >= 0 && ex < w && ey >= 0 && ey < h) { - case E_BASE: - console.setChar(ex, ey, 127); - break; - case E_NEST: - console.setChar(ex, ey, 15); - break; - case E_STATION: - console.setChar(ex, ey, 234); - break; - default: - break; + console.setChar(ex, ey, entities[i]->get_symbol()); } } } @@ -120,7 +108,8 @@ void Periscope::draw(int rx, int ry) yf = (yf - 0.5f) * 2.0f; float dist = sqrt(xf * xf + yf * yf) * 7.0f; - float bright = 1.0f / (dist * dist); + float bright = 3.0f / (dist * dist); + TCODColor lit = TCODColor(207.0f, 1.0f / bright, bright); @@ -198,4 +187,46 @@ void Periscope::draw(int rx, int ry) } } + // Draw explosions + for (int i = 0; i < get_vehicle()->scene->expl_effects.size(); i++) + { + ExplosionEffect fx = get_vehicle()->scene->expl_effects[i]; + + + for (int x = 0; x < w; x++) + { + for (int y = 0; y < h; y++) + { + // xf, yf (-1, 1) + float xf = (float)x / (float)w; + float yf = (float)y / (float)h; + xf = (xf - 0.5f) * 2.0f; + yf = (yf - 0.5f) * 2.0f; + + float tx = vx + xf; + float ty = vy + yf; + + float dx = tx - fx.x; + float dy = ty - fx.y; + + float dist = sqrt(dx * dx + dy * dy); + + if (dist <= fx.t * 2.0f) + { + int n = min(fx.t * 255.0f, 255); + console.setCharForeground(x, y, TCODColor(n, n, n)); + if (dist <= fx.t) + { + console.setChar(x, y, 178); + } + else + { + console.setChar(x, y, '.'); + } + + } + } + } + } + } diff --git a/src/vehicle/workbench/Periscope.h b/src/vehicle/workbench/Periscope.h index a45b9c4..0b16773 100644 --- a/src/vehicle/workbench/Periscope.h +++ b/src/vehicle/workbench/Periscope.h @@ -19,10 +19,6 @@ class Periscope : public Workbench bool blink = false; float blinkt = 0.5f; - int msg; - - std::vector messages; - TCODConsole console; Periscope(); diff --git a/src/vehicle/workbench/Radio.cpp b/src/vehicle/workbench/Radio.cpp index c17c9d3..2e7e398 100644 --- a/src/vehicle/workbench/Radio.cpp +++ b/src/vehicle/workbench/Radio.cpp @@ -43,9 +43,9 @@ void Radio::draw(int rx, int ry) if (Drawing::draw_button(&console, console.getWidth() - 9, console.getHeight() - 5, rx, ry, '>')) { msg++; - if (msg > 0) + if (msg > messages.size() - 1) { - msg = 0; + msg = messages.size() - 1; } } @@ -96,7 +96,7 @@ void Radio::push_message(std::string nmsg) // If anybody is on the radio, say new message received if (is_crewed() && !is_open()) { - get_crewman()->speak("Captain, new radio message"); + get_crewman()->gc->speak("Captain, new radio message"); } msg = messages.size() - 1; diff --git a/src/vehicle/workbench/Sonar.cpp b/src/vehicle/workbench/Sonar.cpp index 7757c33..64cf0c2 100644 --- a/src/vehicle/workbench/Sonar.cpp +++ b/src/vehicle/workbench/Sonar.cpp @@ -14,7 +14,7 @@ bool Sonar::update(float dt) if (is_open()) { - draw_world(get_vehicle()->get_tile().first, get_vehicle()->get_tile().second, *get_vehicle()->in_map); + //draw_world(get_vehicle()->get_tile().first, get_vehicle()->get_tile().second, *get_vehicle()->in_map); } } @@ -27,6 +27,8 @@ bool Sonar::update(float dt) { sonar_radius = 0.0f; g_soloud->play(ping); + + draw_world((int)floor(get_vehicle()->x), (int)floor(get_vehicle()->y), *get_vehicle()->in_map); } @@ -37,8 +39,6 @@ bool Sonar::update(float dt) { return true; } - - draw_world((int)floor(get_vehicle()->x), (int)floor(get_vehicle()->y), *get_vehicle()->in_map); } return false; @@ -46,6 +46,11 @@ bool Sonar::update(float dt) void Sonar::draw(int rx, int ry) { + if (!sonar_active) + { + draw_world((int)floor(get_vehicle()->x), (int)floor(get_vehicle()->y), *get_vehicle()->in_map); + } + // Clear lower area console.rect(0, 49, 49, 60, true); @@ -151,8 +156,8 @@ void Sonar::draw(int rx, int ry) if (sonar_active) { g_soloud->play(ping); + draw_world((int)floor(get_vehicle()->x), (int)floor(get_vehicle()->y), *get_vehicle()->in_map); } - draw_world(lpx, lpy, *last_map); } console.setDefaultForeground(TCODColor::white); @@ -213,7 +218,7 @@ void Sonar::draw_world(int px, int py, FlightMap& map) lpy = py; last_map = ↦ // Run a visibility algorithm and draw - map.vmap.computeFov(px, py, sonar_active ? 24 : 1, true, FOV_BASIC); + map.vmap.computeFov(px, py, sonar_active ? 24 : 1, true, FOV_DIAMOND); for (int x = -24; x < 24; x++) { @@ -277,6 +282,63 @@ void Sonar::draw_world(int px, int py, FlightMap& map) } } + + // Draw entities + for (int i = 0; i < map.entities.size(); i++) + { + if (map.entities[i]->is_alive()) + { + auto type = map.entities[i]->get_type(); + if (type != E_BASE && type != E_STATION && type != E_NEST) + { + int ch = '*'; + + if (type == E_SUBMARINE) + { + ch = '!'; + } + else if (type == E_TORPEDO) + { + ch = '+'; + } + + + if (map.entities[i]->is_identified()) + { + ch = map.entities[i]->get_symbol(); + } + + auto fpos = map.entities[i]->get_position(); + int x = (int)floor(fpos.first); + int y = (int)floor(fpos.second); + + int mx = x - px; + int my = y - py; + + int sx = mx + 24; + int sy = my + 24; + + bool seen = true; + + + if (!map.vmap.isInFov(x, y)) + { + seen = false; + } + + if (seen) + { + console.setChar(sx, sy, ch); + map.entities[i]->set_visible(true); + } + else + { + map.entities[i]->set_visible(false); + } + + } + } + } } diff --git a/src/vehicle/workbench/Targeting.cpp b/src/vehicle/workbench/Targeting.cpp new file mode 100644 index 0000000..da6de26 --- /dev/null +++ b/src/vehicle/workbench/Targeting.cpp @@ -0,0 +1,363 @@ +#include "Targeting.h" +#include "../../flight/entity/EntityTorpedo.h" +#include "../Vehicle.h" +#include "../../flight/FlightScene.h" + +void Targeting::fire_torpedo() +{ + if (is_crewed()) + { + if (target == nullptr) + { + get_crewman()->gc->speak("No target assigned!"); + } + else + { + if (has_torpedo()) + { + if (torpedo_timer < 0.0f) + { + get_crewman()->gc->speak("Firing torpedo!"); + torpedo_timer = 4.0f; + g_soloud->play(torpedo_out, 6.0f); + firing = true; + } + } + else + { + get_crewman()->gc->speak("No torpedoes loaded!"); + } + } + } +} + +bool Targeting::has_torpedo() +{ + for (int i = 0; i < get_vehicle()->torpedo_slots.size(); i++) + { + if (get_vehicle()->torpedo_slots[i].has_torpedo) + { + return true; + } + } + + return false; +} + +Targeting::Targeting() : console(50, 50) +{ + torpedo_timer = 0.0f; + torpedo_out.load("torpedo_out.wav"); + target = nullptr; + + identify = nullptr; + identify_timer = 0.0f; +} + + +Targeting::~Targeting() +{ +} + +std::vector Targeting::get_visible_entities() +{ + std::vector out; + + for (int i = 0; i < get_vehicle()->in_map->entities.size(); i++) + { + FlightEntity* ent = get_vehicle()->in_map->entities[i]; + if (ent->is_visible() && + ent->get_type() != E_BASE && ent->get_type() != E_NEST && + ent->get_type() != E_STATION && ent->get_type() != E_TORPEDO + && ent->is_alive()) + { + out.push_back(get_vehicle()->in_map->entities[i]); + } + } + + return out; +} + +bool Targeting::update(float dt) +{ + + torpedo_timer -= dt; + + if (torpedo_timer < 1.8f) + { + if (firing) + { + // Fire the boy + EntityTorpedo* torpedo = new EntityTorpedo(target); + torpedo->velocity = 0.35f; + torpedo->heading = get_vehicle()->angle; + torpedo->safety = 1.0f; + torpedo->maneouver = 0.74f; + torpedo->lifetime = 25.0f; + + torpedo->set_position(get_vehicle()->x, get_vehicle()->y); + torpedo->init(get_vehicle()->in_map, get_vehicle()->scene); + + + get_vehicle()->in_map->entities.push_back(torpedo); + + for (int i = 0; i < get_vehicle()->torpedo_slots.size(); i++) + { + if (get_vehicle()->torpedo_slots[i].has_torpedo) + { + get_vehicle()->torpedo_slots[i].has_torpedo = false; + break; + } + } + + firing = false; + } + } + + if (torpedo_timer < 0.0f) + { + if (!said) + { + if (is_crewed() && has_torpedo()) + { + get_crewman()->gc->speak("Weapons ready!"); + said = true; + } + } + } + else + { + said = false; + } + + if (is_crewed()) + { + identify_timer -= dt; + + auto visibles = get_visible_entities(); + + if (identify != nullptr) + { + bool found = false; + for (int i = 0; i < visibles.size(); i++) + { + if (visibles[i] == identify) + { + found = true; + break; + } + } + + if (!found) + { + get_crewman()->gc->speak("Sonar target lost"); + identify = nullptr; + identify_timer = 0.0f; + } + } + + if (identify_timer <= 0.0f) + { + if (identify != nullptr) + { + identify->identify(); + + std::string name = identify->get_name(); + + get_crewman()->gc->speak("Identified a " + name); + identify = nullptr; + } + + // Automatically start finding a new one + for (int i = 0; i < visibles.size(); i++) + { + if (!visibles[i]->is_identified()) + { + identify = visibles[i]; + identify_timer = IDENTIFY_TIME; + } + } + } + } + + return false; +} + +void Targeting::draw(int rx, int ry) +{ + console.clear(); + + // Draw a list of visible entities, and show a identification + // progress meter. The user can click entities to target + // and it shows a little preview of the entity in a sonar + + auto visible = get_visible_entities(); + + std::vector sorted; + + // We first sort + for (int i = 0; i < visible.size(); i++) + { + if (visible[i]->is_identified()) + { + sorted.push_back(visible[i]); + } + } + + for (int i = 0; i < visible.size(); i++) + { + if (!visible[i]->is_identified()) + { + sorted.push_back(visible[i]); + } + } + + int selected = -1; + int highlighted = -1; + + TCOD_mouse_t mouse = TCODMouse::getStatus(); + + // Now draw the list + for (int i = 0; i < sorted.size(); i++) + { + if (mouse.cy - ry == i + 2) + { + highlighted = i; + } + + if (highlighted == i) + { + console.putChar(2, i + 2, '>'); + if (mouse.lbutton_pressed) + { + if (target != sorted[i]) + { + target = sorted[i]; + get_crewman()->gc->speak("Changed weapon's target"); + } + + } + + if (mouse.rbutton_pressed) + { + if (identify != sorted[i] && !sorted[i]->is_identified()) + { + identify = sorted[i]; + identify_timer = IDENTIFY_TIME; + get_crewman()->gc->speak("Identifying sonar signal"); + } + } + } + + if (identify == sorted[i]) + { + console.setChar(0, i + 2, 'I'); + console.setCharForeground(0, i + 2, TCODColor(255, 191, 0)); + } + + if (target == sorted[i]) + { + console.setChar(1, i + 2, 'T'); + console.setCharForeground(1, i + 2, TCODColor(255, 50, 0)); + } + + if (sorted[i]->is_identified()) + { + if (sorted[i]->get_type() != E_SUBMARINE) + { + console.setDefaultForeground(TCODColor(255, 128, 128)); + } + else + { + console.setDefaultForeground(TCODColor(128, 255, 128)); + } + } + + + if (sorted[i]->is_identified()) + { + console.putChar(3, i + 2, sorted[i]->get_symbol()); + } + else + { + console.putChar(3, i + 2, '?'); + } + + int xo = 3; + + console.setDefaultForeground(TCODColor::white); + + + + if (sorted[i]->is_identified()) + { + console.printf(2 + xo, i + 2, sorted[i]->get_name().c_str()); + } + else + { + console.printf(2 + xo, i + 2, "Unknown Signal"); + } + + xo = 19; + + // Distance + float dx = sorted[i]->get_x() - get_vehicle()->x; + float dy = sorted[i]->get_y() - get_vehicle()->y; + + float dist = sqrt(dx * dx + dy * dy); + + std::string str = ""; + str += std::to_string((int)round(dist * 100.0f)); + str += "m"; + + console.printf(2 + xo, i + 2, str.c_str()); + + + + } + + console.setDefaultForeground(TCODColor::grey); + // Draw small sonar + Drawing::draw_rectangle(&console, 32, -1, 32, 17, true); + + console.setDefaultForeground(TCODColor::white); + + + int sx, sy; + if (highlighted == -1) + { + sx = 16; + sy = 16; + } + else + { + float ex = sorted[highlighted]->get_x(); + float ey = sorted[highlighted]->get_y(); + + // Relative to the player + float rx = ex - get_vehicle()->x; + float ry = ey - get_vehicle()->y; + + // Relative to the sonar upper left corner + float ssx = rx + 24.0f; + float ssy = ry + 24.0f; + + sx = (int)ssx - 7; + sy = (int)ssy - 7; + + } + + if (get_vehicle()->sonar->is_crewed()) + { + get_vehicle()->sonar->draw(0, 0); + TCODConsole::blit(&get_vehicle()->sonar->console, sx, sy, 16, 16, &console, 33, 0); + } + + + +} + +TCODConsole* Targeting::get_console() +{ + return &console; +} diff --git a/src/vehicle/workbench/Targeting.h b/src/vehicle/workbench/Targeting.h new file mode 100644 index 0000000..9a2b3a7 --- /dev/null +++ b/src/vehicle/workbench/Targeting.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include "../../Date.h" +#include "libtcod.h" + +#include "Workbench.h" + +class FlightEntity; + +class Targeting : public Workbench +{ +public: + + FlightEntity* identify; + float identify_timer; + + constexpr static float IDENTIFY_TIME = 10.0f; + + FlightEntity* target; + + TCODConsole console; + + SoLoud::Wav torpedo_out; + + bool firing; + float torpedo_timer; + bool said; + + void fire_torpedo(); + bool has_torpedo(); + + Targeting(); + ~Targeting(); + + // Ignores torpedoes, bases, nests and stations + std::vector get_visible_entities(); + + // Inherited via Workbench + virtual bool update(float dt) override; + virtual void draw(int rx, int ry) override; + virtual TCODConsole * get_console() override; + + virtual std::string get_name() override + { + return "Targeting Station"; + } +}; + diff --git a/src/vehicle/workbench/Workbench.h b/src/vehicle/workbench/Workbench.h index 4a1be6c..baa6a5e 100644 --- a/src/vehicle/workbench/Workbench.h +++ b/src/vehicle/workbench/Workbench.h @@ -5,7 +5,7 @@ #include #include #include -#include "../Crewmember.h" +#include "../FlightCrew.h" class Vehicle; @@ -18,7 +18,7 @@ enum Window PERISCOPE, MACHINES, BATTERY, - LISTENING + TARGETING, }; class Workbench @@ -34,7 +34,7 @@ class Workbench public: - std::vector* crew; + std::vector* crew; // Chair x and y @@ -79,9 +79,9 @@ class Workbench virtual std::string get_name() = 0; - Crewmember* get_crewman() + FlightCrew* get_crewman() { - for (Crewmember& cm : *crew) + for (FlightCrew& cm : *crew) { if (cm.can_work_in(this) && cm.x == cx && cm.y == cy) { @@ -94,7 +94,7 @@ class Workbench bool is_crewed() { - for (Crewmember& cm : *crew) + for (FlightCrew& cm : *crew) { if (cm.can_work_in(this) && cm.x == cx && cm.y == cy) { diff --git a/torpedo_out.wav b/torpedo_out.wav new file mode 100644 index 0000000..cefc1da Binary files /dev/null and b/torpedo_out.wav differ diff --git a/waterflow.wav b/waterflow.wav new file mode 100644 index 0000000..d8f9da3 Binary files /dev/null and b/waterflow.wav differ diff --git a/worm/attack.wav b/worm/attack.wav new file mode 100644 index 0000000..d340395 Binary files /dev/null and b/worm/attack.wav differ diff --git a/worm/dive.wav b/worm/dive.wav new file mode 100644 index 0000000..c799b95 Binary files /dev/null and b/worm/dive.wav differ