summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/intro_gui.cpp196
-rw-r--r--src/main_gui.cpp14
-rw-r--r--src/viewport_func.h2
3 files changed, 210 insertions, 2 deletions
diff --git a/src/intro_gui.cpp b/src/intro_gui.cpp
index 7b02448f9..8d11a2bb3 100644
--- a/src/intro_gui.cpp
+++ b/src/intro_gui.cpp
@@ -11,12 +11,14 @@
#include "error.h"
#include "gui.h"
#include "window_gui.h"
+#include "window_func.h"
#include "textbuf_gui.h"
#include "network/network.h"
#include "genworld.h"
#include "network/network_gui.h"
#include "network/network_content.h"
#include "landscape_type.h"
+#include "landscape.h"
#include "strings_func.h"
#include "fios.h"
#include "ai/ai_gui.hpp"
@@ -25,6 +27,10 @@
#include "language.h"
#include "rev.h"
#include "highscore.h"
+#include "signs_base.h"
+#include "viewport_func.h"
+#include "vehicle_base.h"
+#include <regex>
#include "widgets/intro_widget.h"
@@ -33,13 +39,203 @@
#include "safeguards.h"
+
+/**
+ * A viewport command for the main menu background (intro game).
+ */
+struct IntroGameViewportCommand {
+ /** Horizontal alignment value. */
+ enum AlignmentH : byte {
+ LEFT,
+ CENTRE,
+ RIGHT,
+ };
+ /** Vertical alignment value. */
+ enum AlignmentV : byte {
+ TOP,
+ MIDDLE,
+ BOTTOM,
+ };
+
+ int command_index = 0; ///< Sequence number of the command (order they are performed in).
+ Point position{ 0, 0 }; ///< Calculated world coordinate to position viewport top-left at.
+ VehicleID vehicle = INVALID_VEHICLE; ///< Vehicle to follow, or INVALID_VEHICLE if not following a vehicle.
+ uint delay = 0; ///< Delay until next command.
+ int zoom_adjust = 0; ///< Adjustment to zoom level from base zoom level.
+ bool pan_to_next = false; ///< If true, do a smooth pan from this position to the next.
+ AlignmentH align_h = CENTRE; ///< Horizontal alignment.
+ AlignmentV align_v = MIDDLE; ///< Vertical alignment.
+
+ /**
+ * Calculate effective position.
+ * This will update the position field if a vehicle is followed.
+ * @param vp Viewport to calculate position for.
+ * @return Calculated position in the viewport.
+ */
+ Point PositionForViewport(const Viewport *vp)
+ {
+ if (this->vehicle != INVALID_VEHICLE) {
+ const Vehicle *v = Vehicle::Get(this->vehicle);
+ this->position = RemapCoords(v->x_pos, v->y_pos, v->z_pos);
+ }
+
+ Point p;
+ switch (this->align_h) {
+ case LEFT: p.x = this->position.x; break;
+ case CENTRE: p.x = this->position.x - vp->virtual_width / 2; break;
+ case RIGHT: p.x = this->position.x - vp->virtual_width; break;
+ }
+ switch (this->align_v) {
+ case TOP: p.y = this->position.y; break;
+ case MIDDLE: p.y = this->position.y - vp->virtual_height / 2; break;
+ case BOTTOM: p.y = this->position.y - vp->virtual_height; break;
+ }
+ return p;
+ }
+};
+
+
struct SelectGameWindow : public Window {
+ /** Vector of viewport commands parsed. */
+ std::vector<IntroGameViewportCommand> intro_viewport_commands;
+ /** Index of currently active viewport command. */
+ size_t cur_viewport_command_index;
+ /** Time spent (milliseconds) on current viewport command. */
+ uint cur_viewport_command_time;
+
+ /**
+ * Find and parse all viewport command signs.
+ * Fills the intro_viewport_commands vector and deletes parsed signs from the world.
+ */
+ void ReadIntroGameViewportCommands()
+ {
+ intro_viewport_commands.clear();
+
+ /* Regular expression matching the commands: T, spaces, integer, spaces, flags, spaces, integer */
+ const char *sign_langauge = "^T\\s*([0-9]+)\\s*([-+A-Z0-9]+)\\s*([0-9]+)";
+ std::regex re(sign_langauge, std::regex_constants::icase);
+
+ /* List of signs successfully parsed to delete afterwards. */
+ std::vector<SignID> signs_to_delete;
+
+ for (const Sign *sign : Sign::Iterate()) {
+ std::smatch match;
+ if (std::regex_search(sign->name, match, re)) {
+ IntroGameViewportCommand vc;
+ /* Sequence index from the first matching group. */
+ vc.command_index = std::stoi(match[1].str());
+ /* Sign coordinates for positioning. */
+ vc.position = RemapCoords(sign->x, sign->y, sign->z);
+ /* Delay from the third matching group. */
+ vc.delay = std::stoi(match[3].str()) * 1000; // milliseconds
+
+ /* Parse flags from second matching group. */
+ enum IdType {
+ ID_NONE, ID_VEHICLE
+ } id_type = ID_NONE;
+ for (char c : match[2].str()) {
+ if (isdigit(c)) {
+ if (id_type == ID_VEHICLE) {
+ vc.vehicle = vc.vehicle * 10 + (c - '0');
+ }
+ } else {
+ id_type = ID_NONE;
+ switch (toupper(c)) {
+ case '-': vc.zoom_adjust = +1; break;
+ case '+': vc.zoom_adjust = -1; break;
+ case 'T': vc.align_v = IntroGameViewportCommand::TOP; break;
+ case 'M': vc.align_v = IntroGameViewportCommand::MIDDLE; break;
+ case 'B': vc.align_v = IntroGameViewportCommand::BOTTOM; break;
+ case 'L': vc.align_h = IntroGameViewportCommand::LEFT; break;
+ case 'C': vc.align_h = IntroGameViewportCommand::CENTRE; break;
+ case 'R': vc.align_h = IntroGameViewportCommand::RIGHT; break;
+ case 'P': vc.pan_to_next = true; break;
+ case 'V': id_type = ID_VEHICLE; vc.vehicle = 0; break;
+ }
+ }
+ }
+
+ /* Successfully parsed, store. */
+ intro_viewport_commands.push_back(vc);
+ signs_to_delete.push_back(sign->index);
+ }
+ }
+
+ /* Sort the commands by sequence index. */
+ std::sort(intro_viewport_commands.begin(), intro_viewport_commands.end(), [](const IntroGameViewportCommand &a, const IntroGameViewportCommand &b) { return a.command_index < b.command_index; });
+
+ /* Delete all the consumed signs, from last ID to first ID. */
+ std::sort(signs_to_delete.begin(), signs_to_delete.end(), [](SignID a, SignID b) { return a > b; });
+ for (SignID sign_id : signs_to_delete) {
+ delete Sign::Get(sign_id);
+ }
+ }
SelectGameWindow(WindowDesc *desc) : Window(desc)
{
this->CreateNestedTree();
this->FinishInitNested(0);
this->OnInvalidateData();
+
+ this->ReadIntroGameViewportCommands();
+
+ this->cur_viewport_command_index = (size_t)-1;
+ this->cur_viewport_command_time = 0;
+ }
+
+ void OnRealtimeTick(uint delta_ms) override
+ {
+ /* Move the main game viewport according to intro viewport commands. */
+
+ if (intro_viewport_commands.empty()) return;
+
+ /* Determine whether to move to the next command or stay at current. */
+ bool changed_command = false;
+ if (this->cur_viewport_command_index >= intro_viewport_commands.size()) {
+ /* Reached last, rotate back to start of the list. */
+ this->cur_viewport_command_index = 0;
+ changed_command = true;
+ } else {
+ /* Check if current command has elapsed and switch to next. */
+ this->cur_viewport_command_time += delta_ms;
+ if (this->cur_viewport_command_time >= intro_viewport_commands[this->cur_viewport_command_index].delay) {
+ this->cur_viewport_command_index = (this->cur_viewport_command_index + 1) % intro_viewport_commands.size();
+ this->cur_viewport_command_time = 0;
+ changed_command = true;
+ }
+ }
+
+ IntroGameViewportCommand &vc = intro_viewport_commands[this->cur_viewport_command_index];
+ Window *mw = FindWindowByClass(WC_MAIN_WINDOW);
+ Viewport *vp = mw->viewport;
+
+ /* Early exit if the current command hasn't elapsed and isn't animated. */
+ if (!changed_command && !vc.pan_to_next && vc.vehicle == INVALID_VEHICLE) return;
+
+ /* Reset the zoom level. */
+ if (changed_command) FixTitleGameZoom(vc.zoom_adjust);
+
+ /* Calculate current command position (updates followed vehicle coordinates). */
+ Point pos = vc.PositionForViewport(vp);
+
+ /* Calculate panning (linear interpolation between current and next command position). */
+ if (vc.pan_to_next) {
+ size_t next_command_index = (this->cur_viewport_command_index + 1) % intro_viewport_commands.size();
+ IntroGameViewportCommand &nvc = intro_viewport_commands[next_command_index];
+ Point pos2 = nvc.PositionForViewport(vp);
+ const double t = this->cur_viewport_command_time / (double)vc.delay;
+ pos.x = pos.x + (int)(t * (pos2.x - pos.x));
+ pos.y = pos.y + (int)(t * (pos2.y - pos.y));
+ }
+
+ /* Update the viewport position. */
+ mw->viewport->dest_scrollpos_x = mw->viewport->scrollpos_x = pos.x;
+ mw->viewport->dest_scrollpos_y = mw->viewport->scrollpos_y = pos.y;
+ UpdateViewportPosition(mw);
+ mw->SetDirty(); // Required during panning, otherwise logo graphics disappears
+
+ /* If there is only one command, we just executed it and don't need to do any more */
+ if (intro_viewport_commands.size() == 1 && vc.vehicle == INVALID_VEHICLE) intro_viewport_commands.clear();
}
/**
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 79960d51b..944a28405 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -152,12 +152,24 @@ void ZoomInOrOutToCursorWindow(bool in, Window *w)
}
}
-void FixTitleGameZoom()
+void FixTitleGameZoom(int zoom_adjust)
{
if (_game_mode != GM_MENU) return;
Viewport *vp = FindWindowByClass(WC_MAIN_WINDOW)->viewport;
+
+ /* Adjust the zoom in/out.
+ * Can't simply add, since operator+ is not defined on the ZoomLevel type. */
vp->zoom = _gui_zoom;
+ while (zoom_adjust < 0 && vp->zoom != ZOOM_LVL_MIN) {
+ vp->zoom--;
+ zoom_adjust++;
+ }
+ while (zoom_adjust > 0 && vp->zoom != ZOOM_LVL_MAX) {
+ vp->zoom++;
+ zoom_adjust--;
+ }
+
vp->virtual_width = ScaleByZoom(vp->width, vp->zoom);
vp->virtual_height = ScaleByZoom(vp->height, vp->zoom);
}
diff --git a/src/viewport_func.h b/src/viewport_func.h
index 9461f3df5..58e1706eb 100644
--- a/src/viewport_func.h
+++ b/src/viewport_func.h
@@ -32,7 +32,7 @@ bool MarkAllViewportsDirty(int left, int top, int right, int bottom);
bool DoZoomInOutWindow(ZoomStateChange how, Window *w);
void ZoomInOrOutToCursorWindow(bool in, Window * w);
Point GetTileZoomCenterWindow(bool in, Window * w);
-void FixTitleGameZoom();
+void FixTitleGameZoom(int zoom_adjust = 0);
void HandleZoomMessage(Window *w, const Viewport *vp, byte widget_zoom_in, byte widget_zoom_out);
/**