mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 04:31:34 -10:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e6eb25492 | ||
|
|
9de5d8d011 | ||
|
|
62d178d896 | ||
|
|
f8b210b659 | ||
|
|
d3e6ee81e1 | ||
|
|
66f95312ae | ||
|
|
ead93f3175 | ||
|
|
48b1e3f289 |
No files matched your search
@@ -73,6 +73,11 @@ struct SubtitleFrame {
|
||||
};
|
||||
typedef std::unique_ptr<SubtitleFrame> SubtitleFramePtr;
|
||||
|
||||
struct PlaybackProperties {
|
||||
bool with_audio = true;
|
||||
bool looping = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract class for decoding a video or audio stream
|
||||
*
|
||||
@@ -106,7 +111,7 @@ class Decoder {
|
||||
* @param fileName The name of the file that should be opened
|
||||
* @return @c true if the initialization was successfull, @c false otherwise
|
||||
*/
|
||||
virtual bool initialize(const SCP_string& fileName) = 0;
|
||||
virtual bool initialize(const SCP_string& fileName, const PlaybackProperties& properties) = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the properties of the video
|
||||
|
||||
@@ -223,5 +223,8 @@ void AudioDecoder::finishDecoding() {
|
||||
// Push the last bits of audio data into the queue
|
||||
flushAudioBuffer();
|
||||
}
|
||||
void AudioDecoder::flushBuffers() {
|
||||
avcodec_flush_buffers(m_status->audioCodecCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ class AudioDecoder: public FFMPEGStreamDecoder<AudioFrame> {
|
||||
void decodePacket(AVPacket* packet) override;
|
||||
|
||||
void finishDecoding() override;
|
||||
|
||||
void flushBuffers() override;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ class AVPacketScope
|
||||
: _packet(av_packet) {
|
||||
}
|
||||
|
||||
AVPacketScope(const AVPacketScope&) = delete;
|
||||
AVPacketScope& operator=(const AVPacketScope&) = delete;
|
||||
|
||||
~AVPacketScope() {
|
||||
av_packet_unref(_packet);
|
||||
}
|
||||
@@ -214,8 +217,14 @@ std::unique_ptr<InputStream> openStream(const SCP_string& name) {
|
||||
}
|
||||
|
||||
std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& stream,
|
||||
std::unique_ptr<InputStream>& subt)
|
||||
std::unique_ptr<InputStream>& subt,
|
||||
const PlaybackProperties& properties)
|
||||
{
|
||||
if (subt && properties.looping) {
|
||||
mprintf(("FFmpeg: External subtitles and looping movies are not supported!\n"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<DecoderStatus> status(new DecoderStatus());
|
||||
|
||||
auto ctx = stream->m_ctx->ctx();
|
||||
@@ -233,14 +242,18 @@ std::unique_ptr<DecoderStatus> initializeStatus(std::unique_ptr<InputStream>& st
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, &status->audioCodec, 0);
|
||||
if (audioStream < 0) {
|
||||
if (audioStream == AVERROR_STREAM_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: No audio stream found in file!\n"));
|
||||
} else if (audioStream == AVERROR_DECODER_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: Codec for audio stream could not be found!\n"));
|
||||
} else {
|
||||
mprintf(("FFmpeg: Unknown error while finding audio stream!\n"));
|
||||
int audioStream = -1;
|
||||
|
||||
if (properties.with_audio) {
|
||||
audioStream = av_find_best_stream(ctx, AVMEDIA_TYPE_AUDIO, -1, videoStream, &status->audioCodec, 0);
|
||||
if (audioStream < 0) {
|
||||
if (audioStream == AVERROR_STREAM_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: No audio stream found in file!\n"));
|
||||
} else if (audioStream == AVERROR_DECODER_NOT_FOUND) {
|
||||
mprintf(("FFmpeg: Codec for audio stream could not be found!\n"));
|
||||
} else {
|
||||
mprintf(("FFmpeg: Unknown error while finding audio stream!\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +444,8 @@ std::unique_ptr<InputStream> openSubtitleStream(const SCP_string& name)
|
||||
}
|
||||
}
|
||||
|
||||
bool FFMPEGDecoder::initialize(const SCP_string& fileName) {
|
||||
bool FFMPEGDecoder::initialize(const SCP_string& fileName, const PlaybackProperties& properties)
|
||||
{
|
||||
SCP_string movieName = fileName;
|
||||
// First make the file name lower case
|
||||
std::transform(movieName.begin(), movieName.end(), movieName.begin(), ::tolower);
|
||||
@@ -451,7 +465,7 @@ bool FFMPEGDecoder::initialize(const SCP_string& fileName) {
|
||||
auto subt = openSubtitleStream(movieName);
|
||||
|
||||
// We now have a valid input stream, try to find the correct streams
|
||||
auto status = initializeStatus(input, subt);
|
||||
auto status = initializeStatus(input, subt, properties);
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
@@ -463,6 +477,7 @@ bool FFMPEGDecoder::initialize(const SCP_string& fileName) {
|
||||
std::swap(m_input, input);
|
||||
std::swap(m_subtitleInput, subt);
|
||||
std::swap(m_status, status);
|
||||
m_properties = properties;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -502,74 +517,93 @@ void FFMPEGDecoder::startDecoding() {
|
||||
|
||||
auto ctx = m_input->m_ctx->ctx();
|
||||
AVPacket packet;
|
||||
while (isDecoding()) {
|
||||
auto read_err = av_read_frame(ctx, &packet);
|
||||
AVPacketScope scope(&packet);
|
||||
|
||||
if (read_err < 0) {
|
||||
if (read_err == AVERROR_EOF) {
|
||||
// Finished reading -> break out of loop
|
||||
break;
|
||||
} else if (avio_feof(ctx->pb) != 0) {
|
||||
// Also EOF!
|
||||
break;
|
||||
} else {
|
||||
// Some kind of other error, try to continue reading
|
||||
char errorStr[512];
|
||||
av_strerror(read_err, errorStr, sizeof(errorStr));
|
||||
mprintf(("FFMPEG: Failed to read frame! Error: %s\n", errorStr));
|
||||
|
||||
// Skip packet
|
||||
continue;
|
||||
}
|
||||
do {
|
||||
auto seek_err = av_seek_frame(ctx, -1, 0, AVSEEK_FLAG_BACKWARD);
|
||||
if (seek_err < 0) {
|
||||
char errorStr[512];
|
||||
av_strerror(seek_err, errorStr, sizeof(errorStr));
|
||||
mprintf(("FFMPEG: Failed to seek to start of movie file! Error: %s\n", errorStr));
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet.stream_index == m_status->videoStreamIndex) {
|
||||
videoDecoder->decodePacket(&packet);
|
||||
|
||||
VideoFramePtr ptr;
|
||||
while((ptr = videoDecoder->getFrame()) != nullptr) {
|
||||
pushFrameData(std::move(ptr));
|
||||
}
|
||||
} else if (audioDecoder && packet.stream_index == m_status->audioStreamIndex) {
|
||||
audioDecoder->decodePacket(&packet);
|
||||
|
||||
AudioFramePtr ptr;
|
||||
while ((ptr = audioDecoder->getFrame()) != nullptr) {
|
||||
pushAudioData(std::move(ptr));
|
||||
}
|
||||
} else if (subtitleDecoder && packet.stream_index == m_status->subtitleStreamIndex) {
|
||||
subtitleDecoder->decodePacket(&packet);
|
||||
|
||||
SubtitleFramePtr ptr;
|
||||
while ((ptr = subtitleDecoder->getFrame()) != nullptr) {
|
||||
pushSubtitleData(std::move(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDecoding()) {
|
||||
// If we are still alive then read the last frames from the decoders
|
||||
videoDecoder->finishDecoding();
|
||||
VideoFramePtr video_ptr;
|
||||
while ((video_ptr = videoDecoder->getFrame()) != nullptr) {
|
||||
pushFrameData(std::move(video_ptr));
|
||||
}
|
||||
|
||||
videoDecoder->flushBuffers();
|
||||
if (audioDecoder) {
|
||||
audioDecoder->finishDecoding();
|
||||
AudioFramePtr audio_frame;
|
||||
while ((audio_frame = audioDecoder->getFrame()) != nullptr) {
|
||||
pushAudioData(std::move(audio_frame));
|
||||
audioDecoder->flushBuffers();
|
||||
}
|
||||
if (subtitleDecoder) {
|
||||
subtitleDecoder->flushBuffers();
|
||||
}
|
||||
|
||||
while (isDecoding()) {
|
||||
auto read_err = av_read_frame(ctx, &packet);
|
||||
AVPacketScope scope(&packet);
|
||||
|
||||
if (read_err < 0) {
|
||||
if (read_err == AVERROR_EOF) {
|
||||
// Finished reading -> break out of loop
|
||||
break;
|
||||
} else if (avio_feof(ctx->pb) != 0) {
|
||||
// Also EOF!
|
||||
break;
|
||||
} else {
|
||||
// Some kind of other error, try to continue reading
|
||||
char errorStr[512];
|
||||
av_strerror(read_err, errorStr, sizeof(errorStr));
|
||||
mprintf(("FFMPEG: Failed to read frame! Error: %s\n", errorStr));
|
||||
|
||||
// Skip packet
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (packet.stream_index == m_status->videoStreamIndex) {
|
||||
videoDecoder->decodePacket(&packet);
|
||||
|
||||
VideoFramePtr ptr;
|
||||
while ((ptr = videoDecoder->getFrame()) != nullptr) {
|
||||
pushFrameData(std::move(ptr));
|
||||
}
|
||||
} else if (audioDecoder && packet.stream_index == m_status->audioStreamIndex) {
|
||||
audioDecoder->decodePacket(&packet);
|
||||
|
||||
AudioFramePtr ptr;
|
||||
while ((ptr = audioDecoder->getFrame()) != nullptr) {
|
||||
pushAudioData(std::move(ptr));
|
||||
}
|
||||
} else if (subtitleDecoder && packet.stream_index == m_status->subtitleStreamIndex) {
|
||||
subtitleDecoder->decodePacket(&packet);
|
||||
|
||||
SubtitleFramePtr ptr;
|
||||
while ((ptr = subtitleDecoder->getFrame()) != nullptr) {
|
||||
pushSubtitleData(std::move(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDecoding()) {
|
||||
// If we are still alive then read the last frames from the decoders
|
||||
videoDecoder->finishDecoding();
|
||||
VideoFramePtr video_ptr;
|
||||
while ((video_ptr = videoDecoder->getFrame()) != nullptr) {
|
||||
pushFrameData(std::move(video_ptr));
|
||||
}
|
||||
|
||||
if (audioDecoder) {
|
||||
audioDecoder->finishDecoding();
|
||||
AudioFramePtr audio_frame;
|
||||
while ((audio_frame = audioDecoder->getFrame()) != nullptr) {
|
||||
pushAudioData(std::move(audio_frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the video is being looped then we start again from the beginning
|
||||
} while (m_properties.looping && isDecoding());
|
||||
|
||||
// Stop the decoder first to let the subtitle thread know that it shouldn't continue
|
||||
stopDecoder();
|
||||
|
||||
if (subtitle_thread) {
|
||||
subtitle_thread->join();
|
||||
}
|
||||
|
||||
stopDecoder();
|
||||
}
|
||||
|
||||
bool FFMPEGDecoder::hasAudio() const { return m_status->audioStreamIndex >= 0; }
|
||||
|
||||
@@ -18,6 +18,8 @@ class FFMPEGDecoder: public Decoder {
|
||||
|
||||
std::unique_ptr<DecoderStatus> m_status;
|
||||
|
||||
PlaybackProperties m_properties;
|
||||
|
||||
bool hasExternalSubtitle() const;
|
||||
|
||||
void runSubtitleDecoder(SubtitleDecoder* decoder);
|
||||
@@ -27,7 +29,7 @@ class FFMPEGDecoder: public Decoder {
|
||||
|
||||
~FFMPEGDecoder() override;
|
||||
|
||||
bool initialize(const SCP_string& fileName) override;
|
||||
bool initialize(const SCP_string& fileName, const PlaybackProperties& properties) override;
|
||||
|
||||
MovieProperties getProperties() const override;
|
||||
|
||||
|
||||
@@ -129,6 +129,9 @@ void SubtitleDecoder::pushSubtitleFrame(AVPacket* packet, AVSubtitle* subtitle)
|
||||
|
||||
pushFrame(std::move(frame));
|
||||
}
|
||||
void SubtitleDecoder::flushBuffers() {
|
||||
avcodec_flush_buffers(m_status->subtitleCodecCtx);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ class SubtitleDecoder: public FFMPEGStreamDecoder<SubtitleFrame> {
|
||||
|
||||
void finishDecoding() override;
|
||||
void pushSubtitleFrame(AVPacket* subtitle, AVSubtitle* pSubtitle);
|
||||
void flushBuffers() override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -158,5 +158,8 @@ void VideoDecoder::finishDecoding() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void VideoDecoder::flushBuffers() {
|
||||
avcodec_flush_buffers(m_status->videoCodecCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ class VideoDecoder: public FFMPEGStreamDecoder<VideoFrame> {
|
||||
void decodePacket(AVPacket* packet) override;
|
||||
|
||||
void finishDecoding() override;
|
||||
|
||||
void flushBuffers() override;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,8 @@ class FFMPEGStreamDecoder {
|
||||
|
||||
virtual void finishDecoding() = 0;
|
||||
|
||||
virtual void flushBuffers() = 0;
|
||||
|
||||
virtual FramePtr<frame_type> getFrame();
|
||||
};
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ using namespace cutscene;
|
||||
|
||||
const int MAX_AUDIO_BUFFERS = 15;
|
||||
|
||||
std::unique_ptr<Decoder> findDecoder(const SCP_string& name) {
|
||||
std::unique_ptr<Decoder> findDecoder(const SCP_string& name, const PlaybackProperties& properties) {
|
||||
{
|
||||
std::unique_ptr<Decoder> ffmpeg(new ::cutscene::ffmpeg::FFMPEGDecoder());
|
||||
if (ffmpeg->initialize(name)) {
|
||||
if (ffmpeg->initialize(name, properties)) {
|
||||
return ffmpeg;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,8 @@ void videoPlaybackInit(PlayerState* state) {
|
||||
|
||||
Assert(state != NULL);
|
||||
|
||||
if (gr_screen.mode == GR_OPENGL) {
|
||||
if (gr_screen.mode != GR_STUB) {
|
||||
// The video presenter is independent of the underlying graphics API
|
||||
state->videoPresenter.reset(new VideoPresenter(state->props));
|
||||
}
|
||||
|
||||
@@ -122,6 +123,15 @@ void processVideoData(PlayerState* state) {
|
||||
}
|
||||
|
||||
while (currentTime >= state->nextFrame->frameTime) {
|
||||
if (state->currentFrame) {
|
||||
if (state->nextFrame->frameTime < state->currentFrame->frameTime) {
|
||||
// We reached the end of the movie and just looped to the beginning. Now we need to reset the playback
|
||||
// time
|
||||
state->playback_time = 0;
|
||||
currentTime = playbackGetTime(state);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the next frame to the current frame slot
|
||||
state->currentFrame = std::move(state->nextFrame);
|
||||
|
||||
@@ -273,7 +283,8 @@ bool shouldBeginPlayback(Decoder* decoder) {
|
||||
}
|
||||
|
||||
namespace cutscene {
|
||||
Player::Player(std::unique_ptr<Decoder>&& decoder, bool enable_audio) : m_decoder(std::move(decoder))
|
||||
Player::Player(std::unique_ptr<Decoder>&& decoder, const PlaybackProperties& properties)
|
||||
: m_decoder(std::move(decoder))
|
||||
{
|
||||
Assertion(!m_state.videoInited,
|
||||
"Internal State has been initialized before! Create a new player for replaying a movie.");
|
||||
@@ -285,7 +296,7 @@ Player::Player(std::unique_ptr<Decoder>&& decoder, bool enable_audio) : m_decode
|
||||
|
||||
videoPlaybackInit(&m_state);
|
||||
|
||||
if (enable_audio) {
|
||||
if (properties.with_audio) {
|
||||
audioPlaybackInit(&m_state);
|
||||
}
|
||||
}
|
||||
@@ -355,16 +366,16 @@ void Player::decoderThread() {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Player> Player::newPlayer(const SCP_string& name, bool enable_audio)
|
||||
std::unique_ptr<Player> Player::newPlayer(const SCP_string& name, const PlaybackProperties& properties)
|
||||
{
|
||||
mprintf(("Creating player for movie '%s'.\n", name.c_str()));
|
||||
|
||||
auto decoder = findDecoder(name);
|
||||
auto decoder = findDecoder(name, properties);
|
||||
|
||||
if (decoder == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<Player>(new Player(std::move(decoder), enable_audio));
|
||||
return std::unique_ptr<Player>(new Player(std::move(decoder), properties));
|
||||
}
|
||||
const PlayerState& Player::getInternalState() const {
|
||||
return m_state;
|
||||
|
||||
@@ -64,9 +64,8 @@ class Player {
|
||||
private:
|
||||
void decoderThread();
|
||||
|
||||
Player(std::unique_ptr<Decoder>&& decoder, const PlaybackProperties& properties);
|
||||
public:
|
||||
explicit Player(std::unique_ptr<Decoder>&& decoder, bool enable_audio = true);
|
||||
|
||||
~Player();
|
||||
|
||||
Player(const Player&) = delete;
|
||||
@@ -130,9 +129,9 @@ class Player {
|
||||
* @brief Creates a player
|
||||
* The player is configured to play the movie with the specified name
|
||||
* @param name The movie to play
|
||||
* @param enable_audio Enable audio for this player instance
|
||||
* @param properties A structure describing how the movie will be used
|
||||
* @return @c nullptr if the specified movie could not be opened, a valid Player pointer otherwise
|
||||
*/
|
||||
static std::unique_ptr<Player> newPlayer(const SCP_string& name, bool enable_audio = true);
|
||||
static std::unique_ptr<Player> newPlayer(const SCP_string& name, const PlaybackProperties& properties = PlaybackProperties());
|
||||
};
|
||||
}
|
||||
@@ -103,20 +103,6 @@ typedef struct vec3d {
|
||||
};
|
||||
} vec3d;
|
||||
|
||||
/** Compares two vec3ds */
|
||||
inline bool operator==(const vec3d &self, const vec3d &other)
|
||||
{
|
||||
return (self.xyz.x == other.xyz.x
|
||||
&& self.xyz.y == other.xyz.y
|
||||
&& self.xyz.z == other.xyz.z
|
||||
);
|
||||
}
|
||||
|
||||
inline bool operator!=(const vec3d &self, const vec3d &other)
|
||||
{
|
||||
return !(self == other);
|
||||
}
|
||||
|
||||
typedef struct vec2d {
|
||||
float x, y;
|
||||
} vec2d;
|
||||
|
||||
@@ -495,6 +495,79 @@ vec3d vm_vec4_to_vec3(const vec4& vec);
|
||||
*/
|
||||
vec4 vm_vec3_to_ve4(const vec3d& vec, float w = 1.0f);
|
||||
|
||||
/** Compares two vec3ds */
|
||||
inline bool operator==(const vec3d& left, const vec3d& right) { return vm_vec_same(&left, &right) != 0; }
|
||||
inline bool operator!=(const vec3d& left, const vec3d& right) { return !(left == right); }
|
||||
|
||||
inline vec3d operator+(const vec3d& left, const vec3d& right)
|
||||
{
|
||||
vec3d res;
|
||||
vm_vec_add(&res, &left, &right);
|
||||
return res;
|
||||
}
|
||||
inline vec3d& operator+=(vec3d& left, const vec3d& right)
|
||||
{
|
||||
vm_vec_add2(&left, &right);
|
||||
return left;
|
||||
}
|
||||
|
||||
inline vec3d operator-(const vec3d& left, const vec3d& right)
|
||||
{
|
||||
vec3d res;
|
||||
vm_vec_sub(&res, &left, &right);
|
||||
return res;
|
||||
}
|
||||
inline vec3d& operator-=(vec3d& left, const vec3d& right)
|
||||
{
|
||||
vm_vec_sub2(&left, &right);
|
||||
return left;
|
||||
}
|
||||
|
||||
inline vec3d operator*(vec3d& left, float right)
|
||||
{
|
||||
vec3d out;
|
||||
vm_vec_copy_scale(&out, &left, right);
|
||||
return out;
|
||||
}
|
||||
inline vec3d& operator*=(vec3d& left, float right)
|
||||
{
|
||||
vm_vec_scale(&left, right);
|
||||
return left;
|
||||
}
|
||||
|
||||
inline vec3d operator/(vec3d& left, float right)
|
||||
{
|
||||
vec3d out;
|
||||
vm_vec_copy_scale(&out, &left, 1.0f / right);
|
||||
return out;
|
||||
}
|
||||
inline vec3d& operator/=(vec3d& left, float right)
|
||||
{
|
||||
vm_vec_scale(&left, 1.0f / right);
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rotates a vector into the orientation specified by the matrix
|
||||
* @param left The matrix
|
||||
* @param right The vector
|
||||
* @return The rotated vector
|
||||
*
|
||||
* @note This actually follows the definition of the * operator in linear algebra. The standard vm_vec_rotate actually
|
||||
* implements a multiplication with the transpose of the matrix.
|
||||
*/
|
||||
inline vec3d operator*(const matrix& left, const vec3d& right) {
|
||||
vec3d out;
|
||||
vm_vec_unrotate(&out, &right, &left);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline matrix operator*(const matrix& left, const matrix& right) {
|
||||
matrix out;
|
||||
vm_matrix_x_matrix(&out, &left, &right);
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1541,18 +1541,23 @@ ADE_FUNC(resetClip, l_Graphics, NULL, "Resets the clipping region that might hav
|
||||
return ADE_RETURN_TRUE;
|
||||
}
|
||||
|
||||
ADE_FUNC(openMovie, l_Graphics, "string name",
|
||||
ADE_FUNC(openMovie, l_Graphics, "string name, boolean looping = false",
|
||||
"Opens the movie with the specified name. If the name has an extension it will be removed. This function will "
|
||||
"try all movie formats supported by the engine and use the first that is found.",
|
||||
"movie_player", "The cutscene player handle or invalid handle if cutscene could not be opened.")
|
||||
{
|
||||
const char* name = nullptr;
|
||||
if (!ade_get_args(L, "s", &name)) {
|
||||
bool looping = false;
|
||||
if (!ade_get_args(L, "s|b", &name, &looping)) {
|
||||
return ade_set_error(L, "o", l_MoviePlayer.Set(nullptr));
|
||||
}
|
||||
|
||||
// Audio is disabled for scripted movies at the moment
|
||||
auto player = cutscene::Player::newPlayer(name, false);
|
||||
cutscene::PlaybackProperties props;
|
||||
props.with_audio = false;
|
||||
props.looping = looping;
|
||||
|
||||
auto player = cutscene::Player::newPlayer(name, props);
|
||||
|
||||
if (!player) {
|
||||
return ade_set_args(L, "o", l_MoviePlayer.Set(nullptr));
|
||||
|
||||
@@ -308,7 +308,10 @@ bool fred_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps)
|
||||
cfile_chdir(Fred_base_dir);
|
||||
|
||||
// this should enable mods - Kazan
|
||||
parse_cmdline(__argc, __argv);
|
||||
if (!parse_cmdline(__argc, __argv)) {
|
||||
// Command line contained an option that terminates the program immediately
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
#if FS_VERSION_REVIS == 0
|
||||
|
||||
@@ -38,6 +38,8 @@ add_file_folder("Source/Mission"
|
||||
add_file_folder("Source/Mission/Dialogs"
|
||||
src/mission/dialogs/AbstractDialogModel.cpp
|
||||
src/mission/dialogs/AbstractDialogModel.h
|
||||
src/mission/dialogs/AsteroidEditorDialogModel.cpp
|
||||
src/mission/dialogs/AsteroidEditorDialogModel.h
|
||||
src/mission/dialogs/CustomWingNamesDialogModel.cpp
|
||||
src/mission/dialogs/CustomWingNamesDialogModel.h
|
||||
src/mission/dialogs/FictionViewerDialogModel.cpp
|
||||
@@ -68,6 +70,8 @@ add_file_folder("Source/UI"
|
||||
add_file_folder("Source/UI/Dialogs"
|
||||
src/ui/dialogs/AboutDialog.cpp
|
||||
src/ui/dialogs/AboutDialog.h
|
||||
src/ui/dialogs/AsteroidEditorDialog.cpp
|
||||
src/ui/dialogs/AsteroidEditorDialog.h
|
||||
src/ui/dialogs/BackgroundEditorDialog.h
|
||||
src/ui/dialogs/BackgroundEditorDialog.cpp
|
||||
src/ui/dialogs/BriefingEditorDialog.cpp
|
||||
@@ -116,6 +120,7 @@ add_file_folder("Source/UI/Widgets"
|
||||
|
||||
add_file_folder("UI"
|
||||
ui/AboutDialog.ui
|
||||
ui/AsteroidEditorDialog.ui
|
||||
ui/BackgroundEditor.ui
|
||||
ui/BriefingEditorDialog.ui
|
||||
ui/CustomWingNamesDialog.ui
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
#include "mission/dialogs/AsteroidEditorDialogModel.h"
|
||||
|
||||
namespace fso {
|
||||
namespace fred {
|
||||
namespace dialogs {
|
||||
|
||||
AsteroidEditorDialogModel::AsteroidEditorDialogModel(QObject* parent, EditorViewport* viewport) :
|
||||
AbstractDialogModel(parent, viewport),
|
||||
_enable_asteroids(false),
|
||||
_enable_inner_bounds(false),
|
||||
_num_asteroids(0),
|
||||
_avg_speed(0),
|
||||
_min_x(""),
|
||||
_min_y(""),
|
||||
_min_z(""),
|
||||
_max_x(""),
|
||||
_max_y(""),
|
||||
_max_z(""),
|
||||
_inner_min_x(""),
|
||||
_inner_min_y(""),
|
||||
_inner_min_z(""),
|
||||
_inner_max_x(""),
|
||||
_inner_max_y(""),
|
||||
_inner_max_z(""),
|
||||
_field_type(FT_ACTIVE),
|
||||
_debris_genre(DG_ASTEROID),
|
||||
_bypass_errors(false),
|
||||
_modified(false),
|
||||
_cur_field(0),
|
||||
_last_field(-1)
|
||||
{
|
||||
for (auto i = 0ul; i < ship_debris_idx_lookup.size(); ++i) {
|
||||
debris_inverse_idx_lookup.emplace(ship_debris_idx_lookup[i], i);
|
||||
}
|
||||
// note that normal asteroids use the same index field! Need to add dummy entries for them as well
|
||||
for (auto i = 0; i < 3; ++i) {
|
||||
debris_inverse_idx_lookup.emplace(i, 0);
|
||||
}
|
||||
initializeData();
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::apply()
|
||||
{
|
||||
update_init();
|
||||
if (!AsteroidEditorDialogModel::validate_data()) {
|
||||
return false;
|
||||
}
|
||||
Asteroid_field = _a_field;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::reject()
|
||||
{
|
||||
//do nothing - only here because parent class reject() function is virtual
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::initializeData()
|
||||
{
|
||||
for (auto i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; ++i) {
|
||||
_field_debris_type[i] = -1;
|
||||
}
|
||||
|
||||
_a_field = Asteroid_field;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setEnabled(bool enabled)
|
||||
{
|
||||
_enable_asteroids = enabled;
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::getEnabled()
|
||||
{
|
||||
return _enable_asteroids;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setInnerBoxEnabled(bool enabled)
|
||||
{
|
||||
_enable_inner_bounds = enabled;
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::getInnerBoxEnabled()
|
||||
{
|
||||
return _enable_inner_bounds;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setAsteroidEnabled(_roid_types type, bool enabled)
|
||||
{
|
||||
Assertion(type >=0 && type < MAX_ACTIVE_DEBRIS_TYPES, "Invalid Asteroid checkbox type: %i\n", type);
|
||||
modify(_field_debris_type[type], enabled == true ? 1 : -1);
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::getAsteroidEnabled(_roid_types type)
|
||||
{
|
||||
Assertion(type >=0 && type < MAX_ACTIVE_DEBRIS_TYPES, "Invalid Asteroid checkbox type: %i\n", type);
|
||||
return (_field_debris_type[type] == 1);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setNumAsteroids(int num_asteroids)
|
||||
{
|
||||
modify(_num_asteroids, num_asteroids);
|
||||
}
|
||||
|
||||
int AsteroidEditorDialogModel::getNumAsteroids()
|
||||
{
|
||||
return _num_asteroids;
|
||||
}
|
||||
|
||||
QString & AsteroidEditorDialogModel::getBoxText(_box_line_edits type)
|
||||
{
|
||||
switch (type) {
|
||||
case _O_MIN_X: return _min_x;
|
||||
case _O_MIN_Y: return _min_y;
|
||||
case _O_MIN_Z: return _min_z;
|
||||
case _O_MAX_X: return _max_x;
|
||||
case _O_MAX_Y: return _max_y;
|
||||
case _O_MAX_Z: return _max_z;
|
||||
case _I_MIN_X: return _inner_min_x;
|
||||
case _I_MIN_Y: return _inner_min_y;
|
||||
case _I_MIN_Z: return _inner_min_z;
|
||||
case _I_MAX_X: return _inner_max_x;
|
||||
case _I_MAX_Y: return _inner_max_y;
|
||||
case _I_MAX_Z: return _inner_max_z;
|
||||
default:
|
||||
Error(LOCATION, "Get a coder! Unknown enum value found! %i", type);
|
||||
return _min_x;
|
||||
}
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setBoxText(const QString &text, _box_line_edits type)
|
||||
{
|
||||
switch (type) {
|
||||
case _O_MIN_X: modify(_min_x, text); break;
|
||||
case _O_MIN_Y: modify(_min_y, text); break;
|
||||
case _O_MIN_Z: modify(_min_z, text); break;
|
||||
case _O_MAX_X: modify(_max_x, text); break;
|
||||
case _O_MAX_Y: modify(_max_y, text); break;
|
||||
case _O_MAX_Z: modify(_max_z, text); break;
|
||||
case _I_MIN_X: modify(_inner_min_x, text); break;
|
||||
case _I_MIN_Y: modify(_inner_min_y, text); break;
|
||||
case _I_MIN_Z: modify(_inner_min_z, text); break;
|
||||
case _I_MAX_X: modify(_inner_max_x, text); break;
|
||||
case _I_MAX_Y: modify(_inner_max_y, text); break;
|
||||
case _I_MAX_Z: modify(_inner_max_z, text); break;
|
||||
default:
|
||||
Error(LOCATION, "Get a coder! Unknown enum value found! %i", type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setDebrisGenre(debris_genre_t genre)
|
||||
{
|
||||
modify(_debris_genre, genre);
|
||||
}
|
||||
|
||||
debris_genre_t AsteroidEditorDialogModel::getDebrisGenre()
|
||||
{
|
||||
return _debris_genre;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setFieldType(field_type_t type)
|
||||
{
|
||||
modify(_field_type, type);
|
||||
}
|
||||
|
||||
field_type_t AsteroidEditorDialogModel::getFieldType()
|
||||
{
|
||||
return _field_type;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setFieldDebrisType(int idx, int debris_type)
|
||||
{
|
||||
Assertion(idx >= 0 && idx < MAX_ACTIVE_DEBRIS_TYPES, "Invalid debris index provided: %i\n", idx);
|
||||
modify(_field_debris_type[idx], ship_debris_idx_lookup.at(debris_type));
|
||||
}
|
||||
|
||||
int AsteroidEditorDialogModel::getFieldDebrisType(int idx)
|
||||
{
|
||||
Assertion(idx >= 0 && idx < MAX_ACTIVE_DEBRIS_TYPES, "Invalid debris index provided: %i\n", idx);
|
||||
return debris_inverse_idx_lookup.at(_field_debris_type[idx]);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::setAvgSpeed(int speed)
|
||||
{
|
||||
modify(_avg_speed, speed);
|
||||
}
|
||||
|
||||
QString AsteroidEditorDialogModel::getAvgSpeed()
|
||||
{
|
||||
return QString::number(_avg_speed);
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::validate_data()
|
||||
{
|
||||
if (!_enable_asteroids) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// be helpful to the FREDer; try to advise precisely what the problem is
|
||||
// more general checks 1st, followed by more specific ones
|
||||
_bypass_errors = false;
|
||||
|
||||
// check outer x/y/z max is greater than min
|
||||
if (_a_field.max_bound.xyz.x < _a_field.min_bound.xyz.x) {
|
||||
showErrorDialogNoCancel( "Outer box 'X' min is greater than max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check y
|
||||
if (_a_field.max_bound.xyz.y < _a_field.min_bound.xyz.y) {
|
||||
showErrorDialogNoCancel( "Outer box 'Y' min is greater than max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check z
|
||||
if (_a_field.max_bound.xyz.z < _a_field.min_bound.xyz.z) {
|
||||
showErrorDialogNoCancel( "Outer box 'Z' min is greater than max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.has_inner_bound) {
|
||||
// check inner x/y/z max is greater than min
|
||||
if (_a_field.inner_max_bound.xyz.x < _a_field.inner_min_bound.xyz.x) {
|
||||
showErrorDialogNoCancel( "Inner box 'X' min is greater than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.inner_max_bound.xyz.y < _a_field.inner_min_bound.xyz.y) {
|
||||
showErrorDialogNoCancel( "Inner box 'Y' min is greater than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.inner_max_bound.xyz.z < _a_field.inner_min_bound.xyz.z) {
|
||||
showErrorDialogNoCancel( "Inner box 'Z' min is greater than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check outer x/y/z max is greater than inner x/y/z max
|
||||
if (_a_field.max_bound.xyz.x < _a_field.inner_max_bound.xyz.x) {
|
||||
showErrorDialogNoCancel( "Outer box 'X' max is less than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.max_bound.xyz.y < _a_field.inner_max_bound.xyz.y) {
|
||||
showErrorDialogNoCancel( "Outer box 'Y' max is less than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.max_bound.xyz.z < _a_field.inner_max_bound.xyz.z) {
|
||||
showErrorDialogNoCancel( "Outer box 'Z' max is less than inner max\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check outer x/y/z min is less than inner x/y/z min
|
||||
if (_a_field.min_bound.xyz.x > _a_field.inner_min_bound.xyz.x) {
|
||||
showErrorDialogNoCancel( "Inner box 'X' min is less than outer min\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.min_bound.xyz.y > _a_field.inner_min_bound.xyz.y) {
|
||||
showErrorDialogNoCancel( "Inner box 'Y' min is less than outer min\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_a_field.min_bound.xyz.z > _a_field.inner_min_bound.xyz.z) {
|
||||
showErrorDialogNoCancel( "Inner box 'Z' min is less than outer min\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// split checks to give FREDers more specific feedback
|
||||
// check x thickness
|
||||
if (_a_field.inner_min_bound.xyz.x - _MIN_BOX_THICKNESS < _a_field.min_bound.xyz.x) {
|
||||
showErrorDialogNoCancel(
|
||||
"X axis minimum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
if (_a_field.inner_max_bound.xyz.x + _MIN_BOX_THICKNESS > _a_field.max_bound.xyz.x) {
|
||||
showErrorDialogNoCancel(
|
||||
"X axis maximum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check y thickness
|
||||
if (_a_field.inner_min_bound.xyz.y - _MIN_BOX_THICKNESS < _a_field.min_bound.xyz.y) {
|
||||
showErrorDialogNoCancel(
|
||||
"Y axis minimum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
if (_a_field.inner_max_bound.xyz.y + _MIN_BOX_THICKNESS > _a_field.max_bound.xyz.y) {
|
||||
showErrorDialogNoCancel(
|
||||
"Y axis maximum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check z thickness
|
||||
if (_a_field.inner_min_bound.xyz.z - _MIN_BOX_THICKNESS < _a_field.min_bound.xyz.z) {
|
||||
showErrorDialogNoCancel(
|
||||
"Z axis minimum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
if (_a_field.inner_max_bound.xyz.z + _MIN_BOX_THICKNESS > _a_field.max_bound.xyz.z) {
|
||||
showErrorDialogNoCancel(
|
||||
"Z axis maximum values must be at least " + \
|
||||
std::to_string(_MIN_BOX_THICKNESS) + " apart");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// for a ship debris (i.e. passive) field, need at least one debris type is selected
|
||||
if (_a_field.field_type == FT_PASSIVE) {
|
||||
if (_a_field.debris_genre == DG_SHIP) {
|
||||
if ( (_a_field.field_debris_type[0] == -1) && \
|
||||
(_a_field.field_debris_type[1] == -1) && \
|
||||
(_a_field.field_debris_type[2] == -1) ) {
|
||||
showErrorDialogNoCancel("You must choose one or more types of ship debris\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check at least one asteroid subtype is selected
|
||||
if (_a_field.debris_genre == DG_ASTEROID) {
|
||||
if ( (_a_field.field_debris_type[_AST_BROWN] == -1) && \
|
||||
(_a_field.field_debris_type[_AST_BLUE] == -1) && \
|
||||
(_a_field.field_debris_type[_AST_ORANGE] == -1) ) {
|
||||
showErrorDialogNoCancel("You must choose one or more asteroid subtypes\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::update_init()
|
||||
{
|
||||
int num_asteroids;
|
||||
|
||||
if (_last_field >= 0) {
|
||||
// store into temp asteroid field
|
||||
num_asteroids = _a_field.num_initial_asteroids;
|
||||
_a_field.num_initial_asteroids = _enable_asteroids ? _num_asteroids : 0;
|
||||
CLAMP(_a_field.num_initial_asteroids, 0, MAX_ASTEROIDS);
|
||||
|
||||
if (num_asteroids != _a_field.num_initial_asteroids) {
|
||||
set_modified();
|
||||
}
|
||||
|
||||
vec3d vel_vec {{{ 1.0f, 0.0f, 0.0f }}};
|
||||
vm_vec_scale(&vel_vec, static_cast<float>(_avg_speed));
|
||||
modify(_a_field.vel, vel_vec);
|
||||
|
||||
// save the box coords
|
||||
modify(_a_field.min_bound.xyz.x, _min_x.toFloat());
|
||||
modify(_a_field.min_bound.xyz.y, _min_y.toFloat());
|
||||
modify(_a_field.min_bound.xyz.z, _min_z.toFloat());
|
||||
modify(_a_field.max_bound.xyz.x, _max_x.toFloat());
|
||||
modify(_a_field.max_bound.xyz.y, _max_y.toFloat());
|
||||
modify(_a_field.max_bound.xyz.z, _max_z.toFloat());
|
||||
modify(_a_field.inner_min_bound.xyz.x, _inner_min_x.toFloat());
|
||||
modify(_a_field.inner_min_bound.xyz.y, _inner_min_y.toFloat());
|
||||
modify(_a_field.inner_min_bound.xyz.z, _inner_min_z.toFloat());
|
||||
modify(_a_field.inner_max_bound.xyz.x, _inner_max_x.toFloat());
|
||||
modify(_a_field.inner_max_bound.xyz.y, _inner_max_y.toFloat());
|
||||
modify(_a_field.inner_max_bound.xyz.z, _inner_max_z.toFloat());
|
||||
|
||||
// type of field
|
||||
modify(_a_field.field_type, _field_type);
|
||||
modify(_a_field.debris_genre, _debris_genre);
|
||||
|
||||
// ship debris
|
||||
if ( (_field_type == FT_PASSIVE) && (_debris_genre == DG_SHIP) ) {
|
||||
for (auto idx=0; idx<MAX_ACTIVE_DEBRIS_TYPES; ++idx) {
|
||||
modify(_a_field.field_debris_type[idx], _field_debris_type[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// asteroids
|
||||
if ( _debris_genre == DG_ASTEROID ) {
|
||||
modify(_a_field.field_debris_type[_AST_BROWN], getAsteroidEnabled(_AST_BROWN) == true ? 1 : -1);
|
||||
modify(_a_field.field_debris_type[_AST_BLUE], getAsteroidEnabled(_AST_BLUE) == true ? 1 : -1);
|
||||
modify(_a_field.field_debris_type[_AST_ORANGE], getAsteroidEnabled(_AST_ORANGE) == true ? 1 : -1);
|
||||
}
|
||||
|
||||
modify(_a_field.has_inner_bound, static_cast<int>(_enable_inner_bounds));
|
||||
}
|
||||
|
||||
// get from temp asteroid field into class
|
||||
_enable_asteroids = _a_field.num_initial_asteroids ? true : false;
|
||||
_enable_inner_bounds = _a_field.has_inner_bound ? true : false;
|
||||
_num_asteroids = _a_field.num_initial_asteroids;
|
||||
if (!_enable_asteroids) {
|
||||
_num_asteroids = 10;
|
||||
}
|
||||
|
||||
// set field type
|
||||
_field_type = _a_field.field_type;
|
||||
_debris_genre = _a_field.debris_genre;
|
||||
|
||||
_avg_speed = static_cast<int>(vm_vec_mag(&_a_field.vel));
|
||||
|
||||
_min_x = QString::number(_a_field.min_bound.xyz.x, 'f', 1);
|
||||
_min_y = QString::number(_a_field.min_bound.xyz.y, 'f', 1);
|
||||
_min_z = QString::number(_a_field.min_bound.xyz.z, 'f', 1);
|
||||
_max_x = QString::number(_a_field.max_bound.xyz.x, 'f', 1);
|
||||
_max_y = QString::number(_a_field.max_bound.xyz.y, 'f', 1);
|
||||
_max_z = QString::number(_a_field.max_bound.xyz.z, 'f', 1);
|
||||
_inner_min_x = QString::number(_a_field.inner_min_bound.xyz.x, 'f', 1);
|
||||
_inner_min_y = QString::number(_a_field.inner_min_bound.xyz.y, 'f', 1);
|
||||
_inner_min_z = QString::number(_a_field.inner_min_bound.xyz.z, 'f', 1);
|
||||
_inner_max_x = QString::number(_a_field.inner_max_bound.xyz.x, 'f', 1);
|
||||
_inner_max_y = QString::number(_a_field.inner_max_bound.xyz.y, 'f', 1);
|
||||
_inner_max_z = QString::number(_a_field.inner_max_bound.xyz.z, 'f', 1);
|
||||
|
||||
// ship debris or asteroids
|
||||
for (auto i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; ++i) {
|
||||
_field_debris_type[i] = _a_field.field_debris_type[i];
|
||||
}
|
||||
|
||||
_last_field = _cur_field;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::set_modified()
|
||||
{
|
||||
_modified = true;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::unset_modified()
|
||||
{
|
||||
_modified = false;
|
||||
}
|
||||
|
||||
bool AsteroidEditorDialogModel::get_modified()
|
||||
{
|
||||
return _modified;
|
||||
}
|
||||
|
||||
void AsteroidEditorDialogModel::showErrorDialogNoCancel(const SCP_string& message)
|
||||
{
|
||||
if (_bypass_errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
_bypass_errors = true;
|
||||
_viewport->dialogProvider->showButtonDialog(DialogType::Error,
|
||||
"Error",
|
||||
message,
|
||||
{ DialogButton::Ok });
|
||||
}
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace fred
|
||||
} // namespace fso
|
||||
@@ -0,0 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include "mission/dialogs/AbstractDialogModel.h"
|
||||
|
||||
#include "asteroid/asteroid.h"
|
||||
|
||||
namespace fso {
|
||||
namespace fred {
|
||||
namespace dialogs {
|
||||
|
||||
class AsteroidEditorDialogModel: public AbstractDialogModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AsteroidEditorDialogModel(QObject* parent, EditorViewport* viewport);
|
||||
|
||||
enum _box_line_edits {
|
||||
_I_MIN_X =0,
|
||||
_I_MIN_Y,
|
||||
_I_MIN_Z,
|
||||
_I_MAX_X,
|
||||
_I_MAX_Y,
|
||||
_I_MAX_Z,
|
||||
_O_MIN_X,
|
||||
_O_MIN_Y,
|
||||
_O_MIN_Z,
|
||||
_O_MAX_X,
|
||||
_O_MAX_Y,
|
||||
_O_MAX_Z,
|
||||
};
|
||||
enum _roid_types {
|
||||
_AST_BROWN =0,
|
||||
_AST_BLUE =1,
|
||||
_AST_ORANGE =2,
|
||||
};
|
||||
|
||||
bool apply() override;
|
||||
void reject() override;
|
||||
|
||||
void setEnabled(bool enabled);
|
||||
bool getEnabled();
|
||||
void setInnerBoxEnabled(bool enabled);
|
||||
bool getInnerBoxEnabled();
|
||||
void setAsteroidEnabled(_roid_types type, bool enabled);
|
||||
bool getAsteroidEnabled(_roid_types type);
|
||||
void setNumAsteroids(int num_asteroids);
|
||||
int getNumAsteroids();
|
||||
void setDebrisGenre(debris_genre_t genre);
|
||||
debris_genre_t getDebrisGenre();
|
||||
void setFieldType(field_type_t type);
|
||||
field_type_t getFieldType();
|
||||
void setFieldDebrisType(int idx, int num_asteroids);
|
||||
int getFieldDebrisType(int idx);
|
||||
void setAvgSpeed(int speed);
|
||||
QString getAvgSpeed();
|
||||
void setBoxText(const QString &text, _box_line_edits type);
|
||||
QString & getBoxText(_box_line_edits type);
|
||||
|
||||
void update_init();
|
||||
bool validate_data();
|
||||
|
||||
void set_modified();
|
||||
void unset_modified();
|
||||
bool get_modified();
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
void modify(T &a, const T &b);
|
||||
|
||||
void showErrorDialogNoCancel(const SCP_string& message);
|
||||
void initializeData();
|
||||
|
||||
bool _enable_asteroids;
|
||||
bool _enable_inner_bounds;
|
||||
int _num_asteroids;
|
||||
int _avg_speed;
|
||||
|
||||
QString _min_x;
|
||||
QString _min_y;
|
||||
QString _min_z;
|
||||
QString _max_x;
|
||||
QString _max_y;
|
||||
QString _max_z;
|
||||
QString _inner_min_x;
|
||||
QString _inner_min_y;
|
||||
QString _inner_min_z;
|
||||
QString _inner_max_x;
|
||||
QString _inner_max_y;
|
||||
QString _inner_max_z;
|
||||
|
||||
int _field_debris_type[MAX_ACTIVE_DEBRIS_TYPES]; // species and size of ship debris
|
||||
field_type_t _field_type; // active or passive
|
||||
debris_genre_t _debris_genre; // ship or asteroid
|
||||
asteroid_field _a_field; // :v: had unfinished plans for multiple fields?
|
||||
|
||||
bool _bypass_errors;
|
||||
bool _modified;
|
||||
int _cur_field;
|
||||
int _last_field;
|
||||
|
||||
const int _MIN_BOX_THICKNESS = 400;
|
||||
// for debris combo box indexes
|
||||
// -1 == none, 3 == terran debris (small), etc to 11 == shivan debris (large)
|
||||
const std::array<int, 10> ship_debris_idx_lookup{ {-1, 3, 4, 5, 6, 7, 8, 9, 10, 11} };
|
||||
// and the inverse as a map + roids - populate in ctor
|
||||
std::unordered_map<int, int> debris_inverse_idx_lookup;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline void AsteroidEditorDialogModel::modify(T &a, const T &b) {
|
||||
if (a != b) {
|
||||
a = b;
|
||||
set_modified();
|
||||
modelChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace fred
|
||||
} // namespace fso
|
||||
@@ -66,7 +66,10 @@ initialize(const std::string& cfilepath, int argc, char* argv[], Editor* editor,
|
||||
|
||||
listener(SubSystem::CommandLine);
|
||||
// this should enable mods - Kazan
|
||||
parse_cmdline(argc, argv);
|
||||
if (!parse_cmdline(argc, argv)) {
|
||||
// Command line contained an option that terminates the program immediately
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
#if FS_VERSION_REVIS == 0
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <FredApplication.h>
|
||||
|
||||
#include <ui/dialogs/EventEditorDialog.h>
|
||||
#include <ui/dialogs/AsteroidEditorDialog.h>
|
||||
#include <ui/dialogs/BriefingEditorDialog.h>
|
||||
#include <ui/dialogs/WaypointEditorDialog.h>
|
||||
#include <ui/dialogs/MissionGoalsDialog.h>
|
||||
@@ -672,6 +673,10 @@ void FredView::onUpdateShipClassBox() {
|
||||
void FredView::onShipClassSelected(int ship_class) {
|
||||
_viewport->cur_model_index = ship_class;
|
||||
}
|
||||
void FredView::on_actionAsteroid_Field_triggered(bool) {
|
||||
auto asteroidFieldEditor = new dialogs::AsteroidEditorDialog(this, _viewport);
|
||||
asteroidFieldEditor->show();
|
||||
}
|
||||
void FredView::on_actionBriefing_triggered(bool) {
|
||||
auto eventEditor = new dialogs::BriefingEditorDialog(this);
|
||||
eventEditor->show();
|
||||
|
||||
@@ -80,6 +80,7 @@ class FredView: public QMainWindow, public IDialogProvider {
|
||||
void on_actionCurrent_Ship_triggered(bool enabled);
|
||||
|
||||
void on_actionEvents_triggered(bool);
|
||||
void on_actionAsteroid_Field_triggered(bool);
|
||||
void on_actionBriefing_triggered(bool);
|
||||
void on_actionMission_Specs_triggered(bool);
|
||||
void on_actionWaypoint_Paths_triggered(bool);
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
#include "ui/dialogs/AsteroidEditorDialog.h"
|
||||
#include "ui/util/SignalBlockers.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ui_AsteroidEditorDialog.h"
|
||||
|
||||
namespace fso {
|
||||
namespace fred {
|
||||
namespace dialogs {
|
||||
|
||||
static bool sort_qcombobox_by_name(const QComboBox *left, const QComboBox *right)
|
||||
{
|
||||
Assertion(left != nullptr && right != nullptr, "Don't pass nullptr's to sort!\n");
|
||||
return left->objectName() < right->objectName();
|
||||
}
|
||||
|
||||
AsteroidEditorDialog::AsteroidEditorDialog(FredView *parent, EditorViewport* viewport) :
|
||||
QDialog(parent),
|
||||
_viewport(viewport),
|
||||
_editor(viewport->editor),
|
||||
ui(new Ui::AsteroidEditorDialog()),
|
||||
_model(new AsteroidEditorDialogModel(this, viewport))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
_model->update_init();
|
||||
|
||||
// checkboxes
|
||||
connect(ui->enabled, &QCheckBox::toggled, this, &AsteroidEditorDialog::toggleEnabled);
|
||||
connect(ui->innerBoxEnabled, &QCheckBox::toggled, this, &AsteroidEditorDialog::toggleInnerBoxEnabled);
|
||||
|
||||
connect(ui->checkBoxBrown, &QCheckBox::toggled, this,
|
||||
[this](bool enabled) { \
|
||||
AsteroidEditorDialog::toggleAsteroid(AsteroidEditorDialogModel::_AST_BROWN, enabled); });
|
||||
connect(ui->checkBoxBlue, &QCheckBox::toggled, this,
|
||||
[this](bool enabled) { \
|
||||
AsteroidEditorDialog::toggleAsteroid(AsteroidEditorDialogModel::_AST_BLUE, enabled); });
|
||||
connect(ui->checkBoxOrange, &QCheckBox::toggled, this,
|
||||
[this](bool enabled) { \
|
||||
AsteroidEditorDialog::toggleAsteroid(AsteroidEditorDialogModel::_AST_ORANGE, enabled); });
|
||||
|
||||
// (come in) spinners
|
||||
ui->spinBoxNumber->setRange(1, MAX_ASTEROIDS);
|
||||
ui->spinBoxNumber->setValue(_model->getNumAsteroids());
|
||||
// only connect once we're done setting values or unwanted signal's will be sent
|
||||
connect(ui->spinBoxNumber, QOverload<int>::of(&QSpinBox::valueChanged), this, \
|
||||
&AsteroidEditorDialog::asteroidNumberChanged);
|
||||
|
||||
// setup values in ship debris combo boxes
|
||||
// MFC let you set comboxbox item indexes, Qt doesn't so we'll need a lookup
|
||||
static_assert(MAX_ACTIVE_DEBRIS_TYPES == 3,
|
||||
"qtFRED only provides three combo boxes for debris type input");
|
||||
debrisComboBoxes = ui->fieldProperties->findChildren<QComboBox *>(QString(), Qt::FindDirectChildrenOnly);
|
||||
std::sort(debrisComboBoxes.begin(), debrisComboBoxes.end(), sort_qcombobox_by_name);
|
||||
|
||||
QString debris_size[NUM_DEBRIS_SIZES] = { "Small", "Medium", "Large" };
|
||||
QStringList debris_names("None");
|
||||
for (auto i = 0ul; i < Species_info.size(); i++) // each species
|
||||
{
|
||||
for (auto j = 0; j < NUM_DEBRIS_SIZES; j++) // each size
|
||||
{
|
||||
debris_names += QString(Species_info.at(i).species_name) + " " + debris_size[j];
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0; i < MAX_ACTIVE_DEBRIS_TYPES; ++i) {
|
||||
debrisComboBoxes.at(i)->addItems(debris_names);
|
||||
// update debris combobox data on index changes
|
||||
connect(debrisComboBoxes.at(i), QOverload<int>::of(&QComboBox::currentIndexChanged), this, \
|
||||
[this, i](int debris_type) { AsteroidEditorDialog::updateComboBox(i,debris_type); });
|
||||
}
|
||||
|
||||
|
||||
// radio buttons
|
||||
connect(ui->radioButtonActiveField, &QRadioButton::toggled, this, &AsteroidEditorDialog::setFieldActive);
|
||||
connect(ui->radioButtonPassiveField, &QRadioButton::toggled, this, &AsteroidEditorDialog::setFieldPassive);
|
||||
connect(ui->radioButtonAsteroid, &QRadioButton::toggled, this, &AsteroidEditorDialog::setGenreAsteroid);
|
||||
connect(ui->radioButtonShip, &QRadioButton::toggled, this, &AsteroidEditorDialog::setGenreShip);
|
||||
|
||||
// lineEdit signals/slots
|
||||
connect(ui->lineEdit_obox_minX, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMinX);
|
||||
connect(ui->lineEdit_obox_minY, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMinY);
|
||||
connect(ui->lineEdit_obox_minZ, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMinZ);
|
||||
connect(ui->lineEdit_obox_maxX, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMaxX);
|
||||
connect(ui->lineEdit_obox_maxY, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMaxY);
|
||||
connect(ui->lineEdit_obox_maxZ, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextOMaxZ);
|
||||
connect(ui->lineEdit_ibox_minX, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMinX);
|
||||
connect(ui->lineEdit_ibox_minY, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMinY);
|
||||
connect(ui->lineEdit_ibox_minZ, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMinZ);
|
||||
connect(ui->lineEdit_ibox_maxX, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMaxX);
|
||||
connect(ui->lineEdit_ibox_maxY, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMaxY);
|
||||
connect(ui->lineEdit_ibox_maxZ, &QLineEdit::textEdited, this, \
|
||||
&AsteroidEditorDialog::changedBoxTextIMaxZ);
|
||||
|
||||
// setup validators for text input
|
||||
_box_validator.setNotation(QDoubleValidator::StandardNotation);
|
||||
_box_validator.setDecimals(1);
|
||||
ui->lineEdit_obox_minX->setValidator(&_box_validator);
|
||||
ui->lineEdit_obox_minY->setValidator(&_box_validator);
|
||||
ui->lineEdit_obox_minZ->setValidator(&_box_validator);
|
||||
ui->lineEdit_obox_maxX->setValidator(&_box_validator);
|
||||
ui->lineEdit_obox_maxY->setValidator(&_box_validator);
|
||||
ui->lineEdit_obox_maxZ->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_minX->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_minY->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_minZ->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_maxX->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_maxY->setValidator(&_box_validator);
|
||||
ui->lineEdit_ibox_maxZ->setValidator(&_box_validator);
|
||||
|
||||
ui->lineEditAvgSpeed->setValidator(&_speed_validator);
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
AsteroidEditorDialog::~AsteroidEditorDialog()
|
||||
{
|
||||
}
|
||||
|
||||
QString & AsteroidEditorDialog::getBoxText(AsteroidEditorDialogModel::_box_line_edits type)
|
||||
{
|
||||
return _model->getBoxText(type);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMinX(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MIN_X);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMinY(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MIN_Y);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMinZ(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MIN_Z);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMaxX(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MAX_X);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMaxY(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MAX_Y);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextIMaxZ(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_I_MAX_Z);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMinX(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MIN_X);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMinY(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MIN_Y);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMinZ(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MIN_Z);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMaxX(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MAX_X);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMaxY(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MAX_Y);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::changedBoxTextOMaxZ(const QString &text)
|
||||
{
|
||||
_model->setBoxText(text, AsteroidEditorDialogModel::_O_MAX_Z);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::setFieldActive()
|
||||
{
|
||||
_model->setFieldType(FT_ACTIVE);
|
||||
setGenreAsteroid(); // only allow asteroids in active fields
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::setFieldPassive()
|
||||
{
|
||||
_model->setFieldType(FT_PASSIVE);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::setGenreAsteroid()
|
||||
{
|
||||
_model->setDebrisGenre(DG_ASTEROID);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::setGenreShip()
|
||||
{
|
||||
_model->setDebrisGenre(DG_SHIP);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::toggleEnabled(bool enabled)
|
||||
{
|
||||
_model->setEnabled(enabled);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::toggleInnerBoxEnabled(bool enabled)
|
||||
{
|
||||
_model->setInnerBoxEnabled(enabled);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::toggleAsteroid(AsteroidEditorDialogModel::_roid_types colour, bool enabled)
|
||||
{
|
||||
_model->setAsteroidEnabled(colour, enabled);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::asteroidNumberChanged(int num_asteroids)
|
||||
{
|
||||
_model->setNumAsteroids(num_asteroids);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::updateComboBox(int idx, int debris_type)
|
||||
{
|
||||
_model->setFieldDebrisType(idx, debris_type);
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::updateUI()
|
||||
{
|
||||
util::SignalBlockers blockers(this);
|
||||
|
||||
// various useful states
|
||||
bool asteroids_enabled = _model->getEnabled();
|
||||
bool inner_box_enabled = _model->getInnerBoxEnabled();
|
||||
bool field_is_active = (_model->getFieldType() == FT_ACTIVE);
|
||||
bool debris_is_asteroid = (_model->getDebrisGenre() == DG_ASTEROID);
|
||||
|
||||
// checkboxes
|
||||
ui->enabled->setChecked(asteroids_enabled);
|
||||
ui->innerBoxEnabled->setChecked(inner_box_enabled);
|
||||
ui->checkBoxBrown->setChecked(_model->getAsteroidEnabled(AsteroidEditorDialogModel::_AST_BROWN));
|
||||
ui->checkBoxBlue->setChecked(_model->getAsteroidEnabled(AsteroidEditorDialogModel::_AST_BLUE));
|
||||
ui->checkBoxOrange->setChecked(_model->getAsteroidEnabled(AsteroidEditorDialogModel::_AST_ORANGE));
|
||||
|
||||
// radio buttons (2x groups)
|
||||
ui->radioButtonActiveField->setChecked(field_is_active);
|
||||
ui->radioButtonPassiveField->setChecked(!field_is_active);
|
||||
ui->radioButtonAsteroid->setChecked(debris_is_asteroid);
|
||||
ui->radioButtonShip->setChecked(!debris_is_asteroid);
|
||||
if (field_is_active) {
|
||||
ui->radioButtonShip->setToolTip(QString("Ship Debris is only allowed in passive fields"));
|
||||
}
|
||||
else {
|
||||
ui->radioButtonShip->setToolTip(QString(""));
|
||||
}
|
||||
|
||||
// enable/disable sections of interface
|
||||
ui->fieldProperties->setEnabled(asteroids_enabled);
|
||||
ui->outerBox->setEnabled(asteroids_enabled);
|
||||
ui->innerBox->setEnabled(asteroids_enabled);
|
||||
|
||||
ui->innerBoxEnabled->setEnabled(asteroids_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_maxX->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_maxY->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_maxZ->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_minX->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_minY->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->lineEdit_ibox_minZ->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_maxX->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_maxY->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_maxZ->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_minX->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_minY->setEnabled(inner_box_enabled && field_is_active);
|
||||
ui->label_ibox_minZ->setEnabled(inner_box_enabled && field_is_active);
|
||||
|
||||
ui->radioButtonShip->setEnabled(!field_is_active);
|
||||
|
||||
ui->checkBoxBrown->setEnabled(debris_is_asteroid);
|
||||
ui->checkBoxBlue->setEnabled(debris_is_asteroid);
|
||||
ui->checkBoxOrange->setEnabled(debris_is_asteroid);
|
||||
|
||||
// speed text
|
||||
ui->lineEditAvgSpeed->setText(_model->AsteroidEditorDialogModel::getAvgSpeed());
|
||||
|
||||
// ship debris comboboxes
|
||||
for (auto i = 0; i < debrisComboBoxes.size(); ++i) {
|
||||
debrisComboBoxes.at(i)->setCurrentIndex(_model->AsteroidEditorDialogModel::getFieldDebrisType(i));
|
||||
debrisComboBoxes.at(i)->setEnabled(!debris_is_asteroid);
|
||||
}
|
||||
|
||||
// mix/max field bounding boxes text
|
||||
ui->lineEdit_obox_minX->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MIN_X));
|
||||
ui->lineEdit_obox_minY->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MIN_Y));
|
||||
ui->lineEdit_obox_minZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MIN_Z));
|
||||
ui->lineEdit_obox_maxX->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MAX_X));
|
||||
ui->lineEdit_obox_maxY->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MAX_Y));
|
||||
ui->lineEdit_obox_maxZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_O_MAX_Z));
|
||||
ui->lineEdit_ibox_minX->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MIN_X));
|
||||
ui->lineEdit_ibox_minY->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MIN_Y));
|
||||
ui->lineEdit_ibox_minZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MIN_Z));
|
||||
ui->lineEdit_ibox_maxX->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MAX_X));
|
||||
ui->lineEdit_ibox_maxY->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MAX_Y));
|
||||
ui->lineEdit_ibox_maxZ->setText(_model->AsteroidEditorDialogModel::getBoxText(AsteroidEditorDialogModel::_I_MAX_Z));
|
||||
}
|
||||
|
||||
void AsteroidEditorDialog::done(int r)
|
||||
{
|
||||
if(QDialog::Accepted == r) // ok was pressed
|
||||
{
|
||||
// TODO consider moving the validation to when values are entered
|
||||
// but just visually indicate that there's a problem at that time
|
||||
// hard fail (by checking status boolean?) here instead
|
||||
// i.e. let FREDers have temp bad values when they're changing stuff
|
||||
// if they know what they're doing
|
||||
if (_model->apply()) {
|
||||
// all ok
|
||||
QDialog::done(r);
|
||||
_model->unset_modified();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// leave dialog open
|
||||
return;
|
||||
}
|
||||
}
|
||||
else // cancel, close or exc was pressed
|
||||
{
|
||||
if (_model->get_modified()) {
|
||||
// give FREDer a chance in case they cancelled by mistake
|
||||
// although I wonder if we're better off with always saving & don't prompt
|
||||
// ~philisophical~
|
||||
auto z = _viewport->dialogProvider->showButtonDialog(DialogType::Question,
|
||||
"Question",
|
||||
"You have unsaved changes, do you wish to discard them?",
|
||||
{ DialogButton::Ok, DialogButton::Cancel });
|
||||
if (z == DialogButton::Cancel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
QDialog::done(r);
|
||||
_model->unset_modified();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace fred
|
||||
} // namespace fso
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <mission/dialogs/AsteroidEditorDialogModel.h>
|
||||
#include <ui/FredView.h>
|
||||
|
||||
namespace fso {
|
||||
namespace fred {
|
||||
namespace dialogs {
|
||||
|
||||
namespace Ui {
|
||||
class AsteroidEditorDialog;
|
||||
}
|
||||
|
||||
class AsteroidEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AsteroidEditorDialog(FredView* parent, EditorViewport* viewport);
|
||||
~AsteroidEditorDialog();
|
||||
|
||||
private:
|
||||
void done(int r);
|
||||
|
||||
void toggleEnabled(bool enabled);
|
||||
void toggleInnerBoxEnabled(bool enabled);
|
||||
void toggleAsteroid(AsteroidEditorDialogModel::_roid_types colour, bool enabled);
|
||||
|
||||
void asteroidNumberChanged(int num_asteroids);
|
||||
|
||||
void setFieldActive();
|
||||
void setFieldPassive();
|
||||
void setGenreAsteroid();
|
||||
void setGenreShip();
|
||||
|
||||
void changedBoxTextIMinX(const QString &text);
|
||||
void changedBoxTextIMinY(const QString &text);
|
||||
void changedBoxTextIMinZ(const QString &text);
|
||||
void changedBoxTextIMaxX(const QString &text);
|
||||
void changedBoxTextIMaxY(const QString &text);
|
||||
void changedBoxTextIMaxZ(const QString &text);
|
||||
void changedBoxTextOMinX(const QString &text);
|
||||
void changedBoxTextOMinY(const QString &text);
|
||||
void changedBoxTextOMinZ(const QString &text);
|
||||
void changedBoxTextOMaxX(const QString &text);
|
||||
void changedBoxTextOMaxY(const QString &text);
|
||||
void changedBoxTextOMaxZ(const QString &text);
|
||||
QString & getBoxText(AsteroidEditorDialogModel::_box_line_edits type);
|
||||
|
||||
void updateComboBox(int idx, int debris_type);
|
||||
void updateUI();
|
||||
|
||||
EditorViewport* _viewport = nullptr;
|
||||
Editor* _editor = nullptr;
|
||||
|
||||
std::unique_ptr<Ui::AsteroidEditorDialog> ui;
|
||||
std::unique_ptr<AsteroidEditorDialogModel> _model;
|
||||
|
||||
QDoubleValidator _box_validator;
|
||||
QIntValidator _speed_validator;
|
||||
|
||||
QList<QComboBox *> debrisComboBoxes;
|
||||
};
|
||||
|
||||
|
||||
} // namespace dialogs
|
||||
} // namespace fred
|
||||
} // namespace fso
|
||||
@@ -0,0 +1,377 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>fso::fred::dialogs::AsteroidEditorDialog</class>
|
||||
<widget class="QDialog" name="fso::fred::dialogs::AsteroidEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>675</width>
|
||||
<height>339</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Debris Field Editor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="enabled">
|
||||
<property name="text">
|
||||
<string>Enabled</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="mainLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="fieldProperties">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Field Properties</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Field Properties</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4">
|
||||
<item row="2" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonActiveField">
|
||||
<property name="text">
|
||||
<string>Active Field</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">radioActivePassive</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QRadioButton" name="radioButtonPassiveField">
|
||||
<property name="text">
|
||||
<string>Passive Field</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">radioActivePassive</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonAsteroid">
|
||||
<property name="text">
|
||||
<string>Asteroid</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">radioAsteroidShip</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QRadioButton" name="radioButtonShip">
|
||||
<property name="text">
|
||||
<string>Ship</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">radioAsteroidShip</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="checkBoxBrown">
|
||||
<property name="text">
|
||||
<string>Brown</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="checkBoxBlue">
|
||||
<property name="text">
|
||||
<string>Blue</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QCheckBox" name="checkBoxOrange">
|
||||
<property name="text">
|
||||
<string>Orange</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<widget class="QComboBox" name="debris0"/>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QComboBox" name="debris1"/>
|
||||
</item>
|
||||
<item row="10" column="2">
|
||||
<widget class="QComboBox" name="debris2"/>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QLabel" name="labelNumber">
|
||||
<property name="text">
|
||||
<string>Number:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="2">
|
||||
<widget class="QSpinBox" name="spinBoxNumber"/>
|
||||
</item>
|
||||
<item row="13" column="0">
|
||||
<widget class="QLabel" name="labelAvgSpeed">
|
||||
<property name="text">
|
||||
<string>Avg Speed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="2">
|
||||
<widget class="QLineEdit" name="lineEditAvgSpeed"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="verticalSpacerFieldProperties">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="outerBox">
|
||||
<property name="title">
|
||||
<string>Outer Box</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_obox_minX">
|
||||
<property name="text">
|
||||
<string>Min X:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_minX"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_obox_maxX">
|
||||
<property name="text">
|
||||
<string>Max X:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_maxX"/>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_obox_minY">
|
||||
<property name="text">
|
||||
<string>Min Y:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_minY"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_obox_maxY">
|
||||
<property name="text">
|
||||
<string>Max Y:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_maxY"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_obox_minZ">
|
||||
<property name="text">
|
||||
<string>Min Z:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_minZ"/>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_obox_maxZ">
|
||||
<property name="text">
|
||||
<string>Max Z:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_obox_maxZ"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="verticalSpacerOuterBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="innerBox">
|
||||
<property name="title">
|
||||
<string>Inner Box</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="innerBoxEnabled">
|
||||
<property name="text">
|
||||
<string>Enabled</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_ibox_minX">
|
||||
<property name="text">
|
||||
<string>Min X:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_minX"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_ibox_maxX">
|
||||
<property name="text">
|
||||
<string>Max X:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_maxX"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_ibox_minY">
|
||||
<property name="text">
|
||||
<string>Min Y:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_minY"/>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_ibox_maxY">
|
||||
<property name="text">
|
||||
<string>Max Y:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_maxY"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_ibox_minZ">
|
||||
<property name="text">
|
||||
<string>Min Z:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_minZ"/>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_ibox_maxZ">
|
||||
<property name="text">
|
||||
<string>Max Z:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_ibox_maxZ"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<spacer name="verticalSpacerInnerBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QDialogButtonBox" name="dialogButtonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>dialogButtonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>fso::fred::dialogs::AsteroidEditorDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>337</x>
|
||||
<y>317</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>337</x>
|
||||
<y>169</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>dialogButtonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>fso::fred::dialogs::AsteroidEditorDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>337</x>
|
||||
<y>317</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>337</x>
|
||||
<y>169</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="radioActivePassive"/>
|
||||
<buttongroup name="radioAsteroidShip"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
@@ -1091,6 +1091,9 @@
|
||||
<property name="text">
|
||||
<string>&Asteroid Field</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Shift+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMission_Specs">
|
||||
<property name="text">
|
||||
|
||||
Reference in new issue
Block a user