Add new libRocket UI system

This new system allows to create a user interface that goes beyond the
existing UI system.
This commit is contained in:
asarium committed 2018-09-16 13:55:29 +02:00
1 parent 138a1d01d4
commit 7173299ca7
74 files changed
+3157 -235

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ misc-*,\
-misc-macro-parentheses,\
-misc-misplaced-widening-cast,\
-misc-static-assert,\
-modernize-make-unique,\
"
WarningsAsErrors: ''
HeaderFilterRegex: 'code/.*$|freespace2/.*$|qtfred/.*$|test/src/.*$|build/.*$'
-3
View File
@@ -26,9 +26,6 @@
*.unsuccessfulbuild
ipch/
[Oo]bj/
[Bb]in
[Dd]ebug*/
[Rr]elease*/
Ankh.NoLoad
*.VC.db
*.VC.VC.opendb
+3
View File
@@ -1,3 +1,6 @@
[submodule "cmake/external/rpavlik-cmake-modules"]
path = cmake/external/rpavlik-cmake-modules
url = https://github.com/asarium/cmake-modules.git
[submodule "lib/libRocket"]
path = lib/libRocket
url = https://github.com/asarium/libRocket.git
+1
View File
@@ -16,6 +16,7 @@ addons:
- liblua5.1-0-dev
- libjansson-dev
- libsdl2-dev
- libfreetype6-dev
- valgrind
- qt57base
coverity_scan:
+2
View File
@@ -39,6 +39,8 @@ target_link_libraries(code PUBLIC md5)
target_link_libraries(code PUBLIC discord-rpc)
target_link_libraries(code PUBLIC libRocket)
enable_clang_tidy(code)
IF (FSO_USE_SPEECH)
+32
View File
@@ -89,6 +89,8 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
{ CF_TYPE_SCRIPTS, "data" DIR_SEPARATOR_STR "scripts", ".lua .lc", CF_TYPE_DATA },
{ CF_TYPE_FICTION, "data" DIR_SEPARATOR_STR "fiction", ".txt", CF_TYPE_DATA },
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA },
{ CF_TYPE_INTERFACE_MARKUP, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "markup", ".rml", CF_TYPE_INTERFACE },
{ CF_TYPE_INTERFACE_CSS, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "css", ".rcss", CF_TYPE_INTERFACE },
};
// clang-format on
@@ -1961,3 +1963,33 @@ int cflush(CFILE *cfile)
return result;
}
int cfile_get_path_type(const SCP_string& dir)
{
SCP_string buf = dir;
// Remove trailing slashes; avoid buffer overflow on 1-char strings
while (buf.size() > 0 && (buf[buf.size() - 1] == '\\' || buf[buf.size() - 1] == '/')) {
buf.resize(buf.size() - 1);
}
// Remove leading slashes
while (buf.size() > 0 && (buf[0] == '\\' || buf[0] == '/')) {
buf = buf.substr(1);
}
// Use official DIR_SEPARATOR_CHAR
for (char& c : buf) {
if (c == '\\' || c == '/') {
c = DIR_SEPARATOR_CHAR;
}
}
for (auto& Pathtype : Pathtypes) {
if (Pathtype.path != nullptr && buf == Pathtype.path) {
return Pathtype.index;
}
}
return CF_TYPE_INVALID;
}
+6 -2
View File
@@ -79,9 +79,11 @@ typedef struct {
#define CF_TYPE_SCRIPTS 35
#define CF_TYPE_FICTION 36
#define CF_TYPE_FREDDOCS 37
#define CF_TYPE_INTERFACE_MARKUP 38
#define CF_TYPE_INTERFACE_CSS 39
#define CF_MAX_PATH_TYPES 38 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
#define CF_MAX_PATH_TYPES \
40 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
// TRUE if type is specified and valid
#define CF_TYPE_SPECIFIED(path_type) (((path_type)>CF_TYPE_INVALID) && ((path_type)<CF_MAX_PATH_TYPES))
@@ -434,6 +436,8 @@ int cfile_push_chdir(int type);
// restore directory on top of the stack
int cfile_pop_dir();
int cfile_get_path_type(const SCP_string& dir);
namespace cfile
{
// exceptions and other errors
+20 -13
View File
@@ -44,21 +44,11 @@ struct PlaybackState {
int subtitle_font = -1;
};
void processEvents(PlaybackState* state) {
void processEvents()
{
io::mouse::CursorManager::get()->showCursor(false);
os_poll();
int k = key_inkey();
switch (k) {
case KEY_ESC:
case KEY_ENTER:
case KEY_SPACEBAR:
state->playing = false;
break;
default:
break;
}
}
template<typename... Args>
@@ -193,6 +183,21 @@ void movie_display_loop(Player* player, PlaybackState* state) {
// We will sleep at most half the time a frame would be displayed
auto sleepTime = static_cast<std::uint64_t>((1. / (4. * player->getMovieProperties().fps)) * 1000.);
auto listener = [state](const SDL_Event& event) {
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
case SDLK_KP_ENTER:
case SDLK_RETURN:
case SDLK_SPACE:
state->playing = false;
return true;
default:
return false;
}
};
// Use a dedicated listener here since we want to listen for the KEYUP event
auto key_handle = os::events::addEventListener(SDL_KEYUP, os::events::DEFAULT_LISTENER_WEIGHT - 10, listener);
auto lastDisplayTimestamp = timer_get_microseconds();
while (state->playing) {
TRACE_SCOPE(tracing::CutsceneStep);
@@ -209,7 +214,7 @@ void movie_display_loop(Player* player, PlaybackState* state) {
displayVideo(player, state);
}
processEvents(state);
processEvents();
if (passed < sleepTime) {
auto sleep = sleepTime - passed;
@@ -217,6 +222,8 @@ void movie_display_loop(Player* player, PlaybackState* state) {
os_sleep(static_cast<uint>(sleep));
}
}
os::events::removeEventListener(key_handle);
}
}
@@ -0,0 +1,17 @@
in vec2 fragTexCoord;
in vec4 fragColor;
out vec4 fragOut0;
uniform bool textured;
uniform sampler2DArray baseMap;
uniform int baseMapIndex;
void main() {
if (textured) {
fragOut0 = texture(baseMap, vec3(fragTexCoord, float(baseMapIndex))) * fragColor;
} else {
fragOut0 = fragColor;
}
}
@@ -0,0 +1,18 @@
in vec2 vertPosition;
in vec4 vertColor;
in vec2 vertTexCoord;
out vec2 fragTexCoord;
out vec4 fragColor;
uniform mat4 modelViewMatrix;
uniform mat4 projMatrix;
void main()
{
fragTexCoord = vertTexCoord;
fragColor = vertColor;
gl_Position = projMatrix * modelViewMatrix * vec4(vertPosition, 0.0, 1.0);
}
+21 -1
View File
@@ -7,8 +7,9 @@
*
*/
#include "globalincs/pstypes.h"
#include "globalincs/version.h"
#include "globalincs/pstypes.h"
#include "graphics/2d.h"
#include "parse/parselo.h"
@@ -130,5 +131,24 @@ bool version::operator!=(const version& other) const {
return !(*this == other);
}
SCP_string get_version_string()
{
SCP_string str;
sprintf(str, "FreeSpace 2 Open v%s", FS_VERSION_FULL);
#ifndef NDEBUG
str += " Debug";
#endif
// Lets get some more info in here
switch (gr_screen.mode) {
case GR_OPENGL:
str += " OpenGL";
break;
}
return str;
}
}
+7
View File
@@ -85,6 +85,13 @@ bool check_at_least(const version& v);
*/
SCP_string format_version(const version& v);
/**
* @brief
* @return
*/
SCP_string get_version_string();
}
#endif
+12 -1
View File
@@ -49,6 +49,7 @@ class movie_material;
class batched_bitmap_material;
class nanovg_material;
class decal_material;
class interface_material;
class transform_stack {
@@ -154,13 +155,15 @@ enum shader_type {
SDR_TYPE_DEFERRED_LIGHTING,
SDR_TYPE_DEFERRED_CLEAR,
SDR_TYPE_VIDEO_PROCESS,
SDR_TYPE_PASSTHROUGH_RENDER, //!< Shader for doing the old style fixed-function rendering. Only used internally, use SDR_TYPE_DEFAULT_MATERIAL.
SDR_TYPE_PASSTHROUGH_RENDER, //!< Shader for doing the old style fixed-function rendering. Only used internally, use
//!< SDR_TYPE_DEFAULT_MATERIAL.
SDR_TYPE_SHIELD_DECAL,
SDR_TYPE_BATCHED_BITMAP,
SDR_TYPE_DEFAULT_MATERIAL,
SDR_TYPE_NANOVG,
SDR_TYPE_DECAL,
SDR_TYPE_SCENE_FOG,
SDR_TYPE_ROCKET_UI,
NUM_SHADER_TYPES
};
@@ -780,6 +783,8 @@ typedef struct screen {
void (*gf_render_primitives_batched)(batched_bitmap_material* material_info, primitive_type prim_type, vertex_layout* layout, int offset, int n_verts, int buffer_handle);
void (*gf_render_nanovg)(nanovg_material* material_info, primitive_type prim_type, vertex_layout* layout, int offset, int n_verts, int buffer_handle);
void (*gf_render_decals)(decal_material* material_info, primitive_type prim_type, vertex_layout* layout, int num_elements, const indexed_vertex_source& buffers);
void (*gf_render_rocket_primitives)(interface_material* material_info, primitive_type prim_type,
vertex_layout* layout, int n_indices, int vertex_buffer, int index_buffer);
bool (*gf_is_capable)(gr_capability capability);
bool (*gf_get_property)(gr_property property, void* destination);
@@ -1039,6 +1044,12 @@ __inline void gr_render_model(model_material* material_info, indexed_vertex_sour
(*gr_screen.gf_render_model)(material_info, vert_source, bufferp, texi);
}
__inline void gr_render_rocket_primitives(interface_material* material_info, primitive_type prim_type,
vertex_layout* layout, int n_indices, int vertex_buffer, int index_buffer)
{
(*gr_screen.gf_render_rocket_primitives)(material_info, prim_type, layout, n_indices, vertex_buffer, index_buffer);
}
__inline bool gr_is_capable(gr_capability capability)
{
return (*gr_screen.gf_is_capable)(capability);
+7
View File
@@ -351,6 +351,12 @@ void gr_stub_render_primitives_batched(batched_bitmap_material* /*material_info
{
}
void gr_stub_render_rocket_primitives(interface_material* /*material_info*/, primitive_type /*prim_type*/,
vertex_layout* /*layout*/, int /*n_indices*/, int /*vertex_buffer*/,
int /*index_buffer*/)
{
}
bool gr_stub_is_capable(gr_capability /*capability*/)
{
return false;
@@ -511,6 +517,7 @@ bool gr_stub_init()
gr_screen.gf_render_movie = gr_stub_render_movie;
gr_screen.gf_render_nanovg = gr_stub_render_nanovg;
gr_screen.gf_render_primitives_batched = gr_stub_render_primitives_batched;
gr_screen.gf_render_rocket_primitives = gr_stub_render_rocket_primitives;
gr_screen.gf_is_capable = gr_stub_is_capable;
gr_screen.gf_get_property = [](gr_property,void*) { return false; };
+22
View File
@@ -123,6 +123,18 @@ void material_set_distortion(distortion_material *mat_info, int texture, bool th
mat_info->set_cull_mode(false);
mat_info->set_color(1.0f, 1.0f, 1.0f, 1.0f);
}
void material_set_rocket_interface(interface_material* mat_info, int texture, const vec2d& offset)
{
mat_info->set_texture_map(TM_BASE_TYPE, texture);
mat_info->set_offset(offset);
mat_info->set_cull_mode(false);
mat_info->set_color(1.0f, 1.0f, 1.0f, 1.0f);
mat_info->set_depth_mode(ZBUFFER_TYPE_NONE);
mat_info->set_blend_mode(ALPHA_BLEND_ALPHA_BLEND_ALPHA);
}
void material_set_movie(movie_material* mat_info, int y_bm, int u_bm, int v_bm) {
mat_info->set_depth_mode(ZBUFFER_TYPE_NONE);
@@ -890,3 +902,13 @@ uint decal_material::get_shader_flags() const {
return flags;
}
interface_material::interface_material()
{
set_shader_type(SDR_TYPE_ROCKET_UI);
offset.x = 0;
offset.y = 0;
}
void interface_material::set_offset(const vec2d& new_offset) { this->offset = new_offset; }
vec2d interface_material::get_offset() { return offset; }
+15 -6
View File
@@ -315,9 +315,8 @@ class movie_material : public material {
void setVtex(int _Vtex);
};
class batched_bitmap_material : public material
{
public:
class batched_bitmap_material : public material {
public:
batched_bitmap_material();
};
@@ -326,19 +325,29 @@ class nanovg_material : public material {
nanovg_material();
};
class decal_material : public material
{
public:
class decal_material : public material {
public:
decal_material();
uint get_shader_flags() const override;
};
class interface_material : public material {
vec2d offset;
public:
interface_material();
void set_offset(const vec2d& new_offset);
vec2d get_offset();
};
gr_alpha_blend material_determine_blend_mode(int base_bitmap, bool is_transparent);
gr_zbuffer_type material_determine_depth_mode(bool depth_testing, bool is_transparent);
void material_set_interface(material* mat_info, int texture, bool blended, float alpha);
void material_set_rocket_interface(interface_material* mat_info, int texture, const vec2d& offset);
void material_set_unlit(material* mat_info, int texture, float alpha, bool blending, bool depth_testing);
void material_set_unlit_emissive(material* mat_info, int texture, float alpha, float color_scale);
void material_set_unlit_color(material* mat_info, int texture, color *clr, bool blending, bool depth_testing);
+1
View File
@@ -1174,6 +1174,7 @@ void opengl_setup_function_pointers()
gr_screen.gf_render_movie = gr_opengl_render_movie;
gr_screen.gf_render_nanovg = gr_opengl_render_nanovg;
gr_screen.gf_render_decals = gr_opengl_render_decals;
gr_screen.gf_render_rocket_primitives = gr_opengl_render_rocket_primitives;
gr_screen.gf_is_capable = gr_opengl_is_capable;
gr_screen.gf_get_property = gr_opengl_get_property;
+16
View File
@@ -825,6 +825,22 @@ void gr_opengl_render_primitives_batched(batched_bitmap_material* material_info,
opengl_render_primitives(prim_type, layout, n_verts, buffer_handle, offset, 0);
}
void gr_opengl_render_rocket_primitives(interface_material* material_info, primitive_type prim_type,
vertex_layout* layout, int n_indices, int vertex_buffer, int index_buffer)
{
GR_DEBUG_SCOPE("Render rocket ui primitives");
gr_set_2d_matrix();
opengl_tnl_set_rocketui_material(material_info);
opengl_bind_vertex_layout(*layout, opengl_buffer_get_id(GL_ARRAY_BUFFER, vertex_buffer),
opengl_buffer_get_id(GL_ELEMENT_ARRAY_BUFFER, index_buffer));
glDrawElements(opengl_primitive_type(prim_type), n_indices, GL_UNSIGNED_INT, nullptr);
gr_end_2d_matrix();
}
void opengl_draw_textured_quad(GLfloat x1,
GLfloat y1,
+2
View File
@@ -58,6 +58,8 @@ void gr_opengl_render_primitives_distortion(distortion_material* material_info,
void gr_opengl_render_movie(movie_material* material_info, primitive_type prim_type, vertex_layout* layout, int n_verts, int buffer, size_t buffer_offset);
void gr_opengl_render_nanovg(nanovg_material* material_info, primitive_type prim_type, vertex_layout* layout, int offset, int n_verts, int buffer_handle);
void gr_opengl_render_decals(decal_material* material_info, primitive_type prim_type, vertex_layout* layout, int num_elements, const indexed_vertex_source& binding);
void gr_opengl_render_rocket_primitives(interface_material* material_info, primitive_type prim_type,
vertex_layout* layout, int n_indices, int vertex_buffer, int index_buffer);
void opengl_draw_textured_quad(GLfloat x1, GLfloat y1, GLfloat u1, GLfloat v1,
GLfloat x2, GLfloat y2, GLfloat u2, GLfloat v2 );
+6 -1
View File
@@ -64,6 +64,7 @@ opengl_uniform_block_binding GL_uniform_blocks[] = {
* Static lookup reference for shader uniforms
* When adding a new shader, list all associated uniforms and attributes here
*/
// clang-format off
static opengl_shader_type_t GL_shader_types[] = {
{ SDR_TYPE_MODEL, "main-v.sdr", "main-f.sdr", "main-g.sdr",
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::TEXCOORD, opengl_vert_attrib::NORMAL, opengl_vert_attrib::TANGENT, opengl_vert_attrib::MODEL_ID }, "Model Rendering" },
@@ -100,7 +101,7 @@ static opengl_shader_type_t GL_shader_types[] = {
{ SDR_TYPE_DEFERRED_LIGHTING, "deferred-v.sdr", "deferred-f.sdr", 0,
{ opengl_vert_attrib::POSITION }, "Deferred Lighting" },
{ SDR_TYPE_DEFERRED_CLEAR, "deferred-clear-v.sdr", "deferred-clear-f.sdr", 0,
{ opengl_vert_attrib::POSITION }, "Clear Deferred Lighting Buffer" },
@@ -127,7 +128,11 @@ static opengl_shader_type_t GL_shader_types[] = {
{ SDR_TYPE_SCENE_FOG, "post-v.sdr", "fog-f.sdr", nullptr,
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::TEXCOORD }, "Scene fogging" },
{ SDR_TYPE_ROCKET_UI, "rocketui-v.sdr", "rocketui-f.sdr", nullptr,
{ opengl_vert_attrib::POSITION, opengl_vert_attrib::COLOR, opengl_vert_attrib::TEXCOORD }, "libRocket UI" },
};
// clang-format on
/**
* Static lookup reference for shader variant uniforms
+29
View File
@@ -974,6 +974,7 @@ void opengl_tnl_set_material_nanovg(nanovg_material* material_info) {
Current_shader->program->Uniforms.setUniformi("nvg_tex", 0);
}
void opengl_tnl_set_material_decal(decal_material* material_info) {
opengl_tnl_set_material(material_info, false);
@@ -1016,6 +1017,34 @@ void opengl_tnl_set_material_decal(decal_material* material_info) {
}
}
void opengl_tnl_set_rocketui_material(interface_material* material_info)
{
vec3d offset = vmd_zero_vector;
offset.xyz.x = material_info->get_offset().x;
offset.xyz.y = material_info->get_offset().y;
matrix4 modelViewMatrix;
vm_matrix4_set_transform(&modelViewMatrix, &vmd_identity_matrix, &offset);
opengl_tnl_set_material(material_info, false);
Current_shader->program->Uniforms.setUniformMatrix4f("modelViewMatrix", modelViewMatrix);
Current_shader->program->Uniforms.setUniformMatrix4f("projMatrix", gr_projection_matrix);
Current_shader->program->Uniforms.setUniformi("textured", material_info->is_textured() ? GL_TRUE : GL_FALSE);
Current_shader->program->Uniforms.setUniformi("baseMap", 0);
if (material_info->is_textured()) {
float u_scale, v_scale;
uint32_t array_index;
if (!gr_opengl_tcache_set(material_info->get_texture_map(TM_BASE_TYPE), material_info->get_texture_type(),
&u_scale, &v_scale, &array_index)) {
mprintf(("WARNING: Error setting bitmap texture (%i)!\n", material_info->get_texture_map(TM_BASE_TYPE)));
}
Current_shader->program->Uniforms.setUniformi("baseMapIndex", array_index);
}
}
void gr_opengl_set_viewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
+1
View File
@@ -74,6 +74,7 @@ void opengl_tnl_set_material_movie(movie_material* material_info);
void opengl_tnl_set_material_batched(batched_bitmap_material * material_info);
void opengl_tnl_set_material_nanovg(nanovg_material * material_info);
void opengl_tnl_set_material_decal(decal_material * material_info);
void opengl_tnl_set_rocketui_material(interface_material* material_info);
void opengl_tnl_set_model_material(model_material *material_info);
+16 -9
View File
@@ -33,12 +33,6 @@
#include "ship/ship.h"
#include "ui/ui.h"
//Returns 0 on failure, 1 on success
int delete_pilot_file( char *pilot_name ); // manage_pilot.cpp
// stats defines
//#define NUM_STAT_LINES (21 + MAX_SHIP_CLASSES) // Goober5000
#define STAT_COLUMN1_W 40
@@ -539,7 +533,7 @@ int barracks_new_pilot_selected()
Cur_pilot->callsign[0] = 0; // this indicates no pilot active
return -1;
} else {
if (!Pilot.load_savefile(Cur_pilot->current_campaign)) {
if (!Pilot.load_savefile(Cur_pilot, Cur_pilot->current_campaign)) {
// set single player squad image to multi if campaign can't be loaded
strcpy_s(Cur_pilot->s_squad_filename, Cur_pilot->m_squad_filename);
}
@@ -667,6 +661,20 @@ int barracks_pilot_accepted()
return 0;
}
void barracks_accept_pilot(player* plr) {
// set pilot image
if (Game_mode & GM_MULTIPLAYER) {
player_set_squad_bitmap(plr, plr->m_squad_filename, true);
} else {
player_set_squad_bitmap(plr, plr->s_squad_filename, false);
}
// MWA -- I think that we should be writing plr here.
Pilot.save_player(plr);
os_config_write_string(nullptr, "LastPlayer", plr->callsign);
}
// scroll up barracks pilot list one line
void barracks_scroll_callsign_up()
{
@@ -831,7 +839,6 @@ void barracks_delete_pilot()
{
char buf[MAX_FILENAME_LEN];
int active = 0;
int del_rval;
if (!Num_pilots) {
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
@@ -857,7 +864,7 @@ void barracks_delete_pilot()
strcpy_s(buf, Pilots[Selected_line]);
del_rval = delete_pilot_file(buf);
auto del_rval = delete_pilot_file(buf);
if ( !del_rval ) {
popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_BIG | PF_TITLE_RED, 1, POPUP_OK, XSTR("Error\nFailed to delete pilot file. File may be read-only.", 1599));
+4
View File
@@ -12,6 +12,8 @@
#ifndef _BARRACKS_H
#define _BARRACKS_H
#include "playerman/player.h"
extern int Barracks_overlay_id;
// initialize the barracks
@@ -23,4 +25,6 @@ void barracks_do_frame(float frametime);
// close the barracks
void barracks_close();
void barracks_accept_pilot(player* plr);
#endif // _BARRACKS_H
+8 -6
View File
@@ -10,15 +10,16 @@
#include <cstdlib>
#include <climits>
#include "freespace.h"
#include "anim/animplay.h"
#include "anim/packunpack.h"
#include "cmdline/cmdline.h"
#include "freespace.h"
#include "gamehelp/contexthelp.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/eventmusic.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/version.h"
#include "graphics/generic.h"
#include "io/key.h"
#include "io/mouse.h"
@@ -33,9 +34,9 @@
#include "network/multiui.h"
#include "network/multiutil.h"
#include "parse/parselo.h"
#include "scripting/scripting.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "scripting/scripting.h"
#include "sound/audiostr.h"
#ifndef NDEBUG
@@ -410,6 +411,8 @@ void main_hall_init(const SCP_string &main_hall_name)
return;
}
// gameseq_post_event(GS_EVENT_SCRIPTING);
int idx;
SCP_string main_hall_to_load;
@@ -1666,20 +1669,19 @@ void main_hall_reset_ambient_vol()
void main_hall_blit_version()
{
int w, h;
char version_string[100];
// format the version string
get_version_string(version_string, sizeof(version_string));
auto version_string = gameversion::get_version_string();
int old_font = font::get_current_fontnum();
font::set_font(Main_hall->font);
// get the length of the string
gr_get_string_size(&w,&h,version_string);
gr_get_string_size(&w, &h, version_string.c_str());
// print the string near the lower left corner
gr_set_color_fast(&Color_bright_white);
gr_string(5, gr_screen.max_h_unscaled_zoomed - (h * 2 + 6), version_string, GR_RESIZE_MENU_ZOOMED);
gr_string(5, gr_screen.max_h_unscaled_zoomed - (h * 2 + 6), version_string.c_str(), GR_RESIZE_MENU_ZOOMED);
font::set_font(old_font);
}
+141 -26
View File
@@ -11,14 +11,14 @@
#include <climits>
#include <cctype>
#include "freespace.h"
#include "cfile/cfile.h"
#include "cmdline/cmdline.h"
#include "debugconsole/console.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/version.h"
#include "io/key.h"
#include "localization/localize.h"
#include "menuui/mainhallmenu.h"
@@ -34,6 +34,7 @@
#include "popup/popup.h"
#include "ui/ui.h"
#include "playermenu.h"
#include "graphics/paths/PathRenderer.h"
// --------------------------------------------------------------------------------------------------------
@@ -203,15 +204,13 @@ void player_select_eval_very_first_pilot();
void player_select_commit();
void player_select_cancel_create();
extern int delete_pilot_file(char *pilot_name);
/*
* validate that a pilot/player was created with the same language FSO is currently using
*
* @param pilots callsign
* @note not longer needed if intel entry "primary keys" change to a non-translated value
*/
bool valid_pilot_lang(char *callsign)
bool valid_pilot_lang(const char *callsign)
{
char pilot_lang[LCL_LANG_NAME_LEN+1], current_lang[LCL_LANG_NAME_LEN+1];
SCP_string filename = callsign;
@@ -476,7 +475,7 @@ void player_select_close()
}
// if(Player_select_palette >= 0){
// bm_release(Player_select_palette);
//Player_select_palette = -1;
//Player_select_palette = -1;GS_EVENT_MAIN_MENU
// }
// setup the player struct
@@ -502,9 +501,11 @@ void player_select_close()
Player = NULL;
} else {
// NOTE: this may fail if there is no current campaign, it's not fatal
Pilot.load_savefile(Player->current_campaign);
Pilot.load_savefile(Player, Player->current_campaign);
}
os_config_write_string(nullptr, "LastPlayer", Player->callsign);
if (Player_select_force_main_hall != "") {
main_hall_init(Player_select_force_main_hall);
}
@@ -750,14 +751,14 @@ int player_select_create_new_pilot()
void player_select_delete_pilot()
{
char filename[MAX_PATH_LEN + 1];
int i, del_rval;
int i;
// tack on the full path and the pilot file extension
// build up the path name length
// make sure we do this based upon whether we're in single or multiplayer mode
strcpy_s( filename, Pilots[Player_select_pilot] );
del_rval = delete_pilot_file(filename);
auto del_rval = delete_pilot_file(filename);
if ( !del_rval ) {
popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_BIG | PF_TITLE_RED, 1, POPUP_OK, XSTR("Error\nFailed to delete pilot file. File may be read-only.", 1599));
@@ -816,8 +817,6 @@ void player_select_scroll_list_down()
// fill in the data on the last played pilot (callsign and is_multi or not)
int player_select_get_last_pilot_info()
{
// TODO: Replace this with a function that does this properly for the new pilot code.
const char *last_player = os_config_read_string( NULL, "LastPlayer", NULL);
if (last_player == NULL) {
@@ -850,11 +849,15 @@ int player_select_get_last_pilot()
return 0;
}
Get_file_list_filter = player_select_pilot_file_filter;
Player_select_num_pilots = cf_get_file_list_preallocated(
MAX_PILOTS, Pilots_arr, Pilots, CF_TYPE_PLAYERS, NOX("*.plr"), CF_SORT_TIME, nullptr,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
auto pilots = player_select_enumerate_pilots();
// Copy the enumerated pilots into the appropriate local variables
Player_select_num_pilots = static_cast<int>(pilots.size());
for (auto i = 0; i < MAX_PILOTS; ++i) {
if (i < static_cast<int>(pilots.size())) {
strcpy_s(Pilots_arr[i], pilots[i].c_str());
}
Pilots[i] = Pilots_arr[i];
}
Player_select_pilot = -1;
idx = 0;
@@ -889,12 +892,15 @@ void player_select_init_player_stuff(int mode)
// set the select mode to single player for default
Player_select_mode = mode;
// load up the list of players based upon the Player_select_mode (single or multiplayer)
Get_file_list_filter = player_select_pilot_file_filter;
Player_select_num_pilots =
cf_get_file_list_preallocated(MAX_PILOTS, Pilots_arr, Pilots, CF_TYPE_PLAYERS, NOX("*.plr"), CF_SORT_TIME,
nullptr, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
auto pilots = player_select_enumerate_pilots();
// Copy the enumerated pilots into the appropriate local variables
Player_select_num_pilots = static_cast<int>(pilots.size());
for (auto i = 0; i < MAX_PILOTS; ++i) {
if (i < static_cast<int>(pilots.size())) {
strcpy_s(Pilots_arr[i], pilots[i].c_str());
}
Pilots[i] = Pilots_arr[i];
}
// if we have a "last_player", and they're in the list, bash them to the top of the list
if (Player_select_last_pilot[0] != '\0') {
@@ -1126,13 +1132,13 @@ void player_select_process_input(int k)
void player_select_display_copyright()
{
int sx, sy, w;
char Copyright_msg1[256], Copyright_msg2[256];
char Copyright_msg2[256];
// strcpy_s(Copyright_msg1, XSTR("Descent: FreeSpace - The Great War, Copyright c 1998, Volition, Inc.", -1));
gr_set_color_fast(&Color_white);
// sprintf(Copyright_msg1, NOX("FreeSpace 2"));
get_version_string(Copyright_msg1, sizeof(Copyright_msg1));
auto Copyright_msg1 = gameversion::get_version_string();
if (Unicode_text_mode) {
// Use a Unicode character if we are in unicode mode instead of using special characters
strcpy_s(Copyright_msg2, XSTR("Copyright \xC2\xA9 1999, Volition, Inc. All rights reserved.", 385));
@@ -1140,10 +1146,10 @@ void player_select_display_copyright()
sprintf(Copyright_msg2, XSTR("Copyright %c 1999, Volition, Inc. All rights reserved.", 385), lcl_get_font_index(font::get_current_fontnum()) + 4);
}
gr_get_string_size(&w, NULL, Copyright_msg1);
gr_get_string_size(&w, nullptr, Copyright_msg1.c_str());
sx = (int)std::lround((gr_screen.max_w_unscaled / 2) - w/2.0f);
sy = (gr_screen.max_h_unscaled - 2) - 2*gr_get_font_height();
gr_string(sx, sy, Copyright_msg1, GR_RESIZE_MENU);
gr_string(sx, sy, Copyright_msg1.c_str(), GR_RESIZE_MENU);
gr_get_string_size(&w, NULL, Copyright_msg2);
sx = (int)std::lround((gr_screen.max_w_unscaled / 2) - w/2.0f);
@@ -1384,3 +1390,112 @@ void player_tips_popup()
}
} while(ret > 0);
}
SCP_vector<SCP_string> player_select_enumerate_pilots() {
// load up the list of players based upon the Player_select_mode (single or multiplayer)
Get_file_list_filter = player_select_pilot_file_filter;
SCP_vector<SCP_string> pilots;
cf_get_file_list(pilots, CF_TYPE_PLAYERS, "*.plr", CF_SORT_TIME, nullptr, CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
return pilots;
}
SCP_string player_get_last_player()
{
const char* last_player = os_config_read_string(nullptr, "LastPlayer", nullptr);
if (last_player == nullptr) {
// No last player stored
return SCP_string();
}
return SCP_string(last_player);
}
void player_finish_select(const char* callsign, bool is_multi) {
Player_num = 0;
Player = &Players[0];
Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
// New pilot file makes no distinction between multi pilots and regular ones, so let's do this here.
if (is_multi) {
Player->flags |= PLAYER_FLAGS_IS_MULTI;
}
// WMC - Set appropriate game mode
if ( Player->flags & PLAYER_FLAGS_IS_MULTI ) {
Game_mode = GM_MULTIPLAYER;
} else {
Game_mode = GM_NORMAL;
}
// now read in a the pilot data
if ( !Pilot.load_player(callsign, Player) ) {
Error(LOCATION,"Couldn't load pilot file, bailing");
Player = nullptr;
} else {
// NOTE: this may fail if there is no current campaign, it's not fatal
Pilot.load_savefile(Player, Player->current_campaign);
}
os_config_write_string(nullptr, "LastPlayer", Player->callsign);
gameseq_post_event(GS_EVENT_MAIN_MENU);
}
bool player_create_new_pilot(const char* callsign, bool is_multi, const char* copy_from_callsign) {
SCP_string buf = callsign;
drop_white_space(buf);
auto invalid_name = false;
if (!isalpha(buf[0])) {
invalid_name = true;
} else {
for (auto idx=1; buf[idx]; idx++) {
if (!isalpha(buf[idx]) && !isdigit(buf[idx]) && !strchr(VALID_PILOT_CHARS, buf[idx])) {
invalid_name = true;
break;
}
}
}
if (buf.empty()) {
invalid_name = true;
}
if (invalid_name) {
return false;
}
// Create the new pilot, and write out his file
// if this is the first guy, we should set the Player struct
if (Player == nullptr) {
Player = &Players[0];
Player->reset();
Player->flags |= PLAYER_FLAGS_STRUCTURE_IN_USE;
}
if (copy_from_callsign != nullptr) {
// attempt to read in the pilot file of the guy to be cloned
if (!Pilot.load_player(copy_from_callsign, Player)) {
Error(LOCATION, "Couldn't load pilot file, bailing");
return false;
}
}
strcpy_s(Player->callsign, buf.c_str());
init_new_pilot(Player, copy_from_callsign == nullptr);
// set him as being a multiplayer pilot if we're in the correct mode
if (is_multi) {
Player->flags |= PLAYER_FLAGS_IS_MULTI;
Player->stats.flags |= STATS_FLAG_MULTIPLAYER;
}
// create his pilot file
Pilot.save_player(Player);
// unset the player
Player->reset();
Player = nullptr;
return true;
}
+11 -1
View File
@@ -12,6 +12,8 @@
#ifndef _PLAYER_SELECT_MENU_HEADER_FILE
#define _PLAYER_SELECT_MENU_HEADER_FILE
#include "globalincs/pstypes.h"
// general defines
#define PLAYER_SELECT_MODE_SINGLE 0 // looking through single player pilots
#define PLAYER_SELECT_MODE_MULTI 1 // looking through multi player pilots
@@ -39,6 +41,14 @@ void player_tips_close();
extern int Player_select_screen_active;
// check the pilots language
bool valid_pilot_lang(char *callsign);
bool valid_pilot_lang(const char *callsign);
SCP_vector<SCP_string> player_select_enumerate_pilots();
SCP_string player_get_last_player();
void player_finish_select(const char* callsign, bool is_multi);
bool player_create_new_pilot(const char* callsign, bool is_multi, const char* copy_from_callsign = nullptr);
#endif
+1 -18
View File
@@ -676,7 +676,7 @@ int mission_campaign_load( char *filename, player *pl, int load_savefile, bool r
if (!Fred_running && load_savefile && (Campaign.type == CAMPAIGN_TYPE_SINGLE)) {
// savefile can fail to load for numerous otherwise non-fatal reasons
// if it doesn't load in that case then it will be (re)created at save
if ( !Pilot.load_savefile(Campaign.filename) ) {
if ( !Pilot.load_savefile(pl, Campaign.filename) ) {
// but if the data is invalid for the savefile then it is fatal
if ( Pilot.is_invalid() ) {
Campaign.filename[0] = 0;
@@ -890,23 +890,6 @@ void mission_campaign_delete_all_savefiles(char *pilot_name)
}
}
/**
* The following code only ever called by CSFE!!!!
*/
void campaign_savefile_load(char *fname, char *pname)
{
if (Campaign.type == CAMPAIGN_TYPE_SINGLE) {
Game_mode &= ~GM_MULTIPLAYER;
Game_mode &= GM_NORMAL;
}
else
Game_mode |= GM_MULTIPLAYER;
strcpy_s(Player->callsign, pname);
Pilot.load_savefile(fname);
}
/**
* Sets up the internal veriables of the campaign structure so the player can play the next mission.
* If there are no more missions available in the campaign, this function returns -1, else 0 if the mission was
-2
View File
@@ -232,8 +232,6 @@ int mission_campaign_savefile_save();
int campaign_savefile_save(char *pname);
// Deletes the campaign save camp under the player name pname
void campaign_delete_save( char *cfn, char *pname);
// Loads campaign camp from fname under player name pname
void campaign_savefile_load(char *fname, char *pname);
// get name and type of specified campaign file
int mission_campaign_get_info(const char *filename, char *name, int *type, int *max_players, char **desc = NULL);
+2 -6
View File
@@ -364,11 +364,7 @@ namespace os
filename = clean_filename(filename);
// output to the debug log before anything else (so that we have a complete record)
SCP_string printfString = text;
std::transform(printfString.begin(), printfString.end(), printfString.begin(), replaceNewline);
mprintf(("WARNING: \"%s\" at %s:%d\n", printfString.c_str(), filename, line));
mprintf(("WARNING: \"%s\" at %s:%d\n", text.c_str(), filename, line));
// now go for the additional popup window, if we want it ...
if (Cmdline_noninteractive) {
@@ -376,7 +372,7 @@ namespace os
}
if (running_unittests) {
throw WarningException(printfString);
throw WarningException(text);
}
SCP_stringstream boxMsgStream;
+2 -2
View File
@@ -1343,7 +1343,7 @@ void pilotfile::csg_close()
m_have_info = false;
}
bool pilotfile::load_savefile(const char *campaign)
bool pilotfile::load_savefile(player *_p, const char *campaign)
{
char base[_MAX_FNAME] = { '\0' };
std::ostringstream buf;
@@ -1358,7 +1358,7 @@ bool pilotfile::load_savefile(const char *campaign)
// set player ptr first thing
Assert( (Player_num >= 0) && (Player_num < MAX_PLAYERS) );
p = &Players[Player_num];
p = _p;
// build up filename for the savefile...
_splitpath((char*)campaign, NULL, NULL, base, NULL);
+1 -1
View File
@@ -38,7 +38,7 @@ class pilotfile {
~pilotfile();
bool load_player(const char *callsign, player *_p = NULL);
bool load_savefile(const char *campaign);
bool load_savefile(player *_p, const char *campaign);
bool save_player(player *_p = NULL);
bool save_savefile();
-3
View File
@@ -943,9 +943,6 @@ bool pilotfile::load_player(const char *callsign, player *_p)
hud_squadmsg_save_keys();
// set last pilot
os_config_write_string(NULL, "LastPlayer", (char*)callsign);
mprintf(("PLR => Loading complete!\n"));
// cleanup and return
+3 -3
View File
@@ -51,7 +51,7 @@ int Num_pilot_squad_images = 0;
// internal function to delete a player file. Called after a pilot is obsoleted, and when a pilot is deleted
// used in barracks and player_select
//returns 0 on failure, 1 on success
int delete_pilot_file(char *pilot_name)
bool delete_pilot_file(const char *pilot_name)
{
int delreturn;
char filename[MAX_FILENAME_LEN];
@@ -69,9 +69,9 @@ int delete_pilot_file(char *pilot_name)
// we must try and delete the campaign save files for a pilot as well.
if (delreturn) {
mission_campaign_delete_all_savefiles(basename);
return 1;
return true;
} else {
return 0;
return false;
}
}
+4 -2
View File
@@ -16,8 +16,8 @@ class player;
#define VALID_PILOT_CHARS " _-"
#define MAX_PILOTS 20
#define MAX_PILOT_IMAGES 64
const int MAX_PILOTS = 20;
const int MAX_PILOT_IMAGES = 64;
// pilot pic image list stuff ( call pilot_load_pic_list() to make these valid )
extern char Pilot_images_arr[MAX_PILOT_IMAGES][MAX_FILENAME_LEN];
@@ -55,4 +55,6 @@ int pilot_verify_overwrite();
void pilot_set_start_campaign(player* p);
bool delete_pilot_file(const char *pilot_name);
#endif // _MANAGEPILOT_H
+117
View File
@@ -0,0 +1,117 @@
//
//
#include "RocketFileInterface.h"
#include "cfile/cfile.h"
#include "mod_table/mod_table.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/Log.h>
#pragma pop_macro("Assert")
using namespace Rocket::Core;
namespace scpui {
FileHandle RocketFileInterface::Open(const String& path)
{
// In order to use libRocket correctly we need to make sure that we only have Unicode data. This is done here since
// a mod that uses libRocket will always open a file through this function but a mod that doesn't will not call this
if (!Unicode_text_mode) {
Error(LOCATION,
"libRocket was used without Unicode text mode being enabled! The new UI system requires Unicode text.");
}
int dir_type;
SCP_string name;
if (!getCFilePath(path, name, dir_type)) {
return 0;
}
return (FileHandle)cfopen(name.c_str(), "rb", CFILE_NORMAL, dir_type);
}
void RocketFileInterface::Close(FileHandle file) { cfclose((CFILE*)file); }
size_t RocketFileInterface::Read(void* buffer, size_t size, FileHandle file)
{
auto fp = (CFILE*)file;
auto read = cfread(buffer, 1, (int)size, fp);
return (size_t)read;
}
bool RocketFileInterface::Seek(FileHandle file, long offset, int origin)
{
auto fp = (CFILE*)file;
int cf_seek_mode;
switch (origin) {
case SEEK_SET:
cf_seek_mode = CF_SEEK_SET;
break;
case SEEK_CUR:
cf_seek_mode = CF_SEEK_CUR;
break;
case SEEK_END:
cf_seek_mode = CF_SEEK_END;
break;
default:
Assertion(false, "Invalid seek mode encountered!");
return false;
}
return cfseek(fp, (int)offset, cf_seek_mode) == 0;
}
size_t RocketFileInterface::Tell(FileHandle file) { return (size_t)cftell((CFILE*)file); }
size_t RocketFileInterface::Length(FileHandle file) { return (size_t)cfilelength((CFILE*)file); }
bool RocketFileInterface::getCFilePath(const String& path_in, SCP_string& name, int& dir_type)
{
String path = path_in;
if (path.Find("\\") != String::npos) {
// We don't allow these in our paths
Log::Message(
Log::LT_WARNING,
"The path '%s' contains the invalid character '\\'. This is not allowed! You should use '/' instead.",
path.CString());
path.Replace("\\", "/");
}
if (path.Length() > 0 && path[path.Length() - 1] == '/') {
// Trailing slashes are not allowed!
Log::Message(Log::LT_WARNING,
"The path '%s' has trailing slashes. This is not allowed! The trailing slashes have been removed.",
path.CString());
while (path.Length() > 0 && path[path.Length() - 1] == '/') {
path = path.Substring(0, path.Length() - 1);
}
}
auto slashPos = path.RFind("/");
if (slashPos == String::npos) {
// No path specified, search everywhere
dir_type = CF_TYPE_ANY;
name = path.CString();
return true;
}
auto dir_path = path.Substring(0, slashPos);
auto type = cfile_get_path_type(dir_path.CString());
if (type == CF_TYPE_INVALID) {
return false;
}
dir_type = type;
name = path.Substring(slashPos + 1).CString();
return true;
}
} // namespace scpui
+43
View File
@@ -0,0 +1,43 @@
#pragma once
//
//
#include "globalincs/pstypes.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/FileInterface.h>
#pragma pop_macro("Assert")
namespace scpui {
class RocketFileInterface : public Rocket::Core::FileInterface {
public:
/**
* @brief Opens the specified file using the CFile system
*
* @details The path is split into a directory path and a file name. If the directory path is not valid then the
* file will not be opened.
*
* @param path
* @return
*/
Rocket::Core::FileHandle Open(const Rocket::Core::String& path) override;
void Close(Rocket::Core::FileHandle file) override;
size_t Read(void* buffer, size_t size, Rocket::Core::FileHandle file) override;
bool Seek(Rocket::Core::FileHandle file, long offset, int origin) override;
size_t Tell(Rocket::Core::FileHandle file) override;
size_t Length(Rocket::Core::FileHandle file) override;
static bool getCFilePath(const Rocket::Core::String& path, SCP_string& name, int& dir_type);
};
} // namespace scpui
+250
View File
@@ -0,0 +1,250 @@
//
//
// Undefine stupid windows API defines that get included by the prefix headers
#ifdef GetNextSibling
#undef GetNextSibling
#endif
#ifdef GetFirstChild
#undef GetFirstChild
#endif
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include "RocketRenderingInterface.h"
#include "RocketFileInterface.h"
#pragma pop_macro("Assert")
#include "graphics/2d.h"
#include "graphics/material.h"
using namespace Rocket::Core;
namespace scpui {
RocketRenderingInterface::RocketRenderingInterface()
{
renderOffset.x = 0.0f;
renderOffset.y = 0.0f;
vertex_stream_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Streaming);
index_stream_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Streaming);
layout.add_vertex_component(vertex_format_data::POSITION2, sizeof(Vertex), offsetof(Vertex, position));
layout.add_vertex_component(vertex_format_data::COLOR4, sizeof(Vertex), offsetof(Vertex, colour));
layout.add_vertex_component(vertex_format_data::TEX_COORD2, sizeof(Vertex), offsetof(Vertex, tex_coord));
}
RocketRenderingInterface::~RocketRenderingInterface()
{
if (vertex_stream_buffer >= 0) {
gr_delete_buffer(vertex_stream_buffer);
}
if (index_stream_buffer >= 0) {
gr_delete_buffer(index_stream_buffer);
}
}
RocketRenderingInterface::Texture* RocketRenderingInterface::get_texture(Rocket::Core::TextureHandle handle)
{
return reinterpret_cast<Texture*>(handle);
}
Rocket::Core::TextureHandle RocketRenderingInterface::get_texture_handle(RocketRenderingInterface::Texture* bitmap)
{
return reinterpret_cast<Rocket::Core::TextureHandle>(bitmap);
}
CompiledGeometryHandle RocketRenderingInterface::CompileGeometry(Vertex* vertices, int num_vertices, int* indices,
int num_indices, TextureHandle texture)
{
GR_DEBUG_SCOPE("libRocket::CompileGeometry");
auto* geom = new CompiledGeometry();
geom->vertex_buffer = gr_create_buffer(BufferType::Vertex, BufferUsageHint::Static);
gr_update_buffer_data(geom->vertex_buffer, num_vertices * sizeof(Vertex), reinterpret_cast<void*>(vertices));
geom->index_buffer = gr_create_buffer(BufferType::Index, BufferUsageHint::Static);
gr_update_buffer_data(geom->index_buffer, num_indices * sizeof(int), reinterpret_cast<void*>(indices));
geom->num_elements = num_indices;
geom->texture = get_texture(texture);
return reinterpret_cast<CompiledGeometryHandle>(geom);
}
void RocketRenderingInterface::RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation)
{
GR_DEBUG_SCOPE("libRocket::RenderCompiledGeometry");
auto geom = reinterpret_cast<CompiledGeometry*>(geometry);
auto bitmap = -1;
if (geom->texture != nullptr) {
bitmap = geom->texture->handle + geom->texture->frame_num;
}
renderGeometry(geom->vertex_buffer, geom->index_buffer, geom->num_elements, bitmap, translation);
}
void RocketRenderingInterface::ReleaseCompiledGeometry(CompiledGeometryHandle geometry)
{
GR_DEBUG_SCOPE("libRocket::ReleaseCompiledGeometry");
auto geom = reinterpret_cast<CompiledGeometry*>(geometry);
gr_delete_buffer(geom->vertex_buffer);
gr_delete_buffer(geom->index_buffer);
delete geom;
}
void RocketRenderingInterface::EnableScissorRegion(bool enable)
{
GR_DEBUG_SCOPE("libRocket::EnableScissorRegion");
if (!enable) {
// Disable clipping if requested
gr_reset_clip();
}
// If clipping is active then SetScissorRegion will be called next
clipping_active = enable;
}
void RocketRenderingInterface::SetScissorRegion(int x, int y, int width, int height)
{
GR_DEBUG_SCOPE("libRocket::SetScissorRegion");
if (clipping_active) {
gr_set_clip((int)(x + renderOffset.x), (int)(y + renderOffset.y), width, height, GR_RESIZE_NONE);
}
}
bool RocketRenderingInterface::LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions,
const String& source)
{
GR_DEBUG_SCOPE("libRocket::LoadTexture");
SCP_string filename;
int dir_type;
if (!RocketFileInterface::getCFilePath(source, filename, dir_type)) {
return false;
}
auto period_pos = filename.rfind('.');
if (period_pos != SCP_string::npos) {
filename = filename.substr(0, period_pos);
}
auto id = bm_load_either(filename.c_str(), nullptr, nullptr, nullptr, false, dir_type);
if (id < 0) {
return false;
}
int w, h;
bm_get_info(id, &w, &h);
texture_dimensions.x = w;
texture_dimensions.y = h;
auto* tex = new Texture();
tex->handle = id;
texture_handle = get_texture_handle(tex);
return true;
}
bool RocketRenderingInterface::GenerateTexture(TextureHandle& texture_handle, const Rocket::Core::byte* source,
const Vector2i& source_dimensions)
{
GR_DEBUG_SCOPE("libRocket::GenerateTexture");
auto size = (size_t)(source_dimensions.x * source_dimensions.y * 4); // RGBA format
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
memcpy(buffer.get(), source, size);
auto id = bm_create(32, source_dimensions.x, source_dimensions.y, buffer.get());
if (id < 0) {
return false;
}
auto* tex = new Texture();
tex->handle = id;
tex->data = std::move(buffer);
texture_handle = get_texture_handle(tex);
return true;
}
void RocketRenderingInterface::ReleaseTexture(TextureHandle texture)
{
GR_DEBUG_SCOPE("libRocket::ReleaseTexture");
Assertion(texture, "Invalid texture handle!");
auto tex = get_texture(texture);
bm_release(tex->handle);
delete tex;
}
void RocketRenderingInterface::RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices,
TextureHandle texture, const Vector2f& translation)
{
gr_update_buffer_data(vertex_stream_buffer, sizeof(*vertices) * num_vertices, vertices);
gr_update_buffer_data(index_stream_buffer, sizeof(*indices) * num_indices, indices);
int bitmap;
if (texture == 0) {
bitmap = -1;
} else {
bitmap = get_texture(texture)->handle + get_texture(texture)->frame_num;
}
renderGeometry(vertex_stream_buffer, index_stream_buffer, num_indices, bitmap, translation);
}
void RocketRenderingInterface::setRenderOffset(const vec2d& render_offset) { renderOffset = render_offset; }
float RocketRenderingInterface::GetPixelsPerInch()
{
#if SDL_VERSION_ATLEAST(2, 0, 4)
auto display = os_config_read_uint("Video", "Display", 0);
float ddpi;
if (SDL_GetDisplayDPI(display, &ddpi, nullptr, nullptr) != 0) {
mprintf(("Failed to determine display DPI: %s\n", SDL_GetError()));
return 96.f;
}
// Diagonal DPI should be accurate enough
return ddpi;
#else
// return a default value
return 96.f;
#endif
}
void RocketRenderingInterface::renderGeometry(int vertex_buffer, int index_buffer, int num_elements, int bitmap,
const Rocket::Core::Vector2f& translation)
{
interface_material material;
vec2d transl;
transl.x = translation.x + renderOffset.x;
transl.y = translation.y + renderOffset.y;
material_set_rocket_interface(&material, bitmap, transl);
gr_render_rocket_primitives(&material, PRIM_TYPE_TRIS, &layout, num_elements, vertex_buffer, index_buffer);
}
int RocketRenderingInterface::getBitmapNum(Rocket::Core::TextureHandle handle)
{
int bitmap;
if (handle == 0) {
bitmap = -1;
} else {
bitmap = get_texture(handle)->handle;
}
return bitmap;
}
void RocketRenderingInterface::setAnimationFrame(Rocket::Core::TextureHandle handle, int frame)
{
Assertion(handle != 0, "Invalid handle for setAnimationFrame");
get_texture(handle)->frame_num = frame;
}
} // namespace scpui
+145
View File
@@ -0,0 +1,145 @@
#pragma once
//
//
#include "globalincs/pstypes.h"
#include "graphics/2d.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/RenderInterface.h>
#pragma pop_macro("Assert")
#include <memory>
namespace scpui {
class RocketRenderingInterface : public Rocket::Core::RenderInterface {
struct Texture {
int handle = -1;
int frame_num = 0;
std::unique_ptr<uint8_t[]> data;
};
struct CompiledGeometry {
int vertex_buffer = -1;
int index_buffer = -1;
int num_elements = -1;
Texture* texture = nullptr;
};
bool clipping_active = false;
vec2d renderOffset;
Texture* get_texture(Rocket::Core::TextureHandle handle);
Rocket::Core::TextureHandle get_texture_handle(Texture* bitmap);
void renderGeometry(int vertex_buffer, int index_buffer, int num_elements, int bitmap,
const Rocket::Core::Vector2f& translation);
int vertex_stream_buffer = -1;
int index_stream_buffer = -1;
vertex_layout layout;
public:
RocketRenderingInterface();
~RocketRenderingInterface() override;
/**
* @brief Renders geometry from a memory buffer
*
* @details This uses an internal streaming buffer for storing the data. This is not as fast as using compiled
* geometry but needs to be supported for the debugger.
*
* @param vertices
* @param num_vertices
* @param indices
* @param num_indices
* @param texture
* @param translation
*/
void RenderGeometry(Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices,
Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation) override;
/**
* @brief Compiles geometry to a GPU buffer
*
* @details This uses GPU buffers provided by the graphics interface and stores the data in them. The buffers are
* allocated as static so rendering them should be pretty fast.
*
* @param vertices
* @param num_vertices
* @param indices
* @param num_indices
* @param texture
* @return
*/
Rocket::Core::CompiledGeometryHandle CompileGeometry(Rocket::Core::Vertex* vertices, int num_vertices, int* indices,
int num_indices, Rocket::Core::TextureHandle texture) override;
/**
* @brief Renders compiled geometry
*
* @details Takes the geometry buffers in the specified handle and renders them with the specified translation.
*
* @param geometry
* @param translation
*/
void RenderCompiledGeometry(Rocket::Core::CompiledGeometryHandle geometry,
const Rocket::Core::Vector2f& translation) override;
/**
* @brief Releases the geometry buffers
*
* @details Frees the underlying geometry buffers.
*
* @param geometry
*/
void ReleaseCompiledGeometry(Rocket::Core::CompiledGeometryHandle geometry) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(int x, int y, int width, int height) override;
bool LoadTexture(Rocket::Core::TextureHandle& texture_handle, Rocket::Core::Vector2i& texture_dimensions,
const Rocket::Core::String& source) override;
bool GenerateTexture(Rocket::Core::TextureHandle& texture_handle, const Rocket::Core::byte* source,
const Rocket::Core::Vector2i& source_dimensions) override;
void ReleaseTexture(Rocket::Core::TextureHandle texture) override;
/**
* @brief Sets the render offset of future drawing operations
*
* @details This allows to offset any subsequent rendering operations without changing the libRocket markup.
*
* @param render_offset
*/
void setRenderOffset(const vec2d& render_offset);
float GetPixelsPerInch() override;
/**
* @brief From a texture handle, get the bitmap number for bmpman
* @param handle The libRocket texture handle
* @return The bitmap number
*/
int getBitmapNum(Rocket::Core::TextureHandle handle);
/**
* @brief Sets the animation frame that the TextureHandle will use when it's rendered
* @param handle The libRocket texture handle to modify
* @param frame The animation frame (0-based for the first frame)
*/
void setAnimationFrame(Rocket::Core::TextureHandle handle, int frame);
};
} // namespace scpui
+62
View File
@@ -0,0 +1,62 @@
//
//
#include "RocketSystemInterface.h"
#include "globalincs/pstypes.h"
#include "io/timer.h"
#include "localization/localize.h"
using namespace Rocket::Core;
namespace scpui {
float RocketSystemInterface::GetElapsedTime() { return timer_get_microseconds() / 1000000.f; }
int RocketSystemInterface::TranslateString(Rocket::Core::String& translated, const Rocket::Core::String& input)
{
SCP_string lcl_translated;
lcl_ext_localize(input.CString(), lcl_translated);
translated = lcl_translated.c_str();
return 0; // Everything is translated now
}
void RocketSystemInterface::JoinPath(Rocket::Core::String& translated_path,
const Rocket::Core::String& /*document_path*/, const Rocket::Core::String& path)
{
// We here at the SCP are rather simple. We just take the path that is in the document.
// Our file opening procedures take care of finding those files
translated_path = path;
}
bool RocketSystemInterface::LogMessage(Rocket::Core::Log::Type type, const Rocket::Core::String& message)
{
switch (type) {
case Log::LT_ERROR:
ReleaseWarning(LOCATION, "libRocket error: %s", message.CString());
break;
case Log::LT_WARNING:
Warning(LOCATION, "libRocket warning: %s", message.CString());
break;
case Log::LT_ASSERT:
os::dialogs::AssertMessage("libRocket assertion", LOCATION, "%s", message.CString());
break;
case Log::LT_ALWAYS:
case Log::LT_INFO:
case Log::LT_DEBUG:
// These always go into the standard log
mprintf(("libRocket message: %s\n", message.CString()));
break;
default:
break;
}
// We never break into the debugger
return false;
}
void RocketSystemInterface::ActivateKeyboard()
{
// TODO: Maybe extend libRocket to expose where the input currently is
SDL_StartTextInput();
}
void RocketSystemInterface::DeactivateKeyboard() { SDL_StopTextInput(); }
} // namespace scpui
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include "globalincs/pstypes.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/SystemInterface.h>
#pragma pop_macro("Assert")
namespace scpui {
/**
* @brief Implementation of the rocket system interface for FSO
*/
class RocketSystemInterface : public Rocket::Core::SystemInterface {
public:
/**
* @details Uses the FSO timer routines for determining how much time has passed
*
* @return
*/
float GetElapsedTime() override;
/**
* @details Translates the given string using the FSO translation mechanics. This will replace string of the form
* "XSTR("Test", 1234)" with the corresponding string that was defined in the tables. It will also replace variables
* such as $callsign
*
* @param translated
* @param input
* @return
*/
int TranslateString(Rocket::Core::String& translated, const Rocket::Core::String& input) override;
/**
* @details Since the FSO file opening implementation already takes care of locating a specific file, this
* implementation simply returns the path that was specified in the document without altering it.
*
* @param translated_path
* @param document_path
* @param path
*/
void JoinPath(Rocket::Core::String& translated_path, const Rocket::Core::String& document_path,
const Rocket::Core::String& path) override;
/**
* @details Logs a message using the FSO logging and message routines. Errors, Warnings, and Assertions will use the
* respective dialogs while logging and debug messages will be directed to the FSO log.
*
* @param type
* @param message
* @return
*/
bool LogMessage(Rocket::Core::Log::Type type, const Rocket::Core::String& message) override;
/**
* @details Activates the keyboard by using the SDL provided functions for that.
*/
void ActivateKeyboard() override;
/**
* @details Also uses the SDL functions for deactivating the keyboard again
*/
void DeactivateKeyboard() override;
};
} // namespace scpui
+174
View File
@@ -0,0 +1,174 @@
//
//
#include "SoundPlugin.h"
#include "gamesnd/gamesnd.h"
#include "utils/string_utils.h"
using namespace Rocket::Core;
namespace {
struct SoundDefinition {
interface_snd_id default_id;
SCP_unordered_map<SCP_string, interface_snd_id> state_ids;
};
SoundDefinition parse_sound_value(const String& value)
{
SoundDefinition def{};
bool saw_default = false;
for (auto& part : util::split_string(SCP_string(value.CString()), ',')) {
drop_white_space(part);
if (part.find(')') != SCP_string::npos) {
auto state_start = part.find('(');
auto state_end = part.find(')');
if (state_start == SCP_string::npos || state_end == SCP_string::npos) {
throw std::runtime_error("Could not find both '(' and ')' for state sound!");
}
state_start += 1; // skip the (
if (state_start > state_end) {
throw std::runtime_error("')' appeared before '('!");
}
SCP_string state_name = part.substr(state_start, state_end - state_start);
if (state_name.empty()) {
throw std::runtime_error("State name is empty!");
}
auto sound_name = part.substr(state_end + 1);
drop_white_space(sound_name);
auto snd = gamesnd_get_by_iface_name(sound_name.c_str());
if (!snd.isValid()) {
SCP_string error("Sound '");
error += sound_name;
error += "' for state '";
error += state_name;
error += "' is invalid!";
throw std::runtime_error(error);
}
def.state_ids.emplace(state_name, snd);
} else {
if (saw_default) {
throw std::runtime_error("Multiple default sounds specified!");
}
def.default_id = gamesnd_get_by_iface_name(part.c_str());
saw_default = true;
if (!def.default_id.isValid()) {
throw std::runtime_error("Default sound has an invalid name!");
}
}
}
return def;
}
}
namespace scpui {
bool SoundPropertyParser::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
{
try {
parse_sound_value(value);
} catch (const std::exception& e) {
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", value.CString(), e.what()));
return false;
}
property.value = Variant(value);
property.unit = Property::STRING;
return true;
}
void SoundPropertyParser::Release() { delete this; }
int SoundPlugin::GetEventClasses() { return EVT_BASIC | EVT_ELEMENT; }
SoundPlugin* SoundPlugin::_instance = nullptr;
SCP_vector<SCP_string> SoundPlugin::_event_types = {"click", "dblclick", "mouseover", "mouseout",
"mouseup", "mousedown", "mousescroll", "scrollchange"};
const SCP_vector<SCP_string>& SoundPlugin::getEventTypes() { return _event_types; }
void SoundPlugin::OnShutdown() { delete this; }
void SoundPlugin::OnElementCreate(Element* element)
{
for (auto& type : getEventTypes()) {
element->AddEventListener(type.c_str(), _event_handler.get());
}
}
void SoundPlugin::OnElementDestroy(Element* /*element*/) {}
SoundPlugin::SoundPlugin()
{
_event_handler.reset(new SoundEventHandler());
_instance = this;
}
SoundPlugin::~SoundPlugin() { _instance = nullptr; }
SoundPlugin* SoundPlugin::instance() { return _instance; }
bool SoundPlugin::PlayElementSound(Element* element, const String& event, const String& state)
{
auto property_name = event + "-sound";
const Property* sound_prop = element->GetProperty(property_name);
if (!(sound_prop->unit & Property::STRING)) {
// sounds are stored as their original string values which are then parsed whenever necessary
return false;
}
auto sound = sound_prop->Get<String>();
try {
auto snds = parse_sound_value(sound);
interface_snd_id sound_id;
if (state.Empty()) {
sound_id = snds.default_id;
} else {
auto snd_iter = snds.state_ids.find(SCP_string(state.CString()));
if (snd_iter == snds.state_ids.end()) {
return false;
}
sound_id = snd_iter->second;
}
// The default sound is optional so this might be invalid
if (!sound_id.isValid()) {
return false;
}
if (event == "scrollchange") {
if (_last_scrollchange_time >= 0) {
auto diff = Rocket::Core::GetSystemInterface()->GetElapsedTime() - _last_scrollchange_time;
if (diff < gamesnd_get_max_duration(gamesnd_get_interface_sound(sound_id)) / 1000.0f * 1.1f) {
return false;
}
}
_last_scrollchange_time = Rocket::Core::GetSystemInterface()->GetElapsedTime();
}
gamesnd_play_iface(sound_id);
return true;
} catch (const std::exception& e) {
mprintf(("libRocket: Failure to parse sound CSS property '%s': %s\n", sound.CString(), e.what()));
return false;
}
}
void SoundPlugin::SoundEventHandler::ProcessEvent(Event& event)
{
SoundPlugin::instance()->PlayElementSound(event.GetCurrentElement(), event.GetType());
}
} // namespace scpui
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include "globalincs/pstypes.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core.h>
#include <Rocket/Core/Plugin.h>
#pragma pop_macro("Assert")
namespace scpui {
class SoundPropertyParser : public Rocket::Core::PropertyParser {
bool ParseValue(Rocket::Core::Property& property, const Rocket::Core::String& value,
const Rocket::Core::ParameterMap& parameters) const override;
void Release() override;
};
class SoundPlugin : public Rocket::Core::Plugin {
class SoundEventHandler : public Rocket::Core::EventListener {
public:
void ProcessEvent(Rocket::Core::Event& event) override;
};
std::unique_ptr<SoundEventHandler> _event_handler;
float _last_scrollchange_time = -1.0f;
static SoundPlugin* _instance;
static SCP_vector<SCP_string> _event_types;
public:
SoundPlugin();
~SoundPlugin() override;
SoundPlugin(const SoundPlugin&) = delete;
SoundPlugin& operator=(const SoundPlugin&) = delete;
SoundPlugin(SoundPlugin&&) noexcept = delete;
SoundPlugin& operator=(SoundPlugin&&) noexcept = delete;
void OnShutdown() override;
int GetEventClasses() override;
void OnElementCreate(Rocket::Core::Element* element) override;
void OnElementDestroy(Rocket::Core::Element* element) override;
/**
* @brief Plays a element specific sound
*
* This can be used for implementing context specific sounds for specific elements where the actual sounds are
* defined by the interface designer in the RCSS.
*
* @param element The element to play the sound for
* @param event The event identification
* @param state The state for which the sound should be played. May be empty in which case the default sound is
* used.
* @return @c true if a sound was played, @c false otherwise
*/
bool PlayElementSound(Rocket::Core::Element* element, const Rocket::Core::String& event,
const Rocket::Core::String& state = "");
static const SCP_vector<SCP_string>& getEventTypes();
static SoundPlugin* instance();
};
} // namespace scpui
+278
View File
@@ -0,0 +1,278 @@
//
//
#include "AnimationElement.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core.h>
#include <Rocket/Core/ElementDocument.h>
#include <Rocket/Core/GeometryUtilities.h>
#include <Rocket/Core/String.h>
#include <scpui/RocketRenderingInterface.h>
#pragma pop_macro("Assert")
namespace scpui {
namespace elements {
using namespace Rocket;
using namespace Rocket::Core;
// This is basically a copy of ElementImage with a few changes
AnimationElement::AnimationElement(const String& tag_in) : Element(tag_in), dimensions(-1, -1), geometry(this)
{
ResetCoords();
geometry_dirty = false;
texture_dirty = true;
}
AnimationElement::~AnimationElement() = default;
// Sizes the box to the element's inherent size.
bool AnimationElement::GetIntrinsicDimensions(Vector2f& _dimensions)
{
// Check if we need to reload the texture.
if (texture_dirty) {
LoadTexture();
}
// Calculate the x dimension.
if (HasAttribute("width")) {
dimensions.x = GetAttribute<float>("width", -1);
} else if (using_coords) {
dimensions.x = (float)(coords[2] - coords[0]);
} else {
dimensions.x = (float)texture.GetDimensions(GetRenderInterface()).x;
}
// Calculate the y dimension.
if (HasAttribute("height")) {
dimensions.y = GetAttribute<float>("height", -1);
} else if (using_coords) {
dimensions.y = (float)(coords[3] - coords[1]);
} else {
dimensions.y = (float)texture.GetDimensions(GetRenderInterface()).y;
}
// Return the calculated dimensions. If this changes the size of the element, it will result in
// a 'resize' event which is caught below and will regenerate the geometry.
_dimensions = dimensions;
return true;
}
// Renders the element.
void AnimationElement::OnRender()
{
// Regenerate the geometry if required (this will be set if 'coords' changes but does not
// result in a resize).
if (geometry_dirty) {
GenerateGeometry();
}
// Use dynamic cast for debug assertions but use a static cast everywhere else for improved efficiency
Assertion(dynamic_cast<RocketRenderingInterface*>(GetRenderInterface()) != nullptr,
"AnimationElement can only be used with the FSO rendering interface.");
auto render = static_cast<RocketRenderingInterface*>(GetRenderInterface());
// Set the frame of our texture handle. libRocket doesn't support animations so we need to use a small hack for this
render->setAnimationFrame(texture.GetHandle(render), current_animation_frame);
// Render the geometry beginning at this element's content region.
geometry.Render(GetAbsoluteOffset(Rocket::Core::Box::CONTENT));
// Reset this after rendering in case the texture object is shared with someone else
render->setAnimationFrame(texture.GetHandle(render), 0);
}
// Called when attributes on the element are changed.
void AnimationElement::OnAttributeChange(const Rocket::Core::AttributeNameList& changed_attributes)
{
// Call through to the base element's OnAttributeChange().
Rocket::Core::Element::OnAttributeChange(changed_attributes);
float dirty_layout = false;
// Check for a changed 'src' attribute. If this changes, the old texture handle is released,
// forcing a reload when the layout is regenerated.
if (changed_attributes.find("src") != changed_attributes.end()) {
texture_dirty = true;
dirty_layout = true;
// Reset the animation timestamps to make sure the animation starts at the beginning
current_animation_frame = -1;
animation_start_time = -1.f;
}
// Check for a changed 'width' attribute. If this changes, a layout is forced which will
// recalculate the dimensions.
if (changed_attributes.find("width") != changed_attributes.end() ||
changed_attributes.find("height") != changed_attributes.end()) {
dirty_layout = true;
}
// Check for a change to the 'coords' attribute. If this changes, the coordinates are
// recomputed and a layout forced.
if (changed_attributes.find("coords") != changed_attributes.end()) {
if (HasAttribute("coords")) {
StringList coords_list;
StringUtilities::ExpandString(coords_list, GetAttribute<String>("coords", ""));
if (coords_list.size() != 4) {
Rocket::Core::Log::Message(
Log::LT_WARNING,
"Element '%s' has an invalid 'coords' attribute; coords requires 4 values, found %d.",
GetAddress().CString(), coords_list.size());
ResetCoords();
} else {
for (size_t i = 0; i < 4; ++i) {
coords[i] = atoi(coords_list[i].CString());
}
// Check for the validity of the coordinates.
if (coords[0] < 0 || coords[2] < coords[0] || coords[1] < 0 || coords[3] < coords[1]) {
Rocket::Core::Log::Message(
Log::LT_WARNING,
"Element '%s' has an invalid 'coords' attribute; invalid coordinate values specified.",
GetAddress().CString());
ResetCoords();
} else {
// We have new, valid coordinates; force the geometry to be regenerated.
geometry_dirty = true;
using_coords = true;
}
}
} else {
ResetCoords();
}
// Coordinates have changes; this will most likely result in a size change, so we need to force a layout.
dirty_layout = true;
}
if (dirty_layout) {
DirtyLayout();
}
}
// Regenerates the element's geometry.
void AnimationElement::ProcessEvent(Rocket::Core::Event& event)
{
Element::ProcessEvent(event);
if (event.GetTargetElement() == this && event == "resize") {
GenerateGeometry();
}
}
void AnimationElement::GenerateGeometry()
{
// Release the old geometry before specifying the new vertices.
geometry.Release(true);
std::vector<Rocket::Core::Vertex>& vertices = geometry.GetVertices();
std::vector<int>& indices = geometry.GetIndices();
vertices.resize(4);
indices.resize(6);
// Generate the texture coordinates.
Vector2f texcoords[2];
if (using_coords) {
Vector2f texture_dimensions((float)texture.GetDimensions(GetRenderInterface()).x,
(float)texture.GetDimensions(GetRenderInterface()).y);
if (texture_dimensions.x == 0) {
texture_dimensions.x = 1;
}
if (texture_dimensions.y == 0) {
texture_dimensions.y = 1;
}
texcoords[0].x = (float)coords[0] / texture_dimensions.x;
texcoords[0].y = (float)coords[1] / texture_dimensions.y;
texcoords[1].x = (float)coords[2] / texture_dimensions.x;
texcoords[1].y = (float)coords[3] / texture_dimensions.y;
} else {
texcoords[0] = Vector2f(0, 0);
texcoords[1] = Vector2f(1, 1);
}
Rocket::Core::GeometryUtilities::GenerateQuad(&vertices[0], // vertices to write to
&indices[0], // indices to write to
Vector2f(0, 0), // origin of the quad
GetBox().GetSize(Rocket::Core::Box::CONTENT), // size of the quad
Colourb(255, 255, 255, 255), // colour of the vertices
texcoords[0], // top-left texture coordinate
texcoords[1]); // top-right texture coordinate
geometry_dirty = false;
}
bool AnimationElement::LoadTexture()
{
texture_dirty = false;
// Get the source URL for the image.
String image_source = GetAttribute<String>("src", "");
if (image_source.Empty()) {
return false;
}
geometry_dirty = true;
Rocket::Core::ElementDocument* document = GetOwnerDocument();
URL source_url(document == nullptr ? "" : document->GetSourceURL());
if (!texture.Load(image_source, source_url.GetPath())) {
geometry.SetTexture(nullptr);
return false;
}
// Set the texture onto our geometry object.
geometry.SetTexture(&texture);
return true;
}
void AnimationElement::ResetCoords()
{
using_coords = false;
for (int & coord : coords) {
coord = -1;
}
}
void AnimationElement::OnUpdate()
{
Element::OnUpdate();
if (texture.GetHandle(GetRenderInterface()) == 0) {
// Make sure that the handle is valid
return;
}
// Initialize start time if it hasn't been set yet
if (animation_start_time < 0.f) {
animation_start_time = Rocket::Core::GetSystemInterface()->GetElapsedTime();
}
// Use dynamic cast for debug assertions but use a static cast everywhere else for improved efficiency
Assertion(dynamic_cast<RocketRenderingInterface*>(GetRenderInterface()) != nullptr,
"AnimationElement can only be used with the FSO rendering interface.");
auto render = static_cast<RocketRenderingInterface*>(GetRenderInterface());
// Determine the current frame of our animation
auto bitmap_num = render->getBitmapNum(texture.GetHandle(render));
auto currentTime = Rocket::Core::GetSystemInterface()->GetElapsedTime() - animation_start_time;
// Looping is the default at the moment, could be customized with a property at some point
current_animation_frame = bm_get_anim_frame(bitmap_num, currentTime, 0.0f, true);
}
} // namespace elements
} // namespace scpui
+74
View File
@@ -0,0 +1,74 @@
#pragma once
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/Element.h>
#include <Rocket/Core/Geometry.h>
#include <Rocket/Core/Texture.h>
#pragma pop_macro("Assert")
namespace scpui {
namespace elements {
using namespace Rocket::Core;
class AnimationElement : public Rocket::Core::Element {
public:
/// Constructs a new ElementImage. This should not be called directly; use the Factory instead.
/// @param[in] tag_in The tag the element was declared as in RML.
AnimationElement(const String& tag_in);
~AnimationElement() override;
/// Returns the element's inherent size.
/// @param[out] The element's intrinsic dimensions.
/// @return True.
bool GetIntrinsicDimensions(Vector2f& dimensions) override;
protected:
/// Renders the image.
void OnRender() override;
void OnUpdate() override;
/// Checks for changes to the image's source or dimensions.
/// @param[in] changed_attributes A list of attributes changed on the element.
void OnAttributeChange(const AttributeNameList& changed_attributes) override;
/// Regenerates the element's geometry on a resize event.
/// @param[in] event The event to process.
void ProcessEvent(Event& event) override;
private:
// Generates the element's geometry.
void GenerateGeometry();
// Loads the element's texture, as specified by the 'src' attribute.
bool LoadTexture();
// Resets the values of the 'coords' attribute to mark them as unused.
void ResetCoords();
// The texture this element is rendering from.
Texture texture;
// True if we need to refetch the texture's source from the element's attributes.
bool texture_dirty;
// The element's computed intrinsic dimensions. If either of these values are set to -1, then
// that dimension has not been computed yet.
Vector2f dimensions;
// The integer coords extracted from the 'coords' attribute. using_coords will be false if
// these have not been specified or are invalid.
int coords[4];
bool using_coords;
// The geometry used to render this element.
Geometry geometry;
bool geometry_dirty;
float animation_start_time = -1.f;
int current_animation_frame = -1;
};
} // namespace elements
} // namespace scpui
+641
View File
@@ -0,0 +1,641 @@
//
//
// Undefine stupid windows API defines that get included by the prefix headers
#ifdef GetNextSibling
#undef GetNextSibling
#endif
#ifdef GetFirstChild
#undef GetFirstChild
#endif
#include "scpui/rocket_ui.h"
#include "cfile/cfile.h"
#include "osapi/osapi.h"
#include "scpui/RocketFileInterface.h"
#include "scpui/RocketRenderingInterface.h"
#include "scpui/RocketSystemInterface.h"
#include "scpui/SoundPlugin.h"
#include "scpui/elements/AnimationElement.h"
#include "scripting/scripting.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Controls.h>
#include <Rocket/Core.h>
#include <Rocket/Debugger.h>
#include <Rocket/Controls/Lua/Controls.h>
#include <Rocket/Core/Lua/Interpreter.h>
#pragma pop_macro("Assert")
#include <codecvt>
#include <locale>
using namespace Rocket::Core;
namespace {
std::unique_ptr<scpui::RocketRenderingInterface> rendering_interface;
std::unique_ptr<scpui::RocketSystemInterface> system_interface;
std::unique_ptr<scpui::RocketFileInterface> file_interface;
Context* input_context = nullptr;
vec2d render_offset = {0.f, 0.f};
bool debugger_initialized = false;
bool transform_to_rocket(vec2d& in)
{
in = {in.x - render_offset.x, in.y - render_offset.y};
if (in.x < 0.f || in.y < 0.f) {
// Out of bounds
return false;
}
auto dims = input_context->GetDimensions();
if (in.x > dims.x || in.y > dims.y) {
return false;
}
return true;
}
int get_modifier_state()
{
int mods = 0;
auto sdl_mods = SDL_GetModState();
if (sdl_mods & KMOD_CTRL) {
mods |= Input::KM_CTRL;
}
if (sdl_mods & KMOD_SHIFT) {
mods |= Input::KM_SHIFT;
}
if (sdl_mods & KMOD_ALT) {
mods |= Input::KM_ALT;
}
if (sdl_mods & KMOD_GUI) {
mods |= Input::KM_META;
}
if (sdl_mods & KMOD_CAPS) {
mods |= Input::KM_CAPSLOCK;
}
if (sdl_mods & KMOD_NUM) {
mods |= Input::KM_NUMLOCK;
}
auto states = SDL_GetKeyboardState(nullptr);
if (states[SDL_SCANCODE_SCROLLLOCK]) {
mods |= Input::KM_SCROLLLOCK;
}
return mods;
}
void load_fonts()
{
SCP_vector<SCP_string> files;
cf_get_file_list(files, CF_TYPE_FONT, "*.ttf");
for (auto& name : files) {
// The file extension gets removed by CFile
FontDatabase::LoadFontFace((name + ".ttf").c_str());
}
}
bool mouse_motion_handler(const SDL_Event& evt)
{
if (!os::events::isWindowEvent(evt, os::getSDLMainWindow())) {
// Ignore events that are not relevant to the main window
return false;
}
if (input_context == nullptr) {
// No active input context
return false;
}
vec2d evt_pos = {(float)evt.motion.x, (float)evt.motion.y};
if (!transform_to_rocket(evt_pos)) {
// Out of bounds
return false;
}
input_context->ProcessMouseMove((int)evt_pos.x, (int)evt_pos.y, get_modifier_state());
return true;
}
bool mouse_button_handler(const SDL_Event& evt)
{
if (!os::events::isWindowEvent(evt, os::getSDLMainWindow())) {
// Ignore events that are not relevant to the main window
return false;
}
if (input_context == nullptr) {
// No active input context
return false;
}
vec2d evt_pos = {(float)evt.button.x, (float)evt.button.y};
if (!transform_to_rocket(evt_pos)) {
// Out of bounds
return false;
}
int button;
switch (evt.button.button) {
case SDL_BUTTON_LEFT:
button = 0;
break;
case SDL_BUTTON_MIDDLE:
button = 2;
break;
case SDL_BUTTON_RIGHT:
button = 1;
break;
case SDL_BUTTON_X1:
button = 3;
break;
case SDL_BUTTON_X2:
button = 4;
break;
default:
// Unknown SDL button value
mprintf(("Unknown SDL button value %d in libRocket mouse handler!", evt.button.button));
return false;
}
if (evt.type == SDL_MOUSEBUTTONUP) {
// Up
input_context->ProcessMouseButtonUp(button, get_modifier_state());
} else {
// Down
input_context->ProcessMouseButtonDown(button, get_modifier_state());
}
return true;
}
bool mouse_wheel_handler(const SDL_Event& evt)
{
if (!os::events::isWindowEvent(evt, os::getSDLMainWindow())) {
// Ignore events that are not relevant to the main window
return false;
}
if (input_context == nullptr) {
// No active input context
return false;
}
int wheel_value = evt.wheel.y;
#if SDL_VERSION_ATLEAST(2, 0, 4)
if (evt.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
wheel_value *= -1;
}
#endif
// libRocket expects a different direction than what SDL delivers
input_context->ProcessMouseWheel(-wheel_value, get_modifier_state());
return true;
}
Input::KeyIdentifier translateKey(SDL_Keycode Key)
{
switch (Key) {
case SDLK_a:
return Rocket::Core::Input::KI_A;
case SDLK_b:
return Rocket::Core::Input::KI_B;
case SDLK_c:
return Rocket::Core::Input::KI_C;
case SDLK_d:
return Rocket::Core::Input::KI_D;
case SDLK_e:
return Rocket::Core::Input::KI_E;
case SDLK_f:
return Rocket::Core::Input::KI_F;
case SDLK_g:
return Rocket::Core::Input::KI_G;
case SDLK_h:
return Rocket::Core::Input::KI_H;
case SDLK_i:
return Rocket::Core::Input::KI_I;
case SDLK_j:
return Rocket::Core::Input::KI_J;
case SDLK_k:
return Rocket::Core::Input::KI_K;
case SDLK_l:
return Rocket::Core::Input::KI_L;
case SDLK_m:
return Rocket::Core::Input::KI_M;
case SDLK_n:
return Rocket::Core::Input::KI_N;
case SDLK_o:
return Rocket::Core::Input::KI_O;
case SDLK_p:
return Rocket::Core::Input::KI_P;
case SDLK_q:
return Rocket::Core::Input::KI_Q;
case SDLK_r:
return Rocket::Core::Input::KI_R;
case SDLK_s:
return Rocket::Core::Input::KI_S;
case SDLK_t:
return Rocket::Core::Input::KI_T;
case SDLK_u:
return Rocket::Core::Input::KI_U;
case SDLK_v:
return Rocket::Core::Input::KI_V;
case SDLK_w:
return Rocket::Core::Input::KI_W;
case SDLK_x:
return Rocket::Core::Input::KI_X;
case SDLK_y:
return Rocket::Core::Input::KI_Y;
case SDLK_z:
return Rocket::Core::Input::KI_Z;
case SDLK_0:
return Rocket::Core::Input::KI_0;
case SDLK_1:
return Rocket::Core::Input::KI_1;
case SDLK_2:
return Rocket::Core::Input::KI_2;
case SDLK_3:
return Rocket::Core::Input::KI_3;
case SDLK_4:
return Rocket::Core::Input::KI_4;
case SDLK_5:
return Rocket::Core::Input::KI_5;
case SDLK_6:
return Rocket::Core::Input::KI_6;
case SDLK_7:
return Rocket::Core::Input::KI_7;
case SDLK_8:
return Rocket::Core::Input::KI_8;
case SDLK_9:
return Rocket::Core::Input::KI_9;
case SDLK_KP_0:
return Rocket::Core::Input::KI_NUMPAD0;
case SDLK_KP_1:
return Rocket::Core::Input::KI_NUMPAD1;
case SDLK_KP_2:
return Rocket::Core::Input::KI_NUMPAD2;
case SDLK_KP_3:
return Rocket::Core::Input::KI_NUMPAD3;
case SDLK_KP_4:
return Rocket::Core::Input::KI_NUMPAD4;
case SDLK_KP_5:
return Rocket::Core::Input::KI_NUMPAD5;
case SDLK_KP_6:
return Rocket::Core::Input::KI_NUMPAD6;
case SDLK_KP_7:
return Rocket::Core::Input::KI_NUMPAD7;
case SDLK_KP_8:
return Rocket::Core::Input::KI_NUMPAD8;
case SDLK_KP_9:
return Rocket::Core::Input::KI_NUMPAD9;
case SDLK_LEFT:
return Rocket::Core::Input::KI_LEFT;
case SDLK_RIGHT:
return Rocket::Core::Input::KI_RIGHT;
case SDLK_UP:
return Rocket::Core::Input::KI_UP;
case SDLK_DOWN:
return Rocket::Core::Input::KI_DOWN;
case SDLK_KP_PLUS:
return Rocket::Core::Input::KI_ADD;
case SDLK_BACKSPACE:
return Rocket::Core::Input::KI_BACK;
case SDLK_DELETE:
return Rocket::Core::Input::KI_DELETE;
case SDLK_KP_DIVIDE:
return Rocket::Core::Input::KI_DIVIDE;
case SDLK_END:
return Rocket::Core::Input::KI_END;
case SDLK_ESCAPE:
return Rocket::Core::Input::KI_ESCAPE;
case SDLK_F1:
return Rocket::Core::Input::KI_F1;
case SDLK_F2:
return Rocket::Core::Input::KI_F2;
case SDLK_F3:
return Rocket::Core::Input::KI_F3;
case SDLK_F4:
return Rocket::Core::Input::KI_F4;
case SDLK_F5:
return Rocket::Core::Input::KI_F5;
case SDLK_F6:
return Rocket::Core::Input::KI_F6;
case SDLK_F7:
return Rocket::Core::Input::KI_F7;
case SDLK_F8:
return Rocket::Core::Input::KI_F8;
case SDLK_F9:
return Rocket::Core::Input::KI_F9;
case SDLK_F10:
return Rocket::Core::Input::KI_F10;
case SDLK_F11:
return Rocket::Core::Input::KI_F11;
case SDLK_F12:
return Rocket::Core::Input::KI_F12;
case SDLK_F13:
return Rocket::Core::Input::KI_F13;
case SDLK_F14:
return Rocket::Core::Input::KI_F14;
case SDLK_F15:
return Rocket::Core::Input::KI_F15;
case SDLK_HOME:
return Rocket::Core::Input::KI_HOME;
case SDLK_INSERT:
return Rocket::Core::Input::KI_INSERT;
case SDLK_LCTRL:
return Rocket::Core::Input::KI_LCONTROL;
case SDLK_LSHIFT:
return Rocket::Core::Input::KI_LSHIFT;
case SDLK_KP_MULTIPLY:
return Rocket::Core::Input::KI_MULTIPLY;
case SDLK_PAUSE:
return Rocket::Core::Input::KI_PAUSE;
case SDLK_RCTRL:
return Rocket::Core::Input::KI_RCONTROL;
case SDLK_RETURN:
return Rocket::Core::Input::KI_RETURN;
case SDLK_RSHIFT:
return Rocket::Core::Input::KI_RSHIFT;
case SDLK_SPACE:
return Rocket::Core::Input::KI_SPACE;
case SDLK_KP_MINUS:
return Rocket::Core::Input::KI_SUBTRACT;
case SDLK_TAB:
return Rocket::Core::Input::KI_TAB;
};
return Rocket::Core::Input::KI_UNKNOWN;
}
bool key_event_handler(const SDL_Event& evt)
{
if (!os::events::isWindowEvent(evt, os::getSDLMainWindow())) {
// Ignore events that are not relevant to the main window
return false;
}
if (input_context == nullptr) {
// No active input context
return false;
}
// Check for special key combinations
auto sdl_mods = SDL_GetModState();
auto shift_down = (sdl_mods & KMOD_SHIFT) != 0;
if (evt.key.keysym.sym == SDLK_ESCAPE && shift_down) {
// Ignore this combination to still enable quitting
return false;
}
if (evt.key.keysym.sym == SDLK_PRINTSCREEN) {
// Ignore print screen buttons to still allow the default engine actions
return false;
}
if (evt.key.keysym.sym == SDLK_LSHIFT || evt.key.keysym.sym == SDLK_RSHIFT || evt.key.keysym.sym == SDLK_LCTRL ||
evt.key.keysym.sym == SDLK_RCTRL||evt.key.keysym.sym == SDLK_LALT||evt.key.keysym.sym == SDLK_RALT) {
// Ignore simple key events of modifiers since they are not meaningful to libRocket
return false;
}
if (evt.type == SDL_KEYUP) {
auto ctrl_down = (sdl_mods & KMOD_CTRL) != 0;
if (evt.key.keysym.sym == SDLK_r && ctrl_down && shift_down) {
// Ctrl+Shift+R reloads everything
scpui::reloadAllContexts();
return true;
} else if (evt.key.keysym.sym == SDLK_d && ctrl_down && shift_down) {
// Ctrl+Shift+D toggles the debugger
if (!debugger_initialized) {
// First we need to initialize the debugger. We use the current context for that, should work in
// most cases
if (!Rocket::Debugger::Initialise(input_context)) {
Warning(LOCATION, "Failed to initialize debugger!");
return true;
}
debugger_initialized = true;
}
Rocket::Debugger::SetContext(input_context);
// Toggle the debugger state
Rocket::Debugger::SetVisible(!Rocket::Debugger::IsVisible());
return true;
}
}
if (evt.type == SDL_KEYDOWN) {
return input_context->ProcessKeyDown(translateKey(evt.key.keysym.sym), get_modifier_state());
} else {
return input_context->ProcessKeyUp(translateKey(evt.key.keysym.sym), get_modifier_state());
}
}
#if _MSC_VER >= 1900
std::u16string utf8_to_utf16(const char* utf8_string)
{
std::wstring_convert<std::codecvt_utf8_utf16<int16_t>, int16_t> convert;
auto intString = convert.from_bytes(utf8_string);
return std::u16string(reinterpret_cast<const char16_t*>(intString.data()));
}
#else
std::u16string utf8_to_utf16(const char* utf8_string)
{
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
return convert.from_bytes(utf8_string);
}
#endif
bool text_input_handler(const SDL_Event& evt)
{
if (!os::events::isWindowEvent(evt, os::getSDLMainWindow())) {
// Ignore events that are not relevant to the main window
return false;
}
if (input_context == nullptr) {
// No active input context
return false;
}
// libRocket expects UCS-2 so we first need to convert to that
auto ucs2String = utf8_to_utf16(evt.text.text);
bool consumed = true;
for (auto& c : ucs2String) {
consumed = consumed && input_context->ProcessTextInput(c);
}
return consumed;
}
} // namespace
namespace scpui {
void initialize()
{
rendering_interface.reset(new RocketRenderingInterface());
system_interface.reset(new RocketSystemInterface());
file_interface.reset(new RocketFileInterface());
rendering_interface->setRenderOffset(render_offset);
// Setup our custom interfaces
Rocket::Core::SetRenderInterface(rendering_interface.get());
Rocket::Core::SetSystemInterface(system_interface.get());
Rocket::Core::SetFileInterface(file_interface.get());
Rocket::Core::Initialise();
// Initialise the Rocket Controls library.
Rocket::Controls::Initialise();
Rocket::Core::Factory::RegisterElementInstancer("ani", new ElementInstancerGeneric<elements::AnimationElement>())
->RemoveReference();
// Setup the plugin a style sheet properties for the sound support
Rocket::Core::RegisterPlugin(new SoundPlugin());
Rocket::Core::StyleSheetSpecification::RegisterParser("sound", new SoundPropertyParser());
for (auto& type : SoundPlugin::getEventTypes()) {
auto prop_name = type + "-sound";
Rocket::Core::StyleSheetSpecification::RegisterProperty(prop_name.c_str(), "-1", false).AddParser("sound");
}
// Initialise the Lua interface
Rocket::Core::Lua::Interpreter::Initialise(Script_system.GetLuaSession());
Rocket::Controls::Lua::RegisterTypes(Rocket::Core::Lua::Interpreter::GetLuaState());
load_fonts();
// initialize events
os::events::addEventListener(SDL_MOUSEMOTION, os::events::DEFAULT_LISTENER_WEIGHT - 10, mouse_motion_handler);
os::events::addEventListener(SDL_MOUSEBUTTONUP, os::events::DEFAULT_LISTENER_WEIGHT - 10, mouse_button_handler);
os::events::addEventListener(SDL_MOUSEBUTTONDOWN, os::events::DEFAULT_LISTENER_WEIGHT - 10, mouse_button_handler);
os::events::addEventListener(SDL_MOUSEWHEEL, os::events::DEFAULT_LISTENER_WEIGHT - 10, mouse_wheel_handler);
os::events::addEventListener(SDL_KEYUP, os::events::DEFAULT_LISTENER_WEIGHT - 10, key_event_handler);
os::events::addEventListener(SDL_KEYDOWN, os::events::DEFAULT_LISTENER_WEIGHT - 10, key_event_handler);
os::events::addEventListener(SDL_TEXTINPUT, os::events::DEFAULT_LISTENER_WEIGHT - 10, text_input_handler);
}
void setOffset(float x, float y) { rendering_interface->setRenderOffset({x, y}); }
void shutdown_scripting() { Rocket::Core::Lua::Interpreter::Shutdown(false); }
void shutdown()
{
if (input_context != nullptr) {
// If this is still here then the user forgot to disable input
mprintf(("Warning: The input context is still active! Disabling input...\n"));
disableInput();
}
// Shutdown Rocket.
Rocket::Core::Shutdown();
rendering_interface.reset();
system_interface.reset();
file_interface.reset();
}
void reloadContext(Rocket::Core::Context* context)
{
struct DocumentStatus {
ElementDocument* original = nullptr;
String file_name;
bool visible = true;
bool focus = false;
};
// Save the current status of the documents in this context
SCP_vector<DocumentStatus> documents;
for (auto i = 0; i < context->GetNumDocuments(); ++i) {
ElementDocument* doc = context->GetDocument(i);
if (doc->GetSourceURL() == "") {
// In-memory documents can't be reloaded
continue;
}
DocumentStatus status;
status.original = doc;
status.file_name = doc->GetSourceURL();
status.visible = doc->IsVisible();
status.focus = doc->GetFocusLeafNode() != nullptr;
documents.push_back(status);
}
Factory::ClearStyleSheetCache();
Factory::ClearTemplateCache();
// And readd them all
for (auto& status : documents) {
// Selectively unload the old documents.
// We can't just unload all since that destroys the debugger
context->UnloadDocument(status.original);
auto doc = context->LoadDocument(status.file_name);
if (status.visible) {
doc->Show(status.focus ? ElementDocument::FOCUS : ElementDocument::NONE);
}
// We don't own this reference
doc->RemoveReference();
}
}
void reloadAllContexts()
{
for (auto i = 0; i < Rocket::Core::GetNumContexts(); ++i) {
reloadContext(Rocket::Core::GetContext(i));
}
}
void enableInput(Rocket::Core::Context* main_ctx)
{
Assertion(main_ctx != nullptr, "Invalid context pointer detected!");
if (input_context != nullptr) {
// If we still have an active context, disable the input on the previous context
disableInput();
}
input_context = main_ctx;
input_context->AddReference();
}
void disableInput()
{
if (input_context == nullptr) {
return;
}
input_context->RemoveReference();
input_context = nullptr;
}
} // namespace scpui
+65
View File
@@ -0,0 +1,65 @@
#pragma once
// Forward definition
namespace Rocket {
namespace Core {
class Context;
}
} // namespace Rocket
/**
* @brief Contains all functions and types related to the libRocket based UI system
*/
namespace scpui {
/**
* @brief Initialzes libRocket and the associated interfaces
*/
void initialize();
/**
* @brief Sets an offset by which the libRocket interface is moved by.
*
* @details This translates all drawing operations by the specified offset and transforms input events by the same
* offset to bring them back into the same frame of reference for libRocket.
*
* @param x The x-offset
* @param y The y-offset
*/
void setOffset(float x, float y);
/**
* @brief Reloads all libRocket contexts currently active
*
* This can be used to reload the active documents if the files contained documents.
*/
void reloadAllContexts();
/**
* @brief Enables mouse and text input on the specified input
*
* If there was another context with input enabled then input will be disabled for that context before enabling input
* on this context.
*
* @param main_ctx The context ot enable input for
*/
void enableInput(Rocket::Core::Context* main_ctx);
/**
* @brief Disable all input handling
*/
void disableInput();
/**
* @brief Shutdown the scripting part of libRocket
*
* This needs to be called before the scripting state is destroyed so that all resources are deallocated properly.
*/
void shutdown_scripting();
/**
* @brief Frees all resources of the UI system
*/
void shutdown();
} // namespace scpui
+20
View File
@@ -180,6 +180,26 @@ ADE_FUNC(playInterfaceSound, l_Audio, "Sound index", "Plays a sound from #Interf
}
}
ADE_FUNC(playInterfaceSoundByName, l_Audio, "string name",
"Plays a sound from #Interface Sounds in sounds.tbl by specifying the name of the sound entry. Sounds using "
"the retail sound syntax can be accessed by specifying the index number as a string.",
"boolean", "True if sound was played, false if not")
{
const char* name;
if (!ade_get_args(L, "s", &name))
return ade_set_error(L, "b", false);
auto gamesnd_idx = gamesnd_get_by_iface_name(name);
if (gamesnd_idx.isValid()) {
gamesnd_play_iface(gamesnd_idx);
return ade_set_args(L, "b", true);
} else {
LuaError(L, "Invalid sound name %s in playInterfaceSoundByName()", name);
return ADE_RETURN_FALSE;
}
}
ADE_FUNC(playMusic, l_Audio, "string Filename, [float volume = 1.0, bool looping = true]", "Plays a music file using FS2Open's builtin music system. Volume is currently ignored, uses players music volume setting. Files passed to this function are looped by default.", "number", "Audiohandle of the created audiostream, or -1 on failure")
{
char *s;
+44 -4
View File
@@ -3,10 +3,11 @@
#include <freespace.h>
#include <gamesequence/gamesequence.h>
#include <network/multi.h>
#include <playerman/player.h>
#include <parse/parselo.h>
#include <globalincs/version.h>
#include <network/multi.h>
#include <parse/parselo.h>
#include <pilotfile/pilotfile.h>
#include <playerman/player.h>
#include "base.h"
@@ -117,7 +118,37 @@ ADE_FUNC(getCurrentMPStatus, l_Base, "NIL", "Gets this computers current MP stat
ADE_FUNC(getCurrentPlayer, l_Base, NULL, "Gets a handle of the currently used player.<br><b>Note:</b> If there is no current player then the first player will be returned, check the game state to make sure you have a valid player handle.", "player", "Player handle")
{
return ade_set_args(L, "o", l_Player.Set(Player_num));
return ade_set_args(L, "o", l_Player.Set(player_h(Players[Player_num])));
}
ADE_FUNC(loadPlayer, l_Base, "string callsign", "Loads the player with the specified callsign.", "player",
"Player handle or invalid handle on load failure")
{
const char* callsign;
if (!ade_get_args(L, "s", &callsign)) {
return ade_set_error(L, "o", l_Player.Set(player_h()));
}
player plr;
plr.reset();
pilotfile loader;
if (!loader.load_player(callsign, &plr)) {
return ade_set_error(L, "o", l_Player.Set(player_h()));
}
return ade_set_args(L, "o", l_Player.Set(player_h(plr)));
}
ADE_FUNC(savePlayer, l_Base, "player plr", "Saves the specified player.", "boolean",
"true of successfull, false otherwise")
{
player_h* plh;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plh))) {
return ADE_RETURN_FALSE;
}
pilotfile loader;
return ade_set_args(L, "b", loader.save_player(plh->get()));
}
ADE_FUNC(setControlMode, l_Base, "NIL or enumeration LE_*_CONTROL", "Sets the current control mode for the game.", "string", "Current control mode")
@@ -304,6 +335,15 @@ ADE_FUNC(getCurrentLanguageExtension,
return ade_set_args(L, "s", Lcl_languages[Lcl_current_lang].lang_ext);
}
ADE_FUNC(getVersionString, l_Base, nullptr,
"Returns a string describing the version of the build that is currently running. This is mostly intended to "
"be displayed to the user and not processed by a script so don't rely on the exact format of the string.",
"string", "The version information")
{
auto str = gameversion::get_version_string();
return ade_set_args(L, "s", str.c_str());
}
//**********SUBLIBRARY: Base/Events
ADE_LIB_DERIV(l_Base_Events, "GameEvents", NULL, "Freespace 2 game events", l_Base);
+5 -46
View File
@@ -10,47 +10,6 @@
namespace scripting {
namespace api {
int l_cf_get_path_id(const char* n_path) {
size_t i;
size_t path_len = strlen(n_path);
char *buf = (char*) vm_malloc((path_len+1) * sizeof(char));
if (!buf)
return CF_TYPE_INVALID;
strcpy(buf, n_path);
//Remove trailing slashes; avoid buffer overflow on 1-char strings
i = path_len - 1;
while(i < std::numeric_limits<size_t>::max() && (buf[i] == '\\' || buf[i] == '/'))
buf[i--] = '\0';
//Remove leading slashes
i = 0;
while(i < path_len && (buf[i] == '\\' || buf[i] == '/'))
buf[i++] = '\0';
//Use official DIR_SEPARATOR_CHAR
for(i = 0; i < path_len; i++)
{
if(buf[i] == '\\' || buf[i] == '/')
buf[i] = DIR_SEPARATOR_CHAR;
}
for(i = 0; i < CF_MAX_PATH_TYPES; i++)
{
if(Pathtypes[i].path != NULL && !stricmp(buf, Pathtypes[i].path)) {
vm_free(buf);
buf = NULL;
return Pathtypes[i].index;
}
}
vm_free(buf);
buf = NULL;
return CF_TYPE_INVALID;
};
//**********LIBRARY: CFILE
//WMC - It's on my to-do list! (Well, if I had one anyway)
//WMC - Did it. I had to invent a to-do list first, though.
@@ -66,7 +25,7 @@ ADE_FUNC(deleteFile, l_CFile, "string Filename, string Path", "Deletes given fil
int path = CF_TYPE_INVALID;
if(n_path != NULL && strlen(n_path))
path = l_cf_get_path_id(n_path);
path = cfile_get_path_type(n_path);
if(path == CF_TYPE_INVALID)
return ade_set_error(L, "b", false);
@@ -84,7 +43,7 @@ ADE_FUNC(fileExists, l_CFile, "string Filename, [string Path = \"\", boolean Che
int path = CF_TYPE_ANY;
if(n_path != NULL && strlen(n_path))
path = l_cf_get_path_id(n_path);
path = cfile_get_path_type(n_path);
if(path == CF_TYPE_INVALID)
return ade_set_error(L, "b", false);
@@ -125,7 +84,7 @@ ADE_FUNC(listFiles, l_CFile, "string directory, string filter",
ext = filter_str.substr(dot_pos);
}
auto path_type = l_cf_get_path_id(dir);
auto path_type = cfile_get_path_type(dir);
if (path_type == CF_TYPE_INVALID) {
LuaError(L, "The directory \"%s\" is not valid!", dir);
return ADE_RETURN_NIL;
@@ -159,7 +118,7 @@ ADE_FUNC(openFile, l_CFile, "string Filename, [string Mode=\"r\", string Path =
int path = CF_TYPE_ANY;
if(n_path != NULL && strlen(n_path))
path = l_cf_get_path_id(n_path);
path = cfile_get_path_type(n_path);
if(path == CF_TYPE_INVALID)
return ade_set_error(L, "o", l_File.Set(NULL));
@@ -187,7 +146,7 @@ ADE_FUNC(renameFile, l_CFile, "string CurrentFilename, string NewFilename, strin
int path = CF_TYPE_INVALID;
if(n_path != NULL && strlen(n_path))
path = l_cf_get_path_id(n_path);
path = cfile_get_path_type(n_path);
if(path == CF_TYPE_INVALID)
return ade_set_error(L, "b", false);
-2
View File
@@ -7,8 +7,6 @@ namespace api {
DECLARE_ADE_LIB(l_CFile);
int l_cf_get_path_id(const char* n_path);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ ADE_FUNC(readFileText, l_Parsing, "string file, string directory = <any>",
int type = CF_TYPE_ANY;
if (dir != nullptr) {
type = l_cf_get_path_id(dir);
type = cfile_get_path_type(dir);
}
if (type == CF_TYPE_INVALID) {
+256
View File
@@ -0,0 +1,256 @@
//
//
#include "ui.h"
#include "cmdline/cmdline.h"
#include "menuui/barracks.h"
#include "menuui/mainhallmenu.h"
#include "menuui/playermenu.h"
#include "playerman/managepilot.h"
#include "scpui/SoundPlugin.h"
#include "scpui/rocket_ui.h"
#include "scripting/api/objs/player.h"
#include "scripting/lua/LuaTable.h"
// Our Assert conflicts with the definitions inside libRocket
#pragma push_macro("Assert")
#undef Assert
#include <Rocket/Core/Lua/LuaType.h>
#pragma pop_macro("Assert")
namespace scripting {
namespace api {
//*************************Testing stuff*************************
// This section is for stuff that's considered experimental.
ADE_LIB(l_UserInterface, "UserInterface", "ui", "Functions for managing the \"scpui\" user interface system.");
ADE_FUNC(setOffset, l_UserInterface, "number x, number y",
"Sets the offset from the top left corner at which <b>all</b> rocket contexts will be rendered", "boolean",
"true if the operation was successful, false otherwise")
{
float x;
float y;
if (!ade_get_args(L, "ff", &x, &y)) {
return ADE_RETURN_FALSE;
}
scpui::setOffset(x, y);
return ADE_RETURN_TRUE;
}
ADE_FUNC(enableInput, l_UserInterface, "Rocket::Context context", "Enables input for the specified libRocket context",
"boolean", "true if successfull")
{
using namespace Rocket::Core;
// Check parameter number
if (!ade_get_args(L, "*")) {
return ADE_RETURN_FALSE;
}
auto ctx = Lua::LuaType<Context>::check(L, 1);
if (ctx == nullptr) {
LuaError(L, "Parameter 1 is not a valid context handle!");
return ADE_RETURN_FALSE;
}
scpui::enableInput(ctx);
return ADE_RETURN_TRUE;
}
ADE_FUNC(disableInput, l_UserInterface, "", "Disables UI input", "boolean", "true if successfull")
{
scpui::disableInput();
return ADE_RETURN_TRUE;
}
ADE_FUNC(playElementSound, l_UserInterface,
"Rocket::Element element, string event, string state = "
"",
"Plays an element specific sound with an optional state for differentiating different UI states.", "boolean",
"true if a sound was played, false otherwise")
{
using namespace Rocket::Core;
const char* event;
const char* state = "";
if (!ade_get_args(L, "*s|s", &event, &state)) {
return ADE_RETURN_FALSE;
}
auto el = Lua::LuaType<Element>::check(L, 1);
if (el == nullptr) {
return ADE_RETURN_FALSE;
}
return ade_set_args(L, "b", scpui::SoundPlugin::instance()->PlayElementSound(el, event, state));
}
//**********SUBLIBRARY: UserInterface/PilotSelect
ADE_LIB_DERIV(l_UserInterface_PilotSelect, "PilotSelect", nullptr,
"API for accessing values specific to the pilot select screen.<br><b>Warning:</b> This is an internal "
"API for the new UI system. This should not be used by other code and may be removed in the future!",
l_UserInterface);
ADE_VIRTVAR(MAX_PILOTS, l_UserInterface_PilotSelect, nullptr, "Gets the maximum number of possible pilots.", "number",
"The maximum number of pilots")
{
return ade_set_args(L, "i", MAX_PILOTS);
}
ADE_VIRTVAR(WarningCount, l_UserInterface_PilotSelect, nullptr, "The amount of warnings caused by the mod while loading.", "number",
"The maximum number of pilots")
{
return ade_set_args(L, "i", Global_warning_count);
}
ADE_VIRTVAR(ErrorCount, l_UserInterface_PilotSelect, nullptr, "The amount of errors caused by the mod while loading.", "number",
"The maximum number of pilots")
{
return ade_set_args(L, "i", Global_error_count);
}
ADE_FUNC(enumeratePilots, l_UserInterface_PilotSelect, nullptr,
"Lists all pilots available for the pilot selection<br>", "table{ name... }",
"A table containing the pilots (without a file extension) or nil on error")
{
using namespace luacpp;
auto table = LuaTable::create(L);
auto pilots = player_select_enumerate_pilots();
for (size_t i = 0; i < pilots.size(); ++i) {
table.addValue(i + 1, pilots[i]);
}
return ade_set_args(L, "t", &table);
}
ADE_FUNC(getLastPilot, l_UserInterface_PilotSelect, nullptr,
"Reads the last active pilot from the config file and returns some information about it. callsign is the name "
"of the player and is_multi indicates whether the pilot was last active as a multiplayer pilot.",
"string callsign", "The pilot name or nil if there was no last pilot")
{
using namespace luacpp;
auto callsign = player_get_last_player();
if (callsign.empty()) {
return ADE_RETURN_NIL;
}
return ade_set_args(L, "s", callsign.c_str());
}
ADE_FUNC(checkPilotLanguage, l_UserInterface_PilotSelect, "string callsign",
"Checks if the pilot with the specified callsign has the right language.", "boolean",
"true if pilot is valid, false otherwise")
{
const char* callsign;
if (!ade_get_args(L, "s", &callsign)) {
return ADE_RETURN_FALSE;
}
return ade_set_args(L, "b", valid_pilot_lang(callsign));
}
ADE_FUNC(selectPilot, l_UserInterface_PilotSelect, "string callsign, boolean is_multi",
"Selects the pilot with the specified callsign and advances the game to the main menu.", "nil", "nothing")
{
const char* callsign;
bool is_multi;
if (!ade_get_args(L, "sb", &callsign, &is_multi)) {
return ADE_RETURN_NIL;
}
player_finish_select(callsign, is_multi);
return ADE_RETURN_NIL;
}
ADE_FUNC(deletePilot, l_UserInterface_PilotSelect, "string callsign",
"Deletes the pilot with the specified callsign. This is not reversible!", "boolean",
"true on success, false otherwise")
{
const char* callsign;
if (!ade_get_args(L, "s", &callsign)) {
return ADE_RETURN_NIL;
}
return ade_set_args(L, "b", delete_pilot_file(callsign));
}
ADE_FUNC(
createPilot, l_UserInterface_PilotSelect, "string callsign, boolean is_multi[, string copy_from]",
"Creates a new pilot in either single or multiplayer mode and optionally copies settings from an existing pilot.",
"boolean", "true on success, false otherwise")
{
const char* callsign;
bool is_multi;
const char* copy_from = nullptr;
if (!ade_get_args(L, "sb|s", &callsign, &is_multi, &copy_from)) {
return ADE_RETURN_NIL;
}
return ade_set_args(L, "b", player_create_new_pilot(callsign, is_multi, copy_from));
}
ADE_FUNC(isAutoselect, l_UserInterface_PilotSelect, nullptr,
"Determines if the pilot selection screen should automatically select the default user.", "boolean",
"true if autoselect is enabled, false otherwise")
{
return ade_set_args(L, "b", Cmdline_benchmark_mode);
}
//**********SUBLIBRARY: UserInterface/MainHall
ADE_LIB_DERIV(l_UserInterface_MainHall, "MainHall", nullptr,
"API for accessing values specific to the main hall screen.<br><b>Warning:</b> This is an internal "
"API for the new UI system. This should not be used by other code and may be removed in the future!",
l_UserInterface);
ADE_FUNC(startAmbientSound, l_UserInterface_MainHall, nullptr, "Starts the ambient mainhall sound.", "nil", "nothing")
{
(void)L;
main_hall_start_ambient();
return ADE_RETURN_NIL;
}
//**********SUBLIBRARY: UserInterface/Barracks
ADE_LIB_DERIV(l_UserInterface_Barracks, "Barracks", nullptr,
"API for accessing values specific to the barracks screen.<br><b>Warning:</b> This is an internal "
"API for the new UI system. This should not be used by other code and may be removed in the future!",
l_UserInterface);
ADE_FUNC(listPilotImages, l_UserInterface_Barracks, nullptr, "Lists the names of the available pilot images.",
"table {string...}", "The list of pilot filenames or nil on error")
{
pilot_load_pic_list();
using namespace luacpp;
LuaTable out = LuaTable::create(L);
for (auto i = 0; i < Num_pilot_images; ++i) {
out.addValue(i + 1, Pilot_image_names[i]);
}
return ade_set_args(L, "t", &out);
}
ADE_FUNC(acceptPilot, l_UserInterface_Barracks, "player selection", "Accept the given player as the current player",
"boolean", "true on sucess, false otherwise")
{
player_h* plh;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plh))) {
return ADE_RETURN_FALSE;
}
barracks_accept_pilot(plh->get());
return ADE_RETURN_TRUE;
}
} // namespace api
} // namespace scripting
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "scripting/ade_api.h"
namespace scripting {
namespace api {
DECLARE_ADE_LIB(l_UserInterface);
}
} // namespace scripting
+126 -33
View File
@@ -3,24 +3,104 @@
#include "player.h"
#include "playerman/player.h"
#include "mission/missioncampaign.h"
#include "menuui/mainhallmenu.h"
#include "mission/missioncampaign.h"
#include "pilotfile/pilotfile.h"
#include "playerman/player.h"
namespace scripting {
namespace api {
player_h::player_h() = default;
player_h::player_h(const player& plr)
{
_plr = new player();
_plr->assign(&plr);
}
bool player_h::isValid() const { return _plr != nullptr; }
player* player_h::get() { return _plr; }
void player_h::cleanup()
{
delete _plr;
_plr = nullptr;
}
//**********HANDLE: Player
ADE_OBJ(l_Player, int, "player", "Player handle");
ADE_OBJ(l_Player, player_h, "player", "Player handle");
ADE_FUNC(__gc, l_Player, nullptr, "Deletes the underlying resources", "nothing", "nothing") {
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ADE_RETURN_NIL;
if (!plr->isValid())
return ADE_RETURN_NIL;
plr->cleanup();
return ADE_RETURN_NIL;
}
ADE_VIRTVAR(ImageFilename, l_Player, "string name", "The image filename of this pilot", "string",
"Player image filename, or empty string if handle is invalid")
{
player_h* plr;
const char* filename = nullptr;
if (!ade_get_args(L, "o|s", l_Player.GetPtr(&plr), &filename))
return ade_set_error(L, "s", "");
if (!plr->isValid())
return ade_set_error(L, "s", "");
if (ADE_SETTING_VAR && filename != nullptr) {
strcpy_s(plr->get()->image_filename, filename);
}
return ade_set_args(L, "s", plr->get()->image_filename);
}
ADE_VIRTVAR(IsMultiplayer, l_Player, "boolean value", "Determines if this player is currently configured for multiplayer.", "boolean", "true if this is a multiplayer pilot, false otherwise or if the handle is invalid") {
player_h* plr;
bool value = false;
if (!ade_get_args(L, "o|b", l_Player.GetPtr(&plr), &value))
return ADE_RETURN_FALSE;
if (!plr->isValid())
return ADE_RETURN_FALSE;
if (ADE_SETTING_VAR) {
if (value) {
plr->get()->flags |= PLAYER_FLAGS_IS_MULTI;
} else {
plr->get()->flags &= ~PLAYER_FLAGS_IS_MULTI;
}
}
return ade_set_args(L, "b", (plr->get()->flags & PLAYER_FLAGS_IS_MULTI) != 0);
}
ADE_VIRTVAR(WasMultiplayer, l_Player, "boolean value", "Determines if this player is currently configured for multiplayer.", "boolean", "true if this is a multiplayer pilot, false otherwise or if the handle is invalid") {
player_h* plr;
bool value = false;
if (!ade_get_args(L, "o|b", l_Player.GetPtr(&plr), &value))
return ADE_RETURN_FALSE;
if (!plr->isValid())
return ADE_RETURN_FALSE;
if (ADE_SETTING_VAR) {
plr->get()->player_was_multi = value ? 1 : 0;
}
return ade_set_args(L, "b", plr->get()->player_was_multi != 0);
}
ADE_FUNC(isValid, l_Player, NULL, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ADE_RETURN_NIL;
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ADE_RETURN_FALSE;
return ADE_RETURN_TRUE;
@@ -28,38 +108,38 @@ ADE_FUNC(isValid, l_Player, NULL, "Detects whether handle is valid", "boolean",
ADE_FUNC(getName, l_Player, NULL, "Gets current player name", "string", "Player name, or empty string if handle is invalid")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ade_set_error(L, "s", "");
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", Players[idx].callsign);
return ade_set_args(L, "s", plr->get()->callsign);
}
ADE_FUNC(getCampaignFilename, l_Player, NULL, "Gets current player campaign filename", "string", "Campaign name, or empty string if handle is invalid")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ade_set_error(L, "s", "");
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", Players[idx].current_campaign);
return ade_set_args(L, "s", plr->get()->current_campaign);
}
ADE_FUNC(getImageFilename, l_Player, NULL, "Gets current player image filename", "string", "Player image filename, or empty string if handle is invalid")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ade_set_error(L, "s", "");
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", Players[idx].image_filename);
return ade_set_args(L, "s", plr->get()->image_filename);
}
ADE_FUNC(getMainHallName, l_Player, NULL, "Gets player's current main hall name", "string", "Main hall name, or name of first mainhall in campaign if something goes wrong")
@@ -91,39 +171,52 @@ ADE_FUNC(getMainHallIndex, l_Player, NULL, "Gets player's current main hall numb
ADE_FUNC(getSquadronName, l_Player, NULL, "Gets current player squad name", "string", "Squadron name, or empty string if handle is invalid")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ade_set_error(L, "s", "");
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", Players[idx].s_squad_name);
return ade_set_args(L, "s", plr->get()->s_squad_name);
}
ADE_FUNC(getMultiSquadronName, l_Player, NULL, "Gets current player multi squad name", "string", "Squadron name, or empty string if handle is invalid")
{
int idx;
if(!ade_get_args(L, "o", l_Player.Get(&idx)))
player_h* plr;
if (!ade_get_args(L, "o", l_Player.GetPtr(&plr)))
return ade_set_error(L, "s", "");
if (idx < 0 || idx >= MAX_PLAYERS)
if (!plr->isValid())
return ade_set_error(L, "s", "");
return ade_set_args(L, "s", Players[idx].m_squad_name);
return ade_set_args(L, "s", plr->get()->m_squad_name);
}
//WMC - This isn't working
/*
ADE_FUNC(getSquadronImage, l_Player, NULL, "Squad image (string)", "Gets current player squad image")
ADE_FUNC(loadCampaignSavefile, l_Player, "string campaign = <current>", "Loads the specified campaign save file.",
"boolean", "true on success, false otherwise")
{
int idx;
player_helper(L, &idx);
player_h* plh;
const char* savefile = nullptr;
if (!ade_get_args(L, "o|s", l_Player.GetPtr(&plh), &savefile)) {
return ADE_RETURN_FALSE;
}
return ade_set_args(L, "s", Players[idx].squad_filename);
}*/
if (!plh->isValid()) {
return ADE_RETURN_FALSE;
}
if (savefile == nullptr) {
savefile = plh->get()->current_campaign;
}
pilotfile loader;
if (!loader.load_savefile(plh->get(), savefile)) {
return ADE_RETURN_FALSE;
}
return ADE_RETURN_TRUE;
}
}
}
+15 -1
View File
@@ -1,11 +1,25 @@
#pragma once
#include "playerman/player.h"
#include "scripting/ade_api.h"
namespace scripting {
namespace api {
DECLARE_ADE_OBJ(l_Player, int);
class player_h {
player *_plr = nullptr;
public:
player_h();
player_h(const player& plr);
bool isValid() const;
player* get();
void cleanup();
};
DECLARE_ADE_OBJ(l_Player, player_h);
}
}
+1
View File
@@ -64,6 +64,7 @@
#include "scripting/api/libs/tables.h"
#include "scripting/api/libs/testing.h"
#include "scripting/api/libs/time_lib.h"
#include "scripting/api/libs/ui.h"
#include "scripting/api/libs/utf8.h"
// End of definitions includes
+5 -3
View File
@@ -176,9 +176,6 @@ private:
//Internal Lua helper functions
void EndLuaFrame();
//Destroy everything
void Clear();
public:
//***Init/Deinit
script_state(const char *name);
@@ -188,6 +185,11 @@ public:
~script_state();
/**
* @brief Resets the lua state and frees all resources
*/
void Clear();
//***Internal scripting stuff
int LoadBm(char *name);
void UnloadImages();
+24
View File
@@ -192,6 +192,8 @@ add_file_folder("Default files\\\\data\\\\effects"
def_files/data/effects/passthrough-v.sdr
def_files/data/effects/post-f.sdr
def_files/data/effects/post-v.sdr
def_files/data/effects/rocketui-f.sdr
def_files/data/effects/rocketui-v.sdr
def_files/data/effects/shadowdebug-f.sdr
def_files/data/effects/shadowdebug-v.sdr
def_files/data/effects/shadows.sdr
@@ -989,6 +991,24 @@ add_file_folder("Render"
render/batching.h
)
add_file_folder("ScpUi"
scpui/rocket_ui.cpp
scpui/rocket_ui.h
scpui/RocketFileInterface.cpp
scpui/RocketFileInterface.h
scpui/RocketRenderingInterface.cpp
scpui/RocketRenderingInterface.h
scpui/RocketSystemInterface.cpp
scpui/RocketSystemInterface.h
scpui/SoundPlugin.cpp
scpui/SoundPlugin.h
)
add_file_folder("ScpUi\\\\Elements"
scpui/elements/AnimationElement.cpp
scpui/elements/AnimationElement.h
)
add_file_folder("Scripting"
scripting/ade.cpp
scripting/ade.h
@@ -1032,6 +1052,8 @@ add_file_folder("Scripting\\\\Api\\\\Libs"
scripting/api/libs/time_lib.h
scripting/api/libs/utf8.cpp
scripting/api/libs/utf8.h
scripting/api/libs/ui.cpp
scripting/api/libs/ui.h
)
add_file_folder("Scripting\\\\Api\\\\Objs"
@@ -1278,6 +1300,8 @@ add_file_folder("Utils"
utils/HeapAllocator.h
utils/id.h
utils/RandomRange.h
utils/string_utils.cpp
utils/string_utils.h
utils/strings.h
utils/unicode.cpp
utils/unicode.h
+15
View File
@@ -0,0 +1,15 @@
//
//
#include "string_utils.h"
namespace util {
std::vector<std::string> split_string(const std::string& s, char delim) {
std::vector<std::string> elems;
split_string(s, delim, std::back_inserter(elems));
return elems;
}
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include "globalincs/pstypes.h"
namespace util {
template <typename Out>
void split_string(const SCP_string& s, char delim, Out result)
{
SCP_stringstream ss(s);
SCP_string item;
while (std::getline(ss, item, delim)) {
if (!item.empty()) {
*(result++) = item;
}
}
}
std::vector<std::string> split_string(const std::string& s, char delim);
} // namespace util
+1 -1
View File
@@ -81,7 +81,7 @@ int filelength(int fd);
int _chdir(const char *path);
int _getcwd(char *buffer, unsigned int len);
int _mkdir(const char *path);
void _splitpath(char *path, char *drive, char *dir, char *fname, char *ext);
void _splitpath(const char *path, char *drive, char *dir, char *fname, char *ext);
// other stuff
#define _hypot(x, y) hypot(x, y)
+1 -1
View File
@@ -187,7 +187,7 @@ int _mkdir(const char *path)
return mkdir(path, 0777);
}
void _splitpath (char *path, char * /*drive*/, char *dir, char *fname, char *ext)
void _splitpath (const char *path, char * /*drive*/, char *dir, char *fname, char *ext)
{
if ( (path == NULL) || (fname == NULL) )
return;
-2
View File
@@ -193,11 +193,9 @@ int game_get_default_skill_level(){return 0;}
float FreeSpace_gamma;
int set_cdrom_path(int){return 0;}
int find_freespace_cd(char*){return 0;}
void get_version_string(){}
void game_do_state_common(int, int){}
void game_set_frametime(int){}
void game_increase_skill_level(){}
void get_version_string(char*, int){}
int Show_target_weapons;
int Show_target_debug_info;
int Game_do_state_should_skip;
+18 -22
View File
@@ -152,6 +152,7 @@
#include "radar/radarsetup.h"
#include "render/3d.h"
#include "render/batching.h"
#include "scpui/rocket_ui.h"
#include "scripting/scripting.h"
#include "ship/afterburner.h"
#include "ship/awacs.h"
@@ -2018,6 +2019,9 @@ void game_init()
Viewer_mode = 0;
Game_paused = 0;
// Do this before the initial scripting hook runs in case that hook does something with the UI
scpui::initialize();
Script_system.RunCondition(CHA_GAMEINIT);
game_title_screen_close();
@@ -6193,6 +6197,9 @@ void game_do_state(int state)
if(Script_system.IsConditionOverride(CHA_ONFRAME)) {
game_set_frametime(state);
game_check_key();
gr_clear();
gr_flip(); //Does state hook automagically
return;
@@ -6859,7 +6866,17 @@ void game_shutdown(void)
gr_flip();
}
// if the player has left the "player select" screen and quit the game without actually choosing
// Free the scripting resources of the new UI first
scpui::shutdown_scripting();
// Everything after this should be done without scripting so we can free those resources here
Script_system.Clear();
// Deinitialize the new UI system, needs to be done after scripting shutdown to make sure the resources were
// released properly
scpui::shutdown();
// if the player has left the "player select" screen and quit the game without actually choosing
// a player, Player will be NULL, in which case we shouldn't write the player file out!
if (!(Game_mode & GM_STANDALONE_SERVER) && (Player!=NULL) && !Is_standalone){
Pilot.save_player();
@@ -7422,27 +7439,6 @@ void game_format_time(fix m_time,char *time_str)
strcat(time_str,tmp);
}
// Stuff version string in *str.
void get_version_string(char *str, int max_size)
{
//XSTR:OFF
Assert( max_size > 6 );
sprintf(str, "FreeSpace 2 Open v%s", FS_VERSION_FULL);
#ifndef NDEBUG
strcat_s( str, max_size, " Debug" );
#endif
// Lets get some more info in here
switch(gr_screen.mode)
{
case GR_OPENGL:
strcat_s( str, max_size, " OpenGL" );
break;
}
}
// ----------------------------------------------------------------
//
// Subspace Ambient Sound START
-3
View File
@@ -200,9 +200,6 @@ int set_cdrom_path(int drive_num);
// Used to tell the player that a feature is disabled by build settings
void game_feature_disabled_popup();
// Return version string for build.
void get_version_string(char *str, int max_size);
// format the specified time (fixed point) into a nice string
void game_format_time(fix m_time,char *time_str);
+4
View File
@@ -25,6 +25,10 @@ add_subdirectory(md5)
add_subdirectory(utfcpp)
include(freetype.cmake)
include(FFmpeg.cmake)
add_subdirectory(discord)
include(libRocket.cmake)
+23
View File
@@ -0,0 +1,23 @@
add_library(freetype INTERFACE)
if(PLATFORM_WINDOWS)
# We use prebuilt binaries for windows
get_prebuilt_path(PREBUILT_PATH)
set(FRRETYPE_ROOT_DIR "${PREBUILT_PATH}/freetype")
set(SEARCH_PATH "${FRRETYPE_ROOT_DIR}/lib")
set(dll_name "${SEARCH_PATH}/freetype281.dll")
target_include_directories(freetype INTERFACE "${FRRETYPE_ROOT_DIR}/include")
target_link_libraries(freetype INTERFACE "${SEARCH_PATH}/freetype281.lib")
add_target_copy_files("${dll_name}")
else()
find_package(Freetype REQUIRED)
target_include_directories(freetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(freetype INTERFACE ${FREETYPE_LIBRARIES})
endif()
Submodule
+1
Submodule lib/libRocket added at 55c1ba91bc.
+115
View File
@@ -0,0 +1,115 @@
#===================================
# Build script for libRocket =======
#===================================
set(LIBROCKET_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libRocket")
if(COMMAND cmake_policy)
cmake_policy(SET CMP0015 NEW)
endif(COMMAND cmake_policy)
# Search in the 'cmake' directory for additional CMake modules.
list(APPEND CMAKE_MODULE_PATH ${LIBROCKET_DIR}/Build/cmake)
set(PROJECT_SOURCE_DIR "${LIBROCKET_DIR}")
#===================================
# Environment tests ================
#===================================
include(TestForANSIForScope)
include(TestForANSIStreamHeaders)
include(TestForSTDNamespace)
#===================================
# Plaform specific global hacks ====
#===================================
if(APPLE)
# Disables naked builtins from AssertMacros.h which
# This prevents naming collisions such as those from the check()
# function macro with LuaType::check
add_definitions(-D__ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES=0)
endif(APPLE)
#===================================
# Find dependencies ================
#===================================
# FreeType
#find_package(Freetype REQUIRED)7
#===================================
# Setup paths ======================
#===================================
# Include list of source files
include(FileList)
#===================================
# Build libraries ==================
#===================================
set(LIBRARIES Core Controls Debugger)
foreach(library ${LIBRARIES})
set(NAME Rocket${library})
add_library(${NAME}
${${library}_HDR_FILES}
${${library}_PUB_HDR_FILES}
${MASTER_${library}_PUB_HDR_FILES}
${${library}_SRC_FILES}
)
target_include_directories(${NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Include")
target_link_libraries(${NAME} PUBLIC compiler)
suppress_warnings(${NAME})
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(${NAME} PUBLIC ROCKET_STATIC_LIB)
target_compile_definitions(${NAME} PRIVATE ROCKET_VERSION="FSO")
endif()
set_target_properties(${NAME}
PROPERTIES
FOLDER "3rdparty/libRocket"
)
endforeach(library)
# Build Lua bindings
set(LIBRARIES Core Controls)
foreach(library ${LIBRARIES})
set(NAME Rocket${library}Lua)
add_library(${NAME} ${Lua${library}_SRC_FILES}
${Lua${library}_HDR_FILES}
${Lua${library}_PUB_HDR_FILES}
)
target_include_directories(${NAME} PUBLIC "${PROJECT_SOURCE_DIR}/Include")
target_link_libraries(${NAME} PUBLIC compiler)
suppress_warnings(${NAME})
if(NOT BUILD_SHARED_LIBS)
target_compile_definitions(${NAME} PUBLIC ROCKET_STATIC_LIB)
target_compile_definitions(${NAME} PRIVATE ROCKET_VERSION="FSO")
endif()
set_target_properties(${NAME}
PROPERTIES
FOLDER "3rdparty/libRocket/Lua"
)
endforeach(library)
#===================================
# Link libraries ===================
#===================================
target_link_libraries(RocketCore PUBLIC freetype)
target_link_libraries(RocketControls PUBLIC RocketCore)
target_link_libraries(RocketDebugger PUBLIC RocketCore)
target_link_libraries(RocketCoreLua PUBLIC RocketCore ${LUA_LIBS})
target_link_libraries(RocketControlsLua PUBLIC RocketControls RocketCoreLua ${LUA_LIBS})
add_library(libRocket INTERFACE)
target_link_libraries(libRocket INTERFACE RocketCore RocketControls RocketDebugger RocketCoreLua RocketControlsLua)
+1 -1
View File
@@ -1,5 +1,5 @@
set(PREBUILT_VERSION_NAME "4be6c7e36620a3cdec1fc0df77753ae9320a3e2f")
set(PREBUILT_VERSION_NAME "4e87d4e7bef7c29316a0a4f9e33b1442b8edbee7")
set(FSO_PREBUILT_OVERRIDE "" CACHE PATH "Path to the prebuilt binaries, if empty the binaries will be downloaded.")
set(PREBUILT_LIB_DIR "${CMAKE_CURRENT_BINARY_DIR}/prebuilt")
-3
View File
@@ -197,11 +197,9 @@ int game_get_default_skill_level(){return 0;}
float FreeSpace_gamma;
int set_cdrom_path(int){return 0;}
int find_freespace_cd(char*){return 0;}
void get_version_string(){}
void game_do_state_common(int, int){}
void game_set_frametime(int){}
void game_increase_skill_level(){}
void get_version_string(char*, int){}
int Show_target_weapons;
int Show_target_debug_info;
int Game_do_state_should_skip;
@@ -214,7 +212,6 @@ void game_shudder_apply(int, float){}
int game_hacked_data(){return 0;}
int game_single_step;
int last_single_step;
void get_version_string_short(char *){}
void game_tst_mark(class object *, class ship *){}
int tst;
//int Player_multi_died_check;
+29 -4
View File
@@ -114,16 +114,41 @@ TEST_F(CFileTest, override_default_file) {
cfclose(fp);
}
TEST(CFileStandalone, test_check_location_flags)
{
TEST(CFileStandalone, test_check_location_flags) {
ASSERT_FALSE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT, CF_LOCATION_ROOT_USER));
ASSERT_TRUE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT, CF_LOCATION_ROOT_GAME));
ASSERT_TRUE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT, CF_LOCATION_TYPE_ROOT));
ASSERT_FALSE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT, CF_LOCATION_TYPE_PRIMARY_MOD));
ASSERT_FALSE(
cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT, CF_LOCATION_TYPE_SECONDARY_MODS));
ASSERT_FALSE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT,
CF_LOCATION_TYPE_SECONDARY_MODS));
ASSERT_FALSE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT,
CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_PRIMARY_MOD));
ASSERT_TRUE(cf_check_location_flags(CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT,
CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT));
}
TEST_F(CFileTest, test_get_path_type)
{
SCP_string cfile_dir(TEST_DATA_PATH);
cfile_dir += DIR_SEPARATOR_CHAR;
cfile_dir += "test"; // Cfile expects something after the path
ASSERT_FALSE(cfile_init(cfile_dir.c_str()));
// Test simple case
ASSERT_EQ(CF_TYPE_DATA, cfile_get_path_type("data"));
ASSERT_EQ(CF_TYPE_DATA, cfile_get_path_type("///data"));
ASSERT_EQ(CF_TYPE_DATA, cfile_get_path_type("data\\\\\\"));
ASSERT_EQ(CF_TYPE_DATA, cfile_get_path_type("////data\\\\\\"));
// Test single subdirectory case
ASSERT_EQ(CF_TYPE_INTERFACE, cfile_get_path_type("data/interface"));
ASSERT_EQ(CF_TYPE_INTERFACE, cfile_get_path_type("///data/interface"));
ASSERT_EQ(CF_TYPE_INTERFACE, cfile_get_path_type("data/interface\\\\\\"));
ASSERT_EQ(CF_TYPE_INTERFACE, cfile_get_path_type("////data/interface\\\\\\"));
// Test nested subdirectory case
ASSERT_EQ(CF_TYPE_INTERFACE_MARKUP, cfile_get_path_type("data/interface/markup"));
ASSERT_EQ(CF_TYPE_INTERFACE_MARKUP, cfile_get_path_type("///data/interface/markup"));
ASSERT_EQ(CF_TYPE_INTERFACE_MARKUP, cfile_get_path_type("data/interface/markup\\\\\\"));
ASSERT_EQ(CF_TYPE_INTERFACE_MARKUP, cfile_get_path_type("////data/interface/markup\\\\\\"));
}
-2
View File
@@ -201,11 +201,9 @@ int game_get_default_skill_level(){return 0;}
float FreeSpace_gamma;
int set_cdrom_path(int){return 0;}
int find_freespace_cd(char*){return 0;}
void get_version_string(){}
void game_do_state_common(int, int){}
void game_set_frametime(int){}
void game_increase_skill_level(){}
void get_version_string(char*, int){}
int Show_target_weapons;
int Show_target_debug_info;
int Game_do_state_should_skip;