Feature: Multiple rotating views on title screen

This commit is contained in:
Niels Martin Hansen 2021-04-09 20:39:56 +02:00
parent 710b758b81
commit 6bd3106681
3 changed files with 210 additions and 2 deletions

View File

@ -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();
}
/**

View File

@ -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);
}

View File

@ -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);
/**