2014-04-03 20:49:00 +02:00
|
|
|
/*****************************************************************************
|
|
|
|
* Copyright (c) 2014 Ted John
|
|
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
|
|
*
|
|
|
|
* This file is part of OpenRCT2.
|
|
|
|
*
|
|
|
|
* OpenRCT2 is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2014-10-06 18:36:58 +02:00
|
|
|
#include "../addresses.h"
|
2015-05-17 12:55:26 +02:00
|
|
|
#include "../config.h"
|
2014-10-06 18:36:58 +02:00
|
|
|
#include "../audio/audio.h"
|
|
|
|
#include "../localisation/date.h"
|
|
|
|
#include "../localisation/localisation.h"
|
|
|
|
#include "../scenario.h"
|
|
|
|
#include "../sprites.h"
|
|
|
|
#include "../interface/widget.h"
|
|
|
|
#include "../interface/window.h"
|
2015-06-01 17:02:09 +02:00
|
|
|
#include "../interface/themes.h"
|
2016-01-09 16:10:52 +01:00
|
|
|
#include "../title.h"
|
2015-10-30 15:18:29 +01:00
|
|
|
#include "../util/util.h"
|
2014-04-03 20:49:00 +02:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
#define INITIAL_NUM_UNLOCKED_SCENARIOS 5
|
|
|
|
|
|
|
|
enum {
|
|
|
|
LIST_ITEM_TYPE_HEADING,
|
|
|
|
LIST_ITEM_TYPE_SCENARIO,
|
|
|
|
LIST_ITEM_TYPE_END,
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
uint8 type;
|
|
|
|
union {
|
|
|
|
struct {
|
|
|
|
rct_string_id string_id;
|
|
|
|
} heading;
|
|
|
|
struct {
|
|
|
|
scenario_index_entry *scenario;
|
|
|
|
bool is_locked;
|
|
|
|
} scenario;
|
|
|
|
};
|
|
|
|
} sc_list_item;
|
|
|
|
|
|
|
|
static sc_list_item *_listItems = NULL;
|
|
|
|
|
2014-04-09 19:38:04 +02:00
|
|
|
enum {
|
|
|
|
WIDX_BACKGROUND,
|
|
|
|
WIDX_TITLEBAR,
|
|
|
|
WIDX_CLOSE,
|
|
|
|
WIDX_TABCONTENT,
|
|
|
|
WIDX_TAB1,
|
|
|
|
WIDX_TAB2,
|
|
|
|
WIDX_TAB3,
|
|
|
|
WIDX_TAB4,
|
|
|
|
WIDX_TAB5,
|
2015-12-26 10:32:44 +01:00
|
|
|
WIDX_TAB6,
|
|
|
|
WIDX_TAB7,
|
|
|
|
WIDX_TAB8,
|
2014-04-09 19:38:04 +02:00
|
|
|
WIDX_SCENARIOLIST
|
|
|
|
};
|
|
|
|
|
|
|
|
static rct_widget window_scenarioselect_widgets[] = {
|
2015-12-26 10:32:44 +01:00
|
|
|
{ WWT_FRAME, 0, 0, 733, 0, 333, -1, STR_NONE }, // panel / background
|
|
|
|
{ WWT_CAPTION, 0, 1, 732, 1, 14, STR_SELECT_SCENARIO, STR_WINDOW_TITLE_TIP }, // title bar
|
|
|
|
{ WWT_CLOSEBOX, 0, 721, 731, 2, 13, 824, STR_CLOSE_WINDOW_TIP }, // close x button
|
|
|
|
{ WWT_IMGBTN, 1, 0, 733, 50, 333, -1, STR_NONE }, // tab content panel
|
2014-04-03 20:49:00 +02:00
|
|
|
{ WWT_TAB, 1, 3, 93, 17, 50, 0x200015BC, STR_NONE }, // tab 1
|
|
|
|
{ WWT_TAB, 1, 94, 184, 17, 50, 0x200015BC, STR_NONE }, // tab 2
|
|
|
|
{ WWT_TAB, 1, 185, 275, 17, 50, 0x200015BC, STR_NONE }, // tab 3
|
|
|
|
{ WWT_TAB, 1, 276, 366, 17, 50, 0x200015BC, STR_NONE }, // tab 4
|
|
|
|
{ WWT_TAB, 1, 367, 457, 17, 50, 0x200015BC, STR_NONE }, // tab 5
|
2015-12-26 10:32:44 +01:00
|
|
|
{ WWT_TAB, 1, 458, 593, 17, 50, 0x200015BC, STR_NONE }, // tab 6
|
|
|
|
{ WWT_TAB, 1, 594, 684, 17, 50, 0x200015BC, STR_NONE }, // tab 7
|
|
|
|
{ WWT_TAB, 1, 685, 775, 17, 50, 0x200015BC, STR_NONE }, // tab 8
|
|
|
|
{ WWT_SCROLL, 1, 3, 555, 54, 329, 2, STR_NONE }, // level list
|
2014-04-03 20:49:00 +02:00
|
|
|
{ WIDGETS_END },
|
|
|
|
};
|
|
|
|
|
2016-01-01 23:43:57 +01:00
|
|
|
static void window_scenarioselect_init_tabs(rct_window *w);
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
static void window_scenarioselect_close(rct_window *w);
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_mouseup(rct_window *w, int widgetIndex);
|
2014-05-28 23:22:09 +02:00
|
|
|
static void window_scenarioselect_mousedown(int widgetIndex, rct_window*w, rct_widget* widget);
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_scrollgetsize(rct_window *w, int scrollIndex, int *width, int *height);
|
|
|
|
static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex, int x, int y);
|
|
|
|
static void window_scenarioselect_scrollmouseover(rct_window *w, int scrollIndex, int x, int y);
|
|
|
|
static void window_scenarioselect_invalidate(rct_window *w);
|
|
|
|
static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi);
|
|
|
|
static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int scrollIndex);
|
|
|
|
|
|
|
|
static rct_window_event_list window_scenarioselect_events = {
|
2015-12-31 19:26:25 +01:00
|
|
|
window_scenarioselect_close,
|
2014-04-09 19:38:04 +02:00
|
|
|
window_scenarioselect_mouseup,
|
2015-07-10 02:39:16 +02:00
|
|
|
NULL,
|
2014-04-09 19:38:04 +02:00
|
|
|
window_scenarioselect_mousedown,
|
2015-07-10 02:39:16 +02:00
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
2014-04-09 19:38:04 +02:00
|
|
|
window_scenarioselect_scrollgetsize,
|
|
|
|
window_scenarioselect_scrollmousedown,
|
2015-07-10 02:39:16 +02:00
|
|
|
NULL,
|
2014-04-09 19:38:04 +02:00
|
|
|
window_scenarioselect_scrollmouseover,
|
2015-07-10 02:39:16 +02:00
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
2014-04-09 19:38:04 +02:00
|
|
|
window_scenarioselect_invalidate,
|
|
|
|
window_scenarioselect_paint,
|
|
|
|
window_scenarioselect_scrollpaint
|
|
|
|
};
|
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
static void draw_category_heading(rct_window *w, rct_drawpixelinfo *dpi, int left, int right, int y, rct_string_id stringId);
|
|
|
|
static void initialise_list_items(rct_window *w);
|
|
|
|
static bool is_scenario_visible(rct_window *w, scenario_index_entry *scenario);
|
|
|
|
static bool is_locking_enabled(rct_window *w);
|
|
|
|
|
2014-04-03 20:49:00 +02:00
|
|
|
/**
|
2015-10-20 20:16:30 +02:00
|
|
|
*
|
2014-04-03 20:49:00 +02:00
|
|
|
* rct2: 0x006781B5
|
|
|
|
*/
|
2014-04-09 19:38:04 +02:00
|
|
|
void window_scenarioselect_open()
|
2014-04-03 20:49:00 +02:00
|
|
|
{
|
|
|
|
rct_window* window;
|
2015-12-31 19:26:25 +01:00
|
|
|
int windowWidth;
|
|
|
|
int windowHeight = 334;
|
2014-04-03 20:49:00 +02:00
|
|
|
|
2014-10-16 03:02:43 +02:00
|
|
|
if (window_bring_to_front_by_class(WC_SCENARIO_SELECT) != NULL)
|
2014-04-03 20:49:00 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Load scenario list
|
2014-04-09 15:43:27 +02:00
|
|
|
scenario_load_list();
|
2014-04-03 20:49:00 +02:00
|
|
|
|
2015-12-28 09:32:05 +01:00
|
|
|
// Shrink the window if we're showing scenarios by difficulty level.
|
2015-12-31 03:59:08 +01:00
|
|
|
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_DIFFICULTY) {
|
2015-12-31 19:26:25 +01:00
|
|
|
windowWidth = 610;
|
2015-12-31 03:59:08 +01:00
|
|
|
} else {
|
2015-12-31 19:26:25 +01:00
|
|
|
windowWidth = 733;
|
2015-12-31 03:59:08 +01:00
|
|
|
}
|
2015-12-28 09:32:05 +01:00
|
|
|
|
2015-02-14 19:26:59 +01:00
|
|
|
window = window_create_centred(
|
2015-12-31 19:26:25 +01:00
|
|
|
windowWidth,
|
|
|
|
windowHeight,
|
2015-07-10 02:39:16 +02:00
|
|
|
&window_scenarioselect_events,
|
2014-04-13 23:23:56 +02:00
|
|
|
WC_SCENARIO_SELECT,
|
2015-04-15 19:02:04 +02:00
|
|
|
WF_10
|
2014-04-09 15:43:27 +02:00
|
|
|
);
|
2014-04-09 19:38:04 +02:00
|
|
|
window->widgets = window_scenarioselect_widgets;
|
2015-12-26 10:22:50 +01:00
|
|
|
window->enabled_widgets = (1 << WIDX_CLOSE) | (1 << WIDX_TAB1) | (1 << WIDX_TAB2)
|
2015-12-26 10:32:44 +01:00
|
|
|
| (1 << WIDX_TAB3) | (1 << WIDX_TAB4) | (1 << WIDX_TAB5)
|
|
|
|
| (1 << WIDX_TAB6) | (1 << WIDX_TAB7) | (1 << WIDX_TAB8);
|
2014-04-03 20:49:00 +02:00
|
|
|
|
2016-01-01 23:43:57 +01:00
|
|
|
window_scenarioselect_init_tabs(window);
|
2015-12-31 19:26:25 +01:00
|
|
|
initialise_list_items(window);
|
|
|
|
|
|
|
|
window_init_scroll_widgets(window);
|
|
|
|
window->viewport_focus_coordinates.var_480 = -1;
|
2016-01-02 22:13:24 +01:00
|
|
|
window->highlighted_scenario = NULL;
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-10-20 20:16:30 +02:00
|
|
|
*
|
2014-04-09 19:38:04 +02:00
|
|
|
* rct2: 0x00677C8A
|
|
|
|
*/
|
2016-01-01 23:43:57 +01:00
|
|
|
static void window_scenarioselect_init_tabs(rct_window *w)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2016-01-01 23:43:57 +01:00
|
|
|
int showPages = 0;
|
2015-12-26 10:32:44 +01:00
|
|
|
for (int i = 0; i < gScenarioListCount; i++) {
|
2015-12-31 03:59:08 +01:00
|
|
|
scenario_index_entry *scenario = &gScenarioList[i];
|
2016-01-01 23:43:57 +01:00
|
|
|
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
|
|
|
|
showPages |= 1 << scenario->source_game;
|
|
|
|
} else {
|
2016-01-02 21:40:24 +01:00
|
|
|
int category = scenario->category;
|
|
|
|
if (category > SCENARIO_CATEGORY_OTHER) {
|
|
|
|
category = SCENARIO_CATEGORY_OTHER;
|
|
|
|
}
|
|
|
|
showPages |= 1 << category;
|
2015-12-28 09:15:34 +01:00
|
|
|
}
|
2016-01-01 23:43:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int firstPage = bitscanforward(showPages);
|
|
|
|
if (firstPage != -1) {
|
|
|
|
w->selected_tab = firstPage;
|
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-26 10:32:44 +01:00
|
|
|
int x = 3;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
2016-01-01 23:43:57 +01:00
|
|
|
rct_widget* widget = &w->widgets[i + 4];
|
|
|
|
if (!(showPages & (1 << i))) {
|
2015-12-28 09:15:34 +01:00
|
|
|
widget->type = WWT_EMPTY;
|
|
|
|
continue;
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
widget->type = WWT_TAB;
|
|
|
|
widget->left = x;
|
|
|
|
widget->right = x + 90;
|
|
|
|
x += 91;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
static void window_scenarioselect_close(rct_window *w)
|
|
|
|
{
|
|
|
|
SafeFree(_listItems);
|
|
|
|
}
|
|
|
|
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_mouseup(rct_window *w, int widgetIndex)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-31 19:26:25 +01:00
|
|
|
if (widgetIndex == WIDX_CLOSE) {
|
2014-04-09 19:38:04 +02:00
|
|
|
window_close(w);
|
2015-12-31 22:13:49 +01:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2014-05-28 23:22:09 +02:00
|
|
|
static void window_scenarioselect_mousedown(int widgetIndex, rct_window*w, rct_widget* widget)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-26 10:32:44 +01:00
|
|
|
if (widgetIndex >= WIDX_TAB1 && widgetIndex <= WIDX_TAB8) {
|
2014-05-14 15:58:14 +02:00
|
|
|
w->selected_tab = widgetIndex - 4;
|
2016-01-02 22:13:24 +01:00
|
|
|
w->highlighted_scenario = NULL;
|
2015-12-31 19:26:25 +01:00
|
|
|
initialise_list_items(w);
|
2014-04-09 19:38:04 +02:00
|
|
|
window_invalidate(w);
|
2015-03-31 03:21:30 +02:00
|
|
|
window_event_resize_call(w);
|
|
|
|
window_event_invalidate_call(w);
|
2014-04-09 19:38:04 +02:00
|
|
|
window_init_scroll_widgets(w);
|
|
|
|
window_invalidate(w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_scrollgetsize(rct_window *w, int scrollIndex, int *width, int *height)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-31 19:26:25 +01:00
|
|
|
int y = 0;
|
|
|
|
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
|
|
|
|
switch (listItem->type) {
|
|
|
|
case LIST_ITEM_TYPE_HEADING:
|
|
|
|
y += 18;
|
|
|
|
break;
|
|
|
|
case LIST_ITEM_TYPE_SCENARIO:
|
|
|
|
y += 24;
|
|
|
|
break;
|
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
*height = y;
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
2015-12-11 16:38:37 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x6780FE
|
|
|
|
*/
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_scrollmousedown(rct_window *w, int scrollIndex, int x, int y)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-31 19:26:25 +01:00
|
|
|
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
|
|
|
|
switch (listItem->type) {
|
|
|
|
case LIST_ITEM_TYPE_HEADING:
|
|
|
|
y -= 18;
|
2015-12-31 22:13:49 +01:00
|
|
|
break;
|
2015-12-31 19:26:25 +01:00
|
|
|
case LIST_ITEM_TYPE_SCENARIO:
|
2015-12-31 22:13:49 +01:00
|
|
|
y -= 24;
|
2015-12-31 19:26:25 +01:00
|
|
|
if (y < 0 && !listItem->scenario.is_locked) {
|
2015-12-31 22:13:49 +01:00
|
|
|
audio_play_sound_panned(SOUND_CLICK_1, w->width / 2 + w->x, 0, 0, 0);
|
2016-01-09 16:10:52 +01:00
|
|
|
if (!scenario_load_and_play_from_path(listItem->scenario.scenario->path)) {
|
|
|
|
title_load();
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
2015-12-31 22:13:49 +01:00
|
|
|
break;
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
if (y < 0) {
|
|
|
|
break;
|
2015-12-31 22:13:49 +01:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
2015-12-11 16:38:37 +01:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* rct2: 0x678162
|
|
|
|
*/
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_scrollmouseover(rct_window *w, int scrollIndex, int x, int y)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-31 03:59:08 +01:00
|
|
|
scenario_index_entry *selected = NULL;
|
2015-12-31 19:26:25 +01:00
|
|
|
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
|
|
|
|
switch (listItem->type) {
|
|
|
|
case LIST_ITEM_TYPE_HEADING:
|
|
|
|
y -= 18;
|
|
|
|
break;
|
|
|
|
case LIST_ITEM_TYPE_SCENARIO:
|
|
|
|
y -= 24;
|
|
|
|
if (y < 0 && !listItem->scenario.is_locked) {
|
|
|
|
selected = listItem->scenario.scenario;
|
|
|
|
}
|
2015-12-31 22:13:49 +01:00
|
|
|
break;
|
2015-12-28 12:38:14 +01:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
if (y < 0) {
|
2015-12-31 22:13:49 +01:00
|
|
|
break;
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
|
2016-01-02 22:13:24 +01:00
|
|
|
if (w->highlighted_scenario != selected) {
|
|
|
|
w->highlighted_scenario = selected;
|
2014-04-09 19:38:04 +02:00
|
|
|
window_invalidate(w);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_invalidate(rct_window *w)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-05-29 18:04:53 +02:00
|
|
|
colour_scheme_update(w);
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-26 10:22:50 +01:00
|
|
|
w->pressed_widgets &= ~( (1 << WIDX_CLOSE) | (1 << WIDX_TAB1) | (1 << WIDX_TAB2)
|
2015-12-26 10:32:44 +01:00
|
|
|
| (1 << WIDX_TAB3) | (1 << WIDX_TAB4) | (1 << WIDX_TAB5)
|
|
|
|
| (1 << WIDX_TAB6) | (1 << WIDX_TAB7) | (1 << WIDX_TAB8) );
|
2015-12-26 10:22:50 +01:00
|
|
|
|
2014-05-14 15:58:14 +02:00
|
|
|
w->pressed_widgets |= 1LL << (w->selected_tab + 4);
|
2015-12-31 19:26:25 +01:00
|
|
|
|
|
|
|
int windowWidth = w->width;
|
|
|
|
window_scenarioselect_widgets[WIDX_BACKGROUND].right = windowWidth - 1;
|
|
|
|
window_scenarioselect_widgets[WIDX_TITLEBAR].right = windowWidth - 2;
|
|
|
|
window_scenarioselect_widgets[WIDX_CLOSE].left = windowWidth - 13;
|
|
|
|
window_scenarioselect_widgets[WIDX_CLOSE].right = windowWidth - 3;
|
|
|
|
window_scenarioselect_widgets[WIDX_TABCONTENT].right = windowWidth - 1;
|
|
|
|
window_scenarioselect_widgets[WIDX_SCENARIOLIST].right = windowWidth - 179;
|
|
|
|
|
|
|
|
int windowHeight = w->height;
|
|
|
|
window_scenarioselect_widgets[WIDX_BACKGROUND].bottom = windowHeight - 1;
|
|
|
|
window_scenarioselect_widgets[WIDX_TABCONTENT].bottom = windowHeight - 1;
|
2015-12-31 22:13:49 +01:00
|
|
|
|
|
|
|
const int bottomMargin = gConfigGeneral.debugging_tools ? 17 : 5;
|
|
|
|
window_scenarioselect_widgets[WIDX_SCENARIOLIST].bottom = windowHeight - bottomMargin;
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_paint(rct_window *w, rct_drawpixelinfo *dpi)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-05-17 12:55:26 +02:00
|
|
|
int i, x, y, format;
|
2014-04-09 19:38:04 +02:00
|
|
|
rct_widget *widget;
|
2015-12-31 03:59:08 +01:00
|
|
|
scenario_index_entry *scenario;
|
2014-04-09 19:38:04 +02:00
|
|
|
|
|
|
|
window_draw_widgets(w, dpi);
|
2015-10-20 20:16:30 +02:00
|
|
|
|
2016-01-29 20:39:31 +01:00
|
|
|
format = (theme_get_flags() & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) ? 5138 : 1193;
|
2015-05-17 12:55:26 +02:00
|
|
|
|
2014-04-09 19:38:04 +02:00
|
|
|
// Text for each tab
|
2015-12-26 10:32:44 +01:00
|
|
|
for (i = 0; i < 8; i++) {
|
2014-04-09 19:38:04 +02:00
|
|
|
widget = &window_scenarioselect_widgets[WIDX_TAB1 + i];
|
|
|
|
if (widget->type == WWT_EMPTY)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
x = (widget->left + widget->right) / 2 + w->x;
|
|
|
|
y = (widget->top + widget->bottom) / 2 + w->y - 3;
|
2015-12-26 10:32:44 +01:00
|
|
|
|
2015-12-31 03:59:08 +01:00
|
|
|
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
|
2015-12-29 17:20:40 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = STR_SCENARIO_CATEGORY_RCT1 + i;
|
2015-12-26 10:32:44 +01:00
|
|
|
} else { // old-style
|
2016-01-02 21:40:24 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = ScenarioCategoryStringIds[i];
|
2015-12-26 10:32:44 +01:00
|
|
|
}
|
|
|
|
gfx_draw_string_centred_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 87, format, 10);
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return if no scenario highlighted
|
2016-01-02 22:13:24 +01:00
|
|
|
scenario = w->highlighted_scenario;
|
|
|
|
if (scenario == NULL) {
|
2014-04-09 19:38:04 +02:00
|
|
|
return;
|
2016-01-02 22:13:24 +01:00
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 22:13:49 +01:00
|
|
|
// Scenario path
|
|
|
|
if (gConfigGeneral.debugging_tools) {
|
2016-01-02 23:41:35 +01:00
|
|
|
utf8 path[MAX_PATH];
|
2016-02-01 02:35:14 +01:00
|
|
|
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_FONT_SPRITE_BASE, uint16) = 224;
|
2016-01-02 23:41:35 +01:00
|
|
|
shorten_path(path, sizeof(path), scenario->path, w->width - 6);
|
|
|
|
|
|
|
|
const utf8 *pathPtr = path;
|
|
|
|
gfx_draw_string_left(dpi, 1170, (void*)&pathPtr, w->colours[1], w->x + 3, w->y + w->height - 3 - 11);
|
2015-12-31 22:13:49 +01:00
|
|
|
}
|
|
|
|
|
2014-04-09 19:38:04 +02:00
|
|
|
// Scenario name
|
|
|
|
x = w->x + window_scenarioselect_widgets[WIDX_SCENARIOLIST].right + 4;
|
|
|
|
y = w->y + window_scenarioselect_widgets[WIDX_TABCONTENT].top + 5;
|
2016-01-18 20:49:52 +01:00
|
|
|
safe_strcpy((char*)0x009BC677, scenario->name, 64);
|
2015-12-26 10:32:44 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = 3165; // empty string
|
|
|
|
gfx_draw_string_centred_clipped(dpi, 1193, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, 0, x + 85, y, 170);
|
2014-04-09 19:38:04 +02:00
|
|
|
y += 15;
|
|
|
|
|
2014-04-10 04:23:12 +02:00
|
|
|
// Scenario details
|
2016-01-18 20:49:52 +01:00
|
|
|
safe_strcpy((char*)0x009BC677, scenario->details, 256);
|
2015-12-26 10:32:44 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = 3165; // empty string
|
|
|
|
y += gfx_draw_string_left_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 170, 1191, 0) + 5;
|
2014-04-09 19:38:04 +02:00
|
|
|
|
|
|
|
// Scenario objective
|
2015-12-26 10:32:44 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = scenario->objective_type + STR_OBJECTIVE_NONE;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, short) = scenario->objective_arg_3;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 4, short) = date_get_total_months(MONTH_OCTOBER, scenario->objective_arg_1);
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 6, int) = scenario->objective_arg_2;
|
|
|
|
y += gfx_draw_string_left_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 170, STR_OBJECTIVE, 0) + 5;
|
2014-04-09 19:38:04 +02:00
|
|
|
|
|
|
|
// Scenario score
|
2015-12-31 03:59:08 +01:00
|
|
|
if (scenario->highscore != NULL) {
|
2015-12-31 22:13:49 +01:00
|
|
|
const utf8 *completedByName = "???";
|
|
|
|
if (!str_is_null_or_empty(scenario->highscore->name)) {
|
|
|
|
completedByName = scenario->highscore->name;
|
|
|
|
}
|
2016-01-18 20:49:52 +01:00
|
|
|
safe_strcpy((char*)0x009BC677, completedByName, 64);
|
2015-12-26 10:32:44 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, short) = 3165; // empty string
|
2015-12-31 03:59:08 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, int) = scenario->highscore->company_value;
|
2015-12-26 10:32:44 +01:00
|
|
|
y += gfx_draw_string_left_wrapped(dpi, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS, x, y, 170, STR_COMPLETED_BY_WITH_COMPANY_VALUE, 0);
|
2014-04-10 04:23:12 +02:00
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
|
|
|
|
2015-07-10 02:39:16 +02:00
|
|
|
static void window_scenarioselect_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int scrollIndex)
|
2014-04-09 19:38:04 +02:00
|
|
|
{
|
2015-12-28 12:38:14 +01:00
|
|
|
int colour = ColourMapA[w->colours[1]].mid_light;
|
2014-04-09 19:38:04 +02:00
|
|
|
colour = (colour << 24) | (colour << 16) | (colour << 8) | colour;
|
|
|
|
gfx_clear(dpi, colour);
|
|
|
|
|
2016-01-29 20:39:31 +01:00
|
|
|
int highlighted_format = (theme_get_flags() & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) ? 5139 : 1193;
|
|
|
|
int unhighlighted_format = (theme_get_flags() & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT) ? 5139 : 1191;
|
2015-05-17 12:55:26 +02:00
|
|
|
|
2015-12-31 03:59:08 +01:00
|
|
|
bool wide = gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN;
|
2015-12-28 09:32:05 +01:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
rct_widget *listWidget = &w->widgets[WIDX_SCENARIOLIST];
|
|
|
|
int listWidth = listWidget->right - listWidget->left - 12;
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
int y = 0;
|
|
|
|
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
|
|
|
|
if (y > dpi->y + dpi->height) {
|
2014-04-09 19:38:04 +02:00
|
|
|
continue;
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
switch (listItem->type) {
|
|
|
|
case LIST_ITEM_TYPE_HEADING:;
|
|
|
|
const int horizontalRuleMargin = 4;
|
|
|
|
draw_category_heading(w, dpi, horizontalRuleMargin, listWidth - horizontalRuleMargin, y + 2, listItem->heading.string_id);
|
|
|
|
y += 18;
|
|
|
|
break;
|
|
|
|
case LIST_ITEM_TYPE_SCENARIO:;
|
2015-12-31 22:13:49 +01:00
|
|
|
// Draw hover highlight
|
2015-12-31 19:26:25 +01:00
|
|
|
scenario_index_entry *scenario = listItem->scenario.scenario;
|
2016-01-02 22:13:24 +01:00
|
|
|
bool isHighlighted = w->highlighted_scenario == scenario;
|
2015-12-31 19:26:25 +01:00
|
|
|
if (isHighlighted) {
|
2015-12-31 22:13:49 +01:00
|
|
|
gfx_fill_rect(dpi, 0, y, w->width, y + 23, 0x02000031);
|
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
bool isCompleted = scenario->highscore != NULL;
|
|
|
|
bool isDisabled = listItem->scenario.is_locked;
|
2015-12-28 12:38:14 +01:00
|
|
|
|
2015-12-31 22:13:49 +01:00
|
|
|
// Draw scenario name
|
|
|
|
rct_string_id placeholderStringId = 3165;
|
2016-01-18 20:49:52 +01:00
|
|
|
safe_strcpy((char*)language_get_string(placeholderStringId), scenario->name, 64);
|
2015-12-31 19:26:25 +01:00
|
|
|
int format = isDisabled ? 865 : (isHighlighted ? highlighted_format : unhighlighted_format);
|
|
|
|
colour = isDisabled ? w->colours[1] | 0x40 : COLOUR_BLACK;
|
2016-01-25 01:06:05 +01:00
|
|
|
if (isDisabled) RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_FONT_SPRITE_BASE, sint16) = -1;
|
2015-12-31 22:13:49 +01:00
|
|
|
gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 1, colour, &placeholderStringId);
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 22:13:49 +01:00
|
|
|
// Check if scenario is completed
|
2015-12-31 19:26:25 +01:00
|
|
|
if (isCompleted) {
|
2015-12-31 22:13:49 +01:00
|
|
|
// Draw completion tick
|
|
|
|
gfx_draw_sprite(dpi, 0x5A9F, wide ? 500 : 395, y + 1, 0);
|
|
|
|
|
|
|
|
// Draw completion score
|
|
|
|
const utf8 *completedByName = "???";
|
|
|
|
if (!str_is_null_or_empty(scenario->highscore->name)) {
|
|
|
|
completedByName = scenario->highscore->name;
|
|
|
|
}
|
2016-01-18 20:49:52 +01:00
|
|
|
safe_strcpy((char*)language_get_string(placeholderStringId), completedByName, 64);
|
2015-12-31 22:13:49 +01:00
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 0, rct_string_id) = 2793;
|
|
|
|
RCT2_GLOBAL(RCT2_ADDRESS_COMMON_FORMAT_ARGS + 2, rct_string_id) = placeholderStringId;
|
|
|
|
gfx_draw_string_centred(dpi, format, wide ? 270 : 210, y + 11, 0, (void*)RCT2_ADDRESS_COMMON_FORMAT_ARGS);
|
|
|
|
}
|
2014-04-09 19:38:04 +02:00
|
|
|
|
2015-12-31 22:13:49 +01:00
|
|
|
y += 24;
|
2015-12-31 19:26:25 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void draw_category_heading(rct_window *w, rct_drawpixelinfo *dpi, int left, int right, int y, rct_string_id stringId)
|
|
|
|
{
|
|
|
|
uint8 baseColour = w->colours[1];
|
|
|
|
uint8 lightColour = ColourMapA[baseColour].lighter;
|
|
|
|
uint8 darkColour = ColourMapA[baseColour].mid_dark;
|
|
|
|
|
|
|
|
// Draw string
|
|
|
|
int centreX = (left + right) / 2;
|
|
|
|
gfx_draw_string_centred(dpi, stringId, centreX, y, baseColour, NULL);
|
|
|
|
|
|
|
|
// Get string dimensions
|
|
|
|
utf8 *buffer = RCT2_ADDRESS(RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER, utf8);
|
|
|
|
format_string(buffer, stringId, NULL);
|
|
|
|
int categoryStringHalfWidth = (gfx_get_string_width(buffer) / 2) + 4;
|
|
|
|
int strLeft = centreX - categoryStringHalfWidth;
|
|
|
|
int strRight = centreX + categoryStringHalfWidth;
|
|
|
|
|
|
|
|
// Draw light horizontal rule
|
|
|
|
int lineY = y + 4;
|
|
|
|
gfx_draw_line(dpi, left, lineY, strLeft, lineY, lightColour);
|
|
|
|
gfx_draw_line(dpi, strRight, lineY, right, lineY, lightColour);
|
|
|
|
|
|
|
|
// Draw dark horizontal rule
|
|
|
|
lineY++;
|
|
|
|
gfx_draw_line(dpi, left, lineY, strLeft, lineY, darkColour);
|
|
|
|
gfx_draw_line(dpi, strRight, lineY, right, lineY, darkColour);
|
2015-12-31 22:13:49 +01:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
|
|
|
|
static void initialise_list_items(rct_window *w)
|
|
|
|
{
|
|
|
|
SafeFree(_listItems);
|
|
|
|
|
|
|
|
int capacity = gScenarioListCount + 16;
|
|
|
|
int length = 0;
|
|
|
|
_listItems = malloc(capacity * sizeof(sc_list_item));
|
|
|
|
|
2016-01-01 21:46:39 +01:00
|
|
|
// Mega park unlock
|
|
|
|
const uint32 rct1RequiredCompletedScenarios = (1 << SC_MEGA_PARK) - 1;
|
|
|
|
uint32 rct1CompletedScenarios = 0;
|
|
|
|
int megaParkListItemIndex = -1;
|
|
|
|
|
2015-12-31 19:26:25 +01:00
|
|
|
int numUnlocks = INITIAL_NUM_UNLOCKED_SCENARIOS;
|
|
|
|
uint8 currentHeading = UINT8_MAX;
|
|
|
|
for (int i = 0; i < gScenarioListCount; i++) {
|
|
|
|
scenario_index_entry *scenario = &gScenarioList[i];
|
|
|
|
if (!is_scenario_visible(w, scenario)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
sc_list_item *listItem;
|
|
|
|
|
|
|
|
// Category heading
|
|
|
|
rct_string_id headingStringId = STR_NONE;
|
|
|
|
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
|
2016-01-02 21:40:24 +01:00
|
|
|
if (w->selected_tab != SCENARIO_SOURCE_REAL && currentHeading != scenario->category) {
|
2015-12-31 19:26:25 +01:00
|
|
|
currentHeading = scenario->category;
|
2016-01-02 21:40:24 +01:00
|
|
|
headingStringId = ScenarioCategoryStringIds[currentHeading];
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
} else {
|
2016-01-02 21:40:24 +01:00
|
|
|
if (w->selected_tab <= SCENARIO_CATEGORY_EXPERT) {
|
|
|
|
if (currentHeading != scenario->source_game) {
|
|
|
|
currentHeading = scenario->source_game;
|
|
|
|
headingStringId = STR_SCENARIO_CATEGORY_RCT1 + currentHeading;
|
|
|
|
}
|
|
|
|
} else if (w->selected_tab == SCENARIO_CATEGORY_OTHER) {
|
|
|
|
int category = scenario->category;
|
|
|
|
if (category <= SCENARIO_CATEGORY_REAL) {
|
|
|
|
category = SCENARIO_CATEGORY_OTHER;
|
|
|
|
}
|
|
|
|
if (currentHeading != category) {
|
|
|
|
currentHeading = category;
|
|
|
|
headingStringId = ScenarioCategoryStringIds[category];
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-07 23:14:53 +01:00
|
|
|
if (headingStringId != STR_NONE) {
|
2015-12-31 19:26:25 +01:00
|
|
|
// Ensure list capacity
|
|
|
|
if (length == capacity) {
|
|
|
|
capacity += 32;
|
|
|
|
_listItems = realloc(_listItems, capacity * sizeof(sc_list_item));
|
|
|
|
}
|
|
|
|
listItem = &_listItems[length++];
|
|
|
|
|
|
|
|
listItem->type = LIST_ITEM_TYPE_HEADING;
|
|
|
|
listItem->heading.string_id = headingStringId;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure list capacity
|
|
|
|
if (length == capacity) {
|
|
|
|
capacity += 32;
|
|
|
|
_listItems = realloc(_listItems, capacity * sizeof(sc_list_item));
|
|
|
|
}
|
|
|
|
listItem = &_listItems[length++];
|
|
|
|
|
|
|
|
// Scenario
|
|
|
|
listItem->type = LIST_ITEM_TYPE_SCENARIO;
|
|
|
|
listItem->scenario.scenario = scenario;
|
|
|
|
if (is_locking_enabled(w)) {
|
|
|
|
listItem->scenario.is_locked = numUnlocks <= 0;
|
|
|
|
if (scenario->highscore == NULL) {
|
|
|
|
numUnlocks--;
|
2016-01-01 21:46:39 +01:00
|
|
|
} else {
|
|
|
|
// Mark RCT1 scenario as completed
|
|
|
|
if (scenario->sc_id < SC_MEGA_PARK) {
|
|
|
|
rct1CompletedScenarios |= 1 << scenario->sc_id;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If scenario is Mega Park, keep a reference to it
|
|
|
|
if (scenario->sc_id == SC_MEGA_PARK) {
|
|
|
|
megaParkListItemIndex = length - 1;
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
listItem->scenario.is_locked = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
length++;
|
|
|
|
_listItems = realloc(_listItems, length * sizeof(sc_list_item));
|
|
|
|
_listItems[length - 1].type = LIST_ITEM_TYPE_END;
|
2016-01-01 21:46:39 +01:00
|
|
|
|
|
|
|
// Mega park handling
|
|
|
|
if (megaParkListItemIndex != -1) {
|
|
|
|
bool megaParkLocked = (rct1CompletedScenarios & rct1RequiredCompletedScenarios) != rct1RequiredCompletedScenarios;
|
|
|
|
_listItems[megaParkListItemIndex].scenario.is_locked = megaParkLocked;
|
2016-01-03 22:06:10 +01:00
|
|
|
if (megaParkLocked && gConfigGeneral.scenario_hide_mega_park) {
|
2016-01-01 21:46:39 +01:00
|
|
|
// Remove mega park
|
|
|
|
int remainingItems = length - megaParkListItemIndex - 1;
|
|
|
|
memmove(&_listItems[megaParkListItemIndex], &_listItems[megaParkListItemIndex + 1], remainingItems);
|
|
|
|
|
|
|
|
// Remove empty headings
|
|
|
|
int i = 0;
|
|
|
|
for (sc_list_item *listItem = _listItems; listItem->type != LIST_ITEM_TYPE_END; listItem++) {
|
|
|
|
if (listItem->type == LIST_ITEM_TYPE_HEADING && (listItem + 1)->type != LIST_ITEM_TYPE_SCENARIO) {
|
|
|
|
remainingItems = length - i - 1;
|
|
|
|
memmove(&_listItems[i], &_listItems[i + 1], remainingItems);
|
|
|
|
listItem--;
|
|
|
|
} else {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_scenario_visible(rct_window *w, scenario_index_entry *scenario)
|
|
|
|
{
|
2016-01-02 21:40:24 +01:00
|
|
|
if (gConfigGeneral.scenario_select_mode == SCENARIO_SELECT_MODE_ORIGIN) {
|
|
|
|
if (scenario->source_game != w->selected_tab) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
int category = scenario->category;
|
|
|
|
if (category > SCENARIO_CATEGORY_OTHER) {
|
|
|
|
category = SCENARIO_CATEGORY_OTHER;
|
|
|
|
}
|
|
|
|
if (category != w->selected_tab) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_locking_enabled(rct_window *w)
|
|
|
|
{
|
|
|
|
if (gConfigGeneral.scenario_select_mode != SCENARIO_SELECT_MODE_ORIGIN) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!gConfigGeneral.scenario_unlocking_enabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (w->selected_tab >= 6) {
|
|
|
|
return false;
|
2014-04-09 19:38:04 +02:00
|
|
|
}
|
2015-12-31 19:26:25 +01:00
|
|
|
return true;
|
2014-04-21 11:27:48 +02:00
|
|
|
}
|