mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-25 09:22:33 +00:00
Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into acs
# Conflicts: # src/CMakeLists.txt
This commit is contained in:
commit
d2d7421072
143 changed files with 23128 additions and 9460 deletions
|
|
@ -6,6 +6,8 @@ UseTab: Always
|
|||
TabWidth: 4
|
||||
ColumnLimit: 120
|
||||
AccessModifierOffset: -4
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
|
|
@ -13,10 +15,11 @@ AllowShortFunctionsOnASingleLine: InlineOnly
|
|||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Attach # K&R/OTBS, braces on same line, Java style
|
||||
BreakBeforeBraces: Allman # Always break before braces, to match existing SRB2 code
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
CompactNamespaces: true
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
|
|||
font.c
|
||||
hu_stuff.c
|
||||
i_time.c
|
||||
i_video_common.cpp
|
||||
y_inter.c
|
||||
st_stuff.c
|
||||
m_aatree.c
|
||||
|
|
@ -120,7 +121,6 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
|
|||
k_grandprix.c
|
||||
k_boss.c
|
||||
k_hud.c
|
||||
k_menudef.c
|
||||
k_menufunc.c
|
||||
k_menudraw.c
|
||||
k_terrain.c
|
||||
|
|
@ -225,6 +225,9 @@ target_sources(SRB2SDL2 PRIVATE discord.c stun.c)
|
|||
target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE glad::glad)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE fmt)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE imgui::imgui)
|
||||
|
||||
target_link_libraries(SRB2SDL2 PRIVATE acsvm::acsvm)
|
||||
|
||||
|
|
@ -531,11 +534,15 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
|
|||
endif()
|
||||
|
||||
add_subdirectory(audio)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(hwr2)
|
||||
add_subdirectory(io)
|
||||
add_subdirectory(sdl)
|
||||
add_subdirectory(objects)
|
||||
add_subdirectory(acs)
|
||||
add_subdirectory(rhi)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(menus)
|
||||
|
||||
# strip debug symbols into separate file when using gcc.
|
||||
# to be consistent with Makefile, don't generate for OS X.
|
||||
|
|
|
|||
|
|
@ -30,20 +30,26 @@ using std::size_t;
|
|||
using namespace srb2::audio;
|
||||
using namespace srb2;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
// Utility for leveraging Resampler...
|
||||
class SoundChunkSource : public Source<1> {
|
||||
class SoundChunkSource : public Source<1>
|
||||
{
|
||||
public:
|
||||
explicit SoundChunkSource(std::unique_ptr<SoundChunk>&& chunk)
|
||||
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk)) {}
|
||||
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk))
|
||||
{
|
||||
}
|
||||
|
||||
virtual size_t generate(tcb::span<Sample<1>> buffer) override final {
|
||||
virtual size_t generate(tcb::span<Sample<1>> buffer) override final
|
||||
{
|
||||
if (!chunk_)
|
||||
return 0;
|
||||
|
||||
size_t written = 0;
|
||||
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) {
|
||||
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++)
|
||||
{
|
||||
buffer[written] = chunk_->samples[pos_];
|
||||
written++;
|
||||
}
|
||||
|
|
@ -56,13 +62,15 @@ private:
|
|||
};
|
||||
|
||||
template <class I>
|
||||
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) {
|
||||
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0)
|
||||
{
|
||||
std::vector<Sample<1>> generated;
|
||||
|
||||
size_t total = 0;
|
||||
size_t read = 0;
|
||||
generated.reserve(estimate);
|
||||
do {
|
||||
do
|
||||
{
|
||||
generated.resize(total + 4096);
|
||||
read = source.generate(tcb::span {generated.data() + total, 4096});
|
||||
total += read;
|
||||
|
|
@ -71,7 +79,8 @@ std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) {
|
|||
return generated;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data)
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
|
||||
if (io::remaining(stream) < 8)
|
||||
|
|
@ -90,14 +99,16 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
|
|||
stream.seek(io::SeekFrom::kCurrent, 16);
|
||||
|
||||
std::vector<Sample<1>> samples;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
uint8_t doom_sample = io::read_uint8(stream);
|
||||
float float_sample = audio::sample_to_float(doom_sample);
|
||||
samples.push_back(Sample<1> {float_sample});
|
||||
}
|
||||
size_t samples_len = samples.size();
|
||||
|
||||
if (rate == 44100) {
|
||||
if (rate == 44100)
|
||||
{
|
||||
return SoundChunk {samples};
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +121,8 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
|
|||
size_t total = 0;
|
||||
size_t read = 0;
|
||||
resampled.reserve(samples_len * (static_cast<float>(kSampleRate) / rate));
|
||||
do {
|
||||
do
|
||||
{
|
||||
resampled.resize(total + 4096);
|
||||
read = resampler.generate(tcb::span {resampled.data() + total, 4096});
|
||||
total += read;
|
||||
|
|
@ -120,34 +132,44 @@ optional<SoundChunk> try_load_dmx(tcb::span<std::byte> data) {
|
|||
return SoundChunk {std::move(resampled)};
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_wav(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> try_load_wav(tcb::span<std::byte> data)
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
|
||||
audio::Wav wav;
|
||||
std::size_t sample_rate;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
wav = audio::load_wav(stream);
|
||||
} catch (const std::exception& ex) {
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
sample_rate = wav.sample_rate();
|
||||
|
||||
audio::Resampler<1> resampler(std::make_unique<WavPlayer>(std::move(wav)),
|
||||
sample_rate / static_cast<float>(kSampleRate));
|
||||
audio::Resampler<1> resampler(
|
||||
std::make_unique<WavPlayer>(std::move(wav)),
|
||||
sample_rate / static_cast<float>(kSampleRate)
|
||||
);
|
||||
|
||||
SoundChunk chunk {generate_to_vec(resampler)};
|
||||
return chunk;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data)
|
||||
{
|
||||
std::shared_ptr<audio::OggPlayer<1>> player;
|
||||
try {
|
||||
try
|
||||
{
|
||||
io::SpanStream data_stream {data};
|
||||
audio::Ogg ogg = audio::load_ogg(data_stream);
|
||||
player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg));
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
player->looping(false);
|
||||
|
|
@ -161,19 +183,26 @@ optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) {
|
|||
return chunk;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data)
|
||||
{
|
||||
std::shared_ptr<audio::GmePlayer<1>> player;
|
||||
try {
|
||||
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) {
|
||||
try
|
||||
{
|
||||
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B})
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
player = std::make_shared<GmePlayer<1>>(std::move(gme));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
io::ZlibInputStream stream {io::SpanStream(data)};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
player = std::make_shared<GmePlayer<1>>(std::move(gme));
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
std::vector<Sample<1>> samples {generate_to_vec(*player)};
|
||||
|
|
@ -183,7 +212,8 @@ optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) {
|
|||
|
||||
} // namespace
|
||||
|
||||
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data)
|
||||
{
|
||||
optional<SoundChunk> ret;
|
||||
|
||||
ret = try_load_dmx(data);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
#include "sound_chunk.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
/// @brief Try to load a chunk from the given byte span.
|
||||
std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ using namespace srb2::audio;
|
|||
|
||||
ExpandMono::~ExpandMono() = default;
|
||||
|
||||
size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) {
|
||||
for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++) {
|
||||
size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer)
|
||||
{
|
||||
for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++)
|
||||
{
|
||||
buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0];
|
||||
buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@
|
|||
|
||||
#include "filter.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class ExpandMono : public Filter<1, 2> {
|
||||
class ExpandMono : public Filter<1, 2>
|
||||
{
|
||||
public:
|
||||
virtual ~ExpandMono();
|
||||
virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ using srb2::audio::Sample;
|
|||
using srb2::audio::Source;
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) {
|
||||
size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer)
|
||||
{
|
||||
input_buffer_.clear();
|
||||
input_buffer_.resize(buffer.size());
|
||||
|
||||
|
|
@ -27,7 +28,8 @@ size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input) {
|
||||
void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input)
|
||||
{
|
||||
input_ = input;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@
|
|||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
class Filter : public Source<OC> {
|
||||
class Filter : public Source<OC>
|
||||
{
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ using srb2::audio::Sample;
|
|||
constexpr const float kGainInterpolationAlpha = 0.8f;
|
||||
|
||||
template <size_t C>
|
||||
size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) {
|
||||
size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
size_t written = std::min(buffer.size(), input_buffer.size());
|
||||
for (size_t i = 0; i < written; i++) {
|
||||
for (size_t i = 0; i < written; i++)
|
||||
{
|
||||
buffer[i] = input_buffer[i];
|
||||
buffer[i] *= gain_;
|
||||
gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha;
|
||||
|
|
@ -32,7 +34,8 @@ size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> b
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void Gain<C>::gain(float new_gain) {
|
||||
void Gain<C>::gain(float new_gain)
|
||||
{
|
||||
new_gain_ = std::clamp(new_gain, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@
|
|||
|
||||
#include "filter.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class Gain : public Filter<C, C> {
|
||||
class Gain : public Filter<C, C>
|
||||
{
|
||||
public:
|
||||
virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final;
|
||||
void gain(float new_gain);
|
||||
|
|
|
|||
|
|
@ -17,37 +17,45 @@
|
|||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
Gme::Gme() : memory_data_(), instance_(nullptr) {
|
||||
Gme::Gme() : memory_data_(), instance_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr) {
|
||||
Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr)
|
||||
{
|
||||
std::swap(memory_data_, rhs.memory_data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
}
|
||||
|
||||
Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr) {
|
||||
Gme::Gme(std::vector<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr)
|
||||
{
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
|
||||
Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr)
|
||||
{
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Gme& Gme::operator=(Gme&& rhs) noexcept {
|
||||
Gme& Gme::operator=(Gme&& rhs) noexcept
|
||||
{
|
||||
std::swap(memory_data_, rhs.memory_data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gme::~Gme() {
|
||||
if (instance_) {
|
||||
Gme::~Gme()
|
||||
{
|
||||
if (instance_)
|
||||
{
|
||||
gme_delete(instance_);
|
||||
instance_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Gme::get_samples(tcb::span<short> buffer) {
|
||||
std::size_t Gme::get_samples(tcb::span<short> buffer)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
gme_err_t err = gme_play(instance_, buffer.size(), buffer.data());
|
||||
|
|
@ -57,13 +65,15 @@ std::size_t Gme::get_samples(tcb::span<short> buffer) {
|
|||
return buffer.size();
|
||||
}
|
||||
|
||||
void Gme::seek(int sample) {
|
||||
void Gme::seek(int sample)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
gme_seek_samples(instance_, sample);
|
||||
}
|
||||
|
||||
std::optional<float> Gme::duration_seconds() const {
|
||||
std::optional<float> Gme::duration_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
gme_info_t* info = nullptr;
|
||||
|
|
@ -79,7 +89,8 @@ std::optional<float> Gme::duration_seconds() const {
|
|||
return static_cast<float>(info->length) / 1000.f;
|
||||
}
|
||||
|
||||
std::optional<float> Gme::loop_point_seconds() const {
|
||||
std::optional<float> Gme::loop_point_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
gme_info_t* info = nullptr;
|
||||
|
|
@ -95,7 +106,8 @@ std::optional<float> Gme::loop_point_seconds() const {
|
|||
return loop_point_ms / 44100.f;
|
||||
}
|
||||
|
||||
float Gme::position_seconds() const {
|
||||
float Gme::position_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
gme_info_t* info = nullptr;
|
||||
|
|
@ -117,8 +129,10 @@ float Gme::position_seconds() const {
|
|||
return position / 1000.f;
|
||||
}
|
||||
|
||||
void Gme::_init_with_data() {
|
||||
if (instance_) {
|
||||
void Gme::_init_with_data()
|
||||
{
|
||||
if (instance_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@
|
|||
|
||||
#include "../io/streams.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class GmeException : public std::exception {
|
||||
class GmeException : public std::exception
|
||||
{
|
||||
std::string msg_;
|
||||
|
||||
public:
|
||||
|
|
@ -35,7 +37,8 @@ public:
|
|||
virtual const char* what() const noexcept override { return msg_.c_str(); }
|
||||
};
|
||||
|
||||
class Gme {
|
||||
class Gme
|
||||
{
|
||||
std::vector<std::byte> memory_data_;
|
||||
Music_Emu* instance_;
|
||||
|
||||
|
|
@ -64,7 +67,8 @@ private:
|
|||
};
|
||||
|
||||
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Gme load_gme(I& stream) {
|
||||
inline Gme load_gme(I& stream)
|
||||
{
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Gme {std::move(data)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ using namespace srb2;
|
|||
using namespace srb2::audio;
|
||||
|
||||
template <size_t C>
|
||||
GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_() {
|
||||
GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_()
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
|
|
@ -26,17 +27,22 @@ template <size_t C>
|
|||
GmePlayer<C>::~GmePlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
buf_.clear();
|
||||
buf_.resize(buffer.size() * 2);
|
||||
|
||||
std::size_t read = gme_.get_samples(tcb::make_span(buf_));
|
||||
buf_.resize(read);
|
||||
std::size_t new_samples = std::min((read / 2), buffer.size());
|
||||
for (std::size_t i = 0; i < new_samples; i++) {
|
||||
if constexpr (C == 1) {
|
||||
for (std::size_t i = 0; i < new_samples; i++)
|
||||
{
|
||||
if constexpr (C == 1)
|
||||
{
|
||||
buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f;
|
||||
} else if constexpr (C == 2) {
|
||||
}
|
||||
else if constexpr (C == 2)
|
||||
{
|
||||
buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f;
|
||||
buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f;
|
||||
}
|
||||
|
|
@ -45,27 +51,32 @@ std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void GmePlayer<C>::seek(float position_seconds) {
|
||||
void GmePlayer<C>::seek(float position_seconds)
|
||||
{
|
||||
gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void GmePlayer<C>::reset() {
|
||||
void GmePlayer<C>::reset()
|
||||
{
|
||||
gme_.seek(0);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> GmePlayer<C>::duration_seconds() const {
|
||||
std::optional<float> GmePlayer<C>::duration_seconds() const
|
||||
{
|
||||
return gme_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> GmePlayer<C>::loop_point_seconds() const {
|
||||
std::optional<float> GmePlayer<C>::loop_point_seconds() const
|
||||
{
|
||||
return gme_.loop_point_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float GmePlayer<C>::position_seconds() const {
|
||||
float GmePlayer<C>::position_seconds() const
|
||||
{
|
||||
return gme_.position_seconds();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,12 @@
|
|||
#include "gme.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class GmePlayer : public Source<C> {
|
||||
class GmePlayer : public Source<C>
|
||||
{
|
||||
Gme gme_;
|
||||
std::vector<short> buf_;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,16 +18,20 @@ using srb2::audio::Mixer;
|
|||
using srb2::audio::Sample;
|
||||
using srb2::audio::Source;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
void default_init_sample_buffer(Sample<C>* buffer, size_t size) {
|
||||
void default_init_sample_buffer(Sample<C>* buffer, size_t size)
|
||||
{
|
||||
std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; });
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size) {
|
||||
for (size_t i = 0; i < size && i < src_size; i++) {
|
||||
void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size)
|
||||
{
|
||||
for (size_t i = 0; i < size && i < src_size; i++)
|
||||
{
|
||||
dst[i] += src[i];
|
||||
}
|
||||
}
|
||||
|
|
@ -35,12 +39,14 @@ void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_
|
|||
} // namespace
|
||||
|
||||
template <size_t C>
|
||||
size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
buffer_.resize(buffer.size());
|
||||
|
||||
default_init_sample_buffer<C>(buffer.data(), buffer.size());
|
||||
|
||||
for (auto& source : sources_) {
|
||||
for (auto& source : sources_)
|
||||
{
|
||||
size_t read = source->generate(buffer_);
|
||||
|
||||
mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read);
|
||||
|
|
@ -51,7 +57,8 @@ size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void Mixer<C>::add_source(const shared_ptr<Source<C>>& source) {
|
||||
void Mixer<C>::add_source(const shared_ptr<Source<C>>& source)
|
||||
{
|
||||
sources_.push_back(source);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@
|
|||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class Mixer : public Source<C> {
|
||||
class Mixer : public Source<C>
|
||||
{
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,12 +43,14 @@ using srb2::audio::Sample;
|
|||
using srb2::audio::Source;
|
||||
using namespace srb2;
|
||||
|
||||
class MusicPlayer::Impl {
|
||||
class MusicPlayer::Impl
|
||||
{
|
||||
public:
|
||||
Impl() = default;
|
||||
Impl(tcb::span<std::byte> data) : Impl() { _load(data); }
|
||||
|
||||
size_t generate(tcb::span<Sample<2>> buffer) {
|
||||
size_t generate(tcb::span<Sample<2>> buffer)
|
||||
{
|
||||
if (!resampler_)
|
||||
return 0;
|
||||
|
||||
|
|
@ -57,12 +59,14 @@ public:
|
|||
|
||||
size_t total_written = 0;
|
||||
|
||||
while (total_written < buffer.size()) {
|
||||
while (total_written < buffer.size())
|
||||
{
|
||||
const size_t generated = resampler_->generate(buffer.subspan(total_written));
|
||||
|
||||
// To avoid a branch preventing optimizations, we're always going to apply
|
||||
// the fade gain, even if it would clamp anyway.
|
||||
for (std::size_t i = 0; i < generated; i++) {
|
||||
for (std::size_t i = 0; i < generated; i++)
|
||||
{
|
||||
const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) /
|
||||
static_cast<double>(gain_samples_target_);
|
||||
const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_;
|
||||
|
|
@ -71,7 +75,8 @@ public:
|
|||
|
||||
gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_);
|
||||
|
||||
if (gain_samples_ >= gain_samples_target_) {
|
||||
if (gain_samples_ >= gain_samples_target_)
|
||||
{
|
||||
fading_ = false;
|
||||
gain_samples_ = gain_samples_target_;
|
||||
gain_ = gain_target_;
|
||||
|
|
@ -79,7 +84,8 @@ public:
|
|||
|
||||
total_written += generated;
|
||||
|
||||
if (generated == 0) {
|
||||
if (generated == 0)
|
||||
{
|
||||
playing_ = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -88,53 +94,68 @@ public:
|
|||
return total_written;
|
||||
}
|
||||
|
||||
void _load(tcb::span<std::byte> data) {
|
||||
void _load(tcb::span<std::byte> data)
|
||||
{
|
||||
ogg_inst_ = nullptr;
|
||||
gme_inst_ = nullptr;
|
||||
xmp_inst_ = nullptr;
|
||||
resampler_ = std::nullopt;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
audio::Ogg ogg = audio::load_ogg(stream);
|
||||
ogg_inst_ = std::make_shared<audio::OggPlayer<2>>(std::move(ogg));
|
||||
ogg_inst_->looping(looping_);
|
||||
resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f);
|
||||
} catch (const std::exception& ex) {
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
// it's probably not ogg
|
||||
ogg_inst_ = nullptr;
|
||||
resampler_ = std::nullopt;
|
||||
}
|
||||
|
||||
if (!resampler_) {
|
||||
try {
|
||||
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) {
|
||||
if (!resampler_)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B})
|
||||
{
|
||||
io::ZlibInputStream stream {io::SpanStream(data)};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
|
||||
}
|
||||
|
||||
resampler_ = Resampler<2>(gme_inst_, 1.f);
|
||||
} catch (const std::exception& ex) {
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
// it's probably not gme
|
||||
gme_inst_ = nullptr;
|
||||
resampler_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resampler_) {
|
||||
try {
|
||||
if (!resampler_)
|
||||
{
|
||||
try
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
audio::Xmp<2> xmp = audio::load_xmp<2>(stream);
|
||||
xmp_inst_ = std::make_shared<XmpPlayer<2>>(std::move(xmp));
|
||||
xmp_inst_->looping(looping_);
|
||||
|
||||
resampler_ = Resampler<2>(xmp_inst_, 1.f);
|
||||
} catch (const std::exception& ex) {
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
// it's probably not xmp
|
||||
xmp_inst_ = nullptr;
|
||||
resampler_ = std::nullopt;
|
||||
|
|
@ -146,74 +167,103 @@ public:
|
|||
internal_gain(1.f);
|
||||
}
|
||||
|
||||
void play(bool looping) {
|
||||
if (ogg_inst_) {
|
||||
void play(bool looping)
|
||||
{
|
||||
if (ogg_inst_)
|
||||
{
|
||||
ogg_inst_->looping(looping);
|
||||
ogg_inst_->playing(true);
|
||||
playing_ = true;
|
||||
ogg_inst_->reset();
|
||||
} else if (gme_inst_) {
|
||||
}
|
||||
else if (gme_inst_)
|
||||
{
|
||||
playing_ = true;
|
||||
gme_inst_->reset();
|
||||
} else if (xmp_inst_) {
|
||||
}
|
||||
else if (xmp_inst_)
|
||||
{
|
||||
xmp_inst_->looping(looping);
|
||||
playing_ = true;
|
||||
xmp_inst_->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void unpause() {
|
||||
if (ogg_inst_) {
|
||||
void unpause()
|
||||
{
|
||||
if (ogg_inst_)
|
||||
{
|
||||
ogg_inst_->playing(true);
|
||||
playing_ = true;
|
||||
} else if (gme_inst_) {
|
||||
}
|
||||
else if (gme_inst_)
|
||||
{
|
||||
playing_ = true;
|
||||
} else if (xmp_inst_) {
|
||||
}
|
||||
else if (xmp_inst_)
|
||||
{
|
||||
playing_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
if (ogg_inst_) {
|
||||
void pause()
|
||||
{
|
||||
if (ogg_inst_)
|
||||
{
|
||||
ogg_inst_->playing(false);
|
||||
playing_ = false;
|
||||
} else if (gme_inst_) {
|
||||
}
|
||||
else if (gme_inst_)
|
||||
{
|
||||
playing_ = false;
|
||||
} else if (xmp_inst_) {
|
||||
}
|
||||
else if (xmp_inst_)
|
||||
{
|
||||
playing_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (ogg_inst_) {
|
||||
void stop()
|
||||
{
|
||||
if (ogg_inst_)
|
||||
{
|
||||
ogg_inst_->reset();
|
||||
ogg_inst_->playing(false);
|
||||
playing_ = false;
|
||||
} else if (gme_inst_) {
|
||||
}
|
||||
else if (gme_inst_)
|
||||
{
|
||||
gme_inst_->reset();
|
||||
playing_ = false;
|
||||
} else if (xmp_inst_) {
|
||||
}
|
||||
else if (xmp_inst_)
|
||||
{
|
||||
xmp_inst_->reset();
|
||||
playing_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void seek(float position_seconds) {
|
||||
if (ogg_inst_) {
|
||||
void seek(float position_seconds)
|
||||
{
|
||||
if (ogg_inst_)
|
||||
{
|
||||
ogg_inst_->seek(position_seconds);
|
||||
return;
|
||||
}
|
||||
if (gme_inst_) {
|
||||
if (gme_inst_)
|
||||
{
|
||||
gme_inst_->seek(position_seconds);
|
||||
return;
|
||||
}
|
||||
if (xmp_inst_) {
|
||||
if (xmp_inst_)
|
||||
{
|
||||
xmp_inst_->seek(position_seconds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool playing() const {
|
||||
bool playing() const
|
||||
{
|
||||
if (ogg_inst_)
|
||||
return ogg_inst_->playing();
|
||||
else if (gme_inst_)
|
||||
|
|
@ -224,7 +274,8 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
std::optional<audio::MusicType> music_type() const {
|
||||
std::optional<audio::MusicType> music_type() const
|
||||
{
|
||||
if (ogg_inst_)
|
||||
return audio::MusicType::kOgg;
|
||||
else if (gme_inst_)
|
||||
|
|
@ -235,7 +286,8 @@ public:
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> duration_seconds() const {
|
||||
std::optional<float> duration_seconds() const
|
||||
{
|
||||
if (ogg_inst_)
|
||||
return ogg_inst_->duration_seconds();
|
||||
if (gme_inst_)
|
||||
|
|
@ -246,7 +298,8 @@ public:
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> loop_point_seconds() const {
|
||||
std::optional<float> loop_point_seconds() const
|
||||
{
|
||||
if (ogg_inst_)
|
||||
return ogg_inst_->loop_point_seconds();
|
||||
if (gme_inst_)
|
||||
|
|
@ -255,7 +308,8 @@ public:
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<float> position_seconds() const {
|
||||
std::optional<float> position_seconds() const
|
||||
{
|
||||
if (ogg_inst_)
|
||||
return ogg_inst_->position_seconds();
|
||||
if (gme_inst_)
|
||||
|
|
@ -266,13 +320,14 @@ public:
|
|||
|
||||
void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); }
|
||||
|
||||
void fade_from_to(float from, float to, float seconds) {
|
||||
void fade_from_to(float from, float to, float seconds)
|
||||
{
|
||||
fading_ = true;
|
||||
gain_ = from;
|
||||
gain_target_ = to;
|
||||
// Gain samples target must always be at least 1 to avoid a div-by-zero.
|
||||
gain_samples_target_ = std::max(
|
||||
static_cast<uint64_t>(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal
|
||||
gain_samples_target_ =
|
||||
std::max(static_cast<uint64_t>(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal
|
||||
gain_samples_ = 0;
|
||||
}
|
||||
|
||||
|
|
@ -280,12 +335,14 @@ public:
|
|||
|
||||
void stop_fade() { internal_gain(gain_target_); }
|
||||
|
||||
void loop_point_seconds(float loop_point) {
|
||||
void loop_point_seconds(float loop_point)
|
||||
{
|
||||
if (ogg_inst_)
|
||||
ogg_inst_->loop_point_seconds(loop_point);
|
||||
}
|
||||
|
||||
void internal_gain(float gain) {
|
||||
void internal_gain(float gain)
|
||||
{
|
||||
fading_ = false;
|
||||
gain_ = gain;
|
||||
gain_target_ = gain;
|
||||
|
|
@ -310,112 +367,131 @@ private:
|
|||
};
|
||||
|
||||
// The special member functions MUST be declared in this unit, where Impl is complete.
|
||||
MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>()) {
|
||||
MusicPlayer::MusicPlayer() : impl_(make_unique<MusicPlayer::Impl>())
|
||||
{
|
||||
}
|
||||
MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data)) {
|
||||
MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data))
|
||||
{
|
||||
}
|
||||
MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default;
|
||||
MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default;
|
||||
|
||||
MusicPlayer::~MusicPlayer() = default;
|
||||
|
||||
void MusicPlayer::play(bool looping) {
|
||||
void MusicPlayer::play(bool looping)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->play(looping);
|
||||
}
|
||||
|
||||
void MusicPlayer::unpause() {
|
||||
void MusicPlayer::unpause()
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->unpause();
|
||||
}
|
||||
|
||||
void MusicPlayer::pause() {
|
||||
void MusicPlayer::pause()
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->pause();
|
||||
}
|
||||
|
||||
void MusicPlayer::stop() {
|
||||
void MusicPlayer::stop()
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->stop();
|
||||
}
|
||||
|
||||
void MusicPlayer::seek(float position_seconds) {
|
||||
void MusicPlayer::seek(float position_seconds)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->seek(position_seconds);
|
||||
}
|
||||
|
||||
bool MusicPlayer::playing() const {
|
||||
bool MusicPlayer::playing() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->playing();
|
||||
}
|
||||
|
||||
size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer) {
|
||||
size_t MusicPlayer::generate(tcb::span<Sample<2>> buffer)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->generate(buffer);
|
||||
}
|
||||
|
||||
std::optional<audio::MusicType> MusicPlayer::music_type() const {
|
||||
std::optional<audio::MusicType> MusicPlayer::music_type() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->music_type();
|
||||
}
|
||||
|
||||
std::optional<float> MusicPlayer::duration_seconds() const {
|
||||
std::optional<float> MusicPlayer::duration_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->duration_seconds();
|
||||
}
|
||||
|
||||
std::optional<float> MusicPlayer::loop_point_seconds() const {
|
||||
std::optional<float> MusicPlayer::loop_point_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->loop_point_seconds();
|
||||
}
|
||||
|
||||
std::optional<float> MusicPlayer::position_seconds() const {
|
||||
std::optional<float> MusicPlayer::position_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->position_seconds();
|
||||
}
|
||||
|
||||
void MusicPlayer::fade_to(float gain, float seconds) {
|
||||
void MusicPlayer::fade_to(float gain, float seconds)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
impl_->fade_to(gain, seconds);
|
||||
}
|
||||
|
||||
void MusicPlayer::fade_from_to(float from, float to, float seconds) {
|
||||
void MusicPlayer::fade_from_to(float from, float to, float seconds)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
impl_->fade_from_to(from, to, seconds);
|
||||
}
|
||||
|
||||
void MusicPlayer::internal_gain(float gain) {
|
||||
void MusicPlayer::internal_gain(float gain)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
impl_->internal_gain(gain);
|
||||
}
|
||||
|
||||
bool MusicPlayer::fading() const {
|
||||
bool MusicPlayer::fading() const
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->fading();
|
||||
}
|
||||
|
||||
void MusicPlayer::stop_fade() {
|
||||
void MusicPlayer::stop_fade()
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
impl_->stop_fade();
|
||||
}
|
||||
|
||||
void MusicPlayer::loop_point_seconds(float loop_point) {
|
||||
void MusicPlayer::loop_point_seconds(float loop_point)
|
||||
{
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
impl_->loop_point_seconds(loop_point);
|
||||
|
|
|
|||
|
|
@ -19,15 +19,18 @@
|
|||
|
||||
struct stb_vorbis;
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
enum class MusicType {
|
||||
enum class MusicType
|
||||
{
|
||||
kOgg,
|
||||
kGme,
|
||||
kMod
|
||||
};
|
||||
|
||||
class MusicPlayer : public Source<2> {
|
||||
class MusicPlayer : public Source<2>
|
||||
{
|
||||
public:
|
||||
MusicPlayer();
|
||||
MusicPlayer(tcb::span<std::byte> data);
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
StbVorbisException::StbVorbisException(int code) noexcept : code_(code) {
|
||||
StbVorbisException::StbVorbisException(int code) noexcept : code_(code)
|
||||
{
|
||||
}
|
||||
|
||||
const char* StbVorbisException::what() const noexcept {
|
||||
switch (code_) {
|
||||
const char* StbVorbisException::what() const noexcept
|
||||
{
|
||||
switch (code_)
|
||||
{
|
||||
case VORBIS__no_error:
|
||||
return "No error";
|
||||
case VORBIS_need_more_data:
|
||||
|
|
@ -68,54 +71,73 @@ const char* StbVorbisException::what() const noexcept {
|
|||
}
|
||||
}
|
||||
|
||||
Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) {
|
||||
Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr) {
|
||||
Ogg::Ogg(std::vector<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr)
|
||||
{
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
|
||||
Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr)
|
||||
{
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) {
|
||||
Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr)
|
||||
{
|
||||
std::swap(memory_data_, rhs.memory_data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
}
|
||||
|
||||
Ogg& Ogg::operator=(Ogg&& rhs) noexcept {
|
||||
Ogg& Ogg::operator=(Ogg&& rhs) noexcept
|
||||
{
|
||||
std::swap(memory_data_, rhs.memory_data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Ogg::~Ogg() {
|
||||
if (instance_) {
|
||||
Ogg::~Ogg()
|
||||
{
|
||||
if (instance_)
|
||||
{
|
||||
stb_vorbis_close(instance_);
|
||||
instance_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer) {
|
||||
std::size_t Ogg::get_samples(tcb::span<Sample<1>> buffer)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
size_t read = stb_vorbis_get_samples_float_interleaved(
|
||||
instance_, 1, reinterpret_cast<float*>(buffer.data()), buffer.size() * 1);
|
||||
instance_,
|
||||
1,
|
||||
reinterpret_cast<float*>(buffer.data()),
|
||||
buffer.size() * 1
|
||||
);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) {
|
||||
std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
size_t read = stb_vorbis_get_samples_float_interleaved(
|
||||
instance_, 2, reinterpret_cast<float*>(buffer.data()), buffer.size() * 2);
|
||||
instance_,
|
||||
2,
|
||||
reinterpret_cast<float*>(buffer.data()),
|
||||
buffer.size() * 2
|
||||
);
|
||||
|
||||
stb_vorbis_info info = stb_vorbis_get_info(instance_);
|
||||
if (info.channels == 1) {
|
||||
for (auto& sample : buffer.subspan(0, read)) {
|
||||
if (info.channels == 1)
|
||||
{
|
||||
for (auto& sample : buffer.subspan(0, read))
|
||||
{
|
||||
sample.amplitudes[1] = sample.amplitudes[0];
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +145,8 @@ std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) {
|
|||
return read;
|
||||
}
|
||||
|
||||
OggComment Ogg::comment() const {
|
||||
OggComment Ogg::comment() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_);
|
||||
|
|
@ -133,50 +156,59 @@ OggComment Ogg::comment() const {
|
|||
std::vector<std::string>(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)};
|
||||
}
|
||||
|
||||
std::size_t Ogg::sample_rate() const {
|
||||
std::size_t Ogg::sample_rate() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
stb_vorbis_info info = stb_vorbis_get_info(instance_);
|
||||
return info.sample_rate;
|
||||
}
|
||||
|
||||
void Ogg::seek(std::size_t sample) {
|
||||
void Ogg::seek(std::size_t sample)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
stb_vorbis_seek(instance_, sample);
|
||||
}
|
||||
|
||||
std::size_t Ogg::position() const {
|
||||
std::size_t Ogg::position() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
return stb_vorbis_get_sample_offset(instance_);
|
||||
}
|
||||
|
||||
float Ogg::position_seconds() const {
|
||||
float Ogg::position_seconds() const
|
||||
{
|
||||
return position() / static_cast<float>(sample_rate());
|
||||
}
|
||||
|
||||
std::size_t Ogg::duration_samples() const {
|
||||
std::size_t Ogg::duration_samples() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
return stb_vorbis_stream_length_in_samples(instance_);
|
||||
}
|
||||
|
||||
float Ogg::duration_seconds() const {
|
||||
float Ogg::duration_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
return stb_vorbis_stream_length_in_seconds(instance_);
|
||||
}
|
||||
|
||||
std::size_t Ogg::channels() const {
|
||||
std::size_t Ogg::channels() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
stb_vorbis_info info = stb_vorbis_get_info(instance_);
|
||||
return info.channels;
|
||||
}
|
||||
|
||||
void Ogg::_init_with_data() {
|
||||
if (instance_) {
|
||||
void Ogg::_init_with_data()
|
||||
{
|
||||
if (instance_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +219,11 @@ void Ogg::_init_with_data() {
|
|||
|
||||
int vorbis_result;
|
||||
instance_ = stb_vorbis_open_memory(
|
||||
reinterpret_cast<const unsigned char*>(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL);
|
||||
reinterpret_cast<const unsigned char*>(memory_data_.data()),
|
||||
memory_data_.size(),
|
||||
&vorbis_result,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (vorbis_result != VORBIS__no_error)
|
||||
throw StbVorbisException(vorbis_result);
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@
|
|||
#include "../io/streams.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class StbVorbisException final : public std::exception {
|
||||
class StbVorbisException final : public std::exception
|
||||
{
|
||||
int code_;
|
||||
|
||||
public:
|
||||
|
|
@ -31,12 +33,14 @@ public:
|
|||
virtual const char* what() const noexcept;
|
||||
};
|
||||
|
||||
struct OggComment {
|
||||
struct OggComment
|
||||
{
|
||||
std::string vendor;
|
||||
std::vector<std::string> comments;
|
||||
};
|
||||
|
||||
class Ogg final {
|
||||
class Ogg final
|
||||
{
|
||||
std::vector<std::byte> memory_data_;
|
||||
stb_vorbis* instance_;
|
||||
|
||||
|
|
@ -71,7 +75,8 @@ private:
|
|||
};
|
||||
|
||||
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Ogg load_ogg(I& stream) {
|
||||
inline Ogg load_ogg(I& stream)
|
||||
{
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Ogg {std::move(data)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,35 +18,46 @@
|
|||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
std::optional<std::size_t> find_loop_point(const Ogg& ogg) {
|
||||
std::optional<std::size_t> find_loop_point(const Ogg& ogg)
|
||||
{
|
||||
OggComment comment = ogg.comment();
|
||||
std::size_t rate = ogg.sample_rate();
|
||||
for (auto& comment : comment.comments) {
|
||||
if (comment.find("LOOPPOINT=") == 0) {
|
||||
for (auto& comment : comment.comments)
|
||||
{
|
||||
if (comment.find("LOOPPOINT=") == 0)
|
||||
{
|
||||
std::string_view comment_view(comment);
|
||||
comment_view.remove_prefix(10);
|
||||
std::string copied {comment_view};
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
int loop_point = std::stoi(copied);
|
||||
return loop_point;
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (comment.find("LOOPMS=") == 0) {
|
||||
if (comment.find("LOOPMS=") == 0)
|
||||
{
|
||||
std::string_view comment_view(comment);
|
||||
comment_view.remove_prefix(7);
|
||||
std::string copied {comment_view};
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
int loop_ms = std::stoi(copied);
|
||||
int loop_point = std::round(static_cast<double>(loop_ms) / (rate / 1000.));
|
||||
int loop_point = std::round(static_cast<double>(rate) * (loop_ms / 1000.));
|
||||
|
||||
return loop_point;
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +69,8 @@ std::optional<std::size_t> find_loop_point(const Ogg& ogg) {
|
|||
|
||||
template <size_t C>
|
||||
OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept
|
||||
: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg)) {
|
||||
: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg))
|
||||
{
|
||||
loop_point_ = find_loop_point(ogg_);
|
||||
}
|
||||
|
||||
|
|
@ -72,25 +84,30 @@ template <size_t C>
|
|||
OggPlayer<C>::~OggPlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
if (!playing_)
|
||||
return 0;
|
||||
|
||||
std::size_t total = 0;
|
||||
do {
|
||||
do
|
||||
{
|
||||
std::size_t read = ogg_.get_samples(buffer.subspan(total));
|
||||
total += read;
|
||||
|
||||
if (read == 0 && !looping_) {
|
||||
if (read == 0 && !looping_)
|
||||
{
|
||||
playing_ = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (read == 0 && loop_point_) {
|
||||
if (read == 0 && loop_point_)
|
||||
{
|
||||
ogg_.seek(*loop_point_);
|
||||
}
|
||||
|
||||
if (read == 0 && !loop_point_) {
|
||||
if (read == 0 && !loop_point_)
|
||||
{
|
||||
ogg_.seek(0);
|
||||
}
|
||||
} while (total < buffer.size());
|
||||
|
|
@ -99,33 +116,39 @@ std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void OggPlayer<C>::seek(float position_seconds) {
|
||||
void OggPlayer<C>::seek(float position_seconds)
|
||||
{
|
||||
ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate()));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void OggPlayer<C>::loop_point_seconds(float loop_point) {
|
||||
void OggPlayer<C>::loop_point_seconds(float loop_point)
|
||||
{
|
||||
std::size_t rate = sample_rate();
|
||||
loop_point = static_cast<std::size_t>(std::round(loop_point * rate));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void OggPlayer<C>::reset() {
|
||||
void OggPlayer<C>::reset()
|
||||
{
|
||||
ogg_.seek(0);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::size_t OggPlayer<C>::sample_rate() const {
|
||||
std::size_t OggPlayer<C>::sample_rate() const
|
||||
{
|
||||
return ogg_.sample_rate();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float OggPlayer<C>::duration_seconds() const {
|
||||
float OggPlayer<C>::duration_seconds() const
|
||||
{
|
||||
return ogg_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> OggPlayer<C>::loop_point_seconds() const {
|
||||
std::optional<float> OggPlayer<C>::loop_point_seconds() const
|
||||
{
|
||||
if (!loop_point_)
|
||||
return std::nullopt;
|
||||
|
||||
|
|
@ -133,7 +156,8 @@ std::optional<float> OggPlayer<C>::loop_point_seconds() const {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
float OggPlayer<C>::position_seconds() const {
|
||||
float OggPlayer<C>::position_seconds() const
|
||||
{
|
||||
return ogg_.position_seconds();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,12 @@
|
|||
#include "ogg.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class OggPlayer final : public Source<C> {
|
||||
class OggPlayer final : public Source<C>
|
||||
{
|
||||
bool playing_;
|
||||
bool looping_;
|
||||
std::optional<std::size_t> loop_point_;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ using namespace srb2::audio;
|
|||
|
||||
template <size_t C>
|
||||
Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio)
|
||||
: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio) {
|
||||
: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio)
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
|
|
@ -36,11 +37,13 @@ template <size_t C>
|
|||
Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default;
|
||||
|
||||
template <size_t C>
|
||||
size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
if (!source_)
|
||||
return 0;
|
||||
|
||||
if (ratio_ == 1.f) {
|
||||
if (ratio_ == 1.f)
|
||||
{
|
||||
// fast path - generate directly from source
|
||||
size_t source_read = source_->generate(buffer);
|
||||
return source_read;
|
||||
|
|
@ -48,21 +51,25 @@ size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) {
|
|||
|
||||
size_t written = 0;
|
||||
|
||||
while (written < buffer.size()) {
|
||||
while (written < buffer.size())
|
||||
{
|
||||
// do we need a refill?
|
||||
if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1)) {
|
||||
if (buf_.size() == 0 || pos_ >= static_cast<int>(buf_.size() - 1))
|
||||
{
|
||||
pos_ -= buf_.size();
|
||||
last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back();
|
||||
buf_.clear();
|
||||
buf_.resize(512);
|
||||
size_t source_read = source_->generate(buf_);
|
||||
buf_.resize(source_read);
|
||||
if (source_read == 0) {
|
||||
if (source_read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos_ < 0) {
|
||||
if (pos_ < 0)
|
||||
{
|
||||
buffer[written] = (buf_[0] - last_) * pos_frac_ + last_;
|
||||
advance(ratio_);
|
||||
written++;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,12 @@
|
|||
#include "sound_chunk.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class Resampler : public Source<C> {
|
||||
class Resampler : public Source<C>
|
||||
{
|
||||
public:
|
||||
Resampler(std::shared_ptr<Source<C>>&& source_, float ratio);
|
||||
Resampler(const Resampler<C>& r) = delete;
|
||||
|
|
@ -44,7 +46,8 @@ private:
|
|||
int pos_ {0};
|
||||
float pos_frac_ {0.f};
|
||||
|
||||
void advance(float samples) {
|
||||
void advance(float samples)
|
||||
{
|
||||
pos_frac_ += samples;
|
||||
float integer;
|
||||
std::modf(pos_frac_, &integer);
|
||||
|
|
|
|||
|
|
@ -12,21 +12,27 @@
|
|||
|
||||
#include <cstddef>
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
struct Sample {
|
||||
struct Sample
|
||||
{
|
||||
std::array<float, C> amplitudes;
|
||||
|
||||
constexpr Sample& operator+=(const Sample& rhs) noexcept {
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
constexpr Sample& operator+=(const Sample& rhs) noexcept
|
||||
{
|
||||
for (std::size_t i = 0; i < C; i++)
|
||||
{
|
||||
amplitudes[i] += rhs.amplitudes[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Sample& operator*=(float rhs) noexcept {
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
constexpr Sample& operator*=(float rhs) noexcept
|
||||
{
|
||||
for (std::size_t i = 0; i < C; i++)
|
||||
{
|
||||
amplitudes[i] *= rhs;
|
||||
}
|
||||
return *this;
|
||||
|
|
@ -34,27 +40,33 @@ struct Sample {
|
|||
};
|
||||
|
||||
template <size_t C>
|
||||
constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
|
||||
constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept
|
||||
{
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
for (std::size_t i = 0; i < C; i++)
|
||||
{
|
||||
out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
|
||||
constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept
|
||||
{
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
for (std::size_t i = 0; i < C; i++)
|
||||
{
|
||||
out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept {
|
||||
constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept
|
||||
{
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
for (std::size_t i = 0; i < C; i++)
|
||||
{
|
||||
out.amplitudes[i] = lhs.amplitudes[i] * rhs;
|
||||
}
|
||||
return out;
|
||||
|
|
@ -64,12 +76,14 @@ template <class T>
|
|||
static constexpr float sample_to_float(T sample) noexcept;
|
||||
|
||||
template <>
|
||||
constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept {
|
||||
constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept
|
||||
{
|
||||
return (sample / 128.f) - 1.f;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr float sample_to_float<int16_t>(int16_t sample) noexcept {
|
||||
constexpr float sample_to_float<int16_t>(int16_t sample) noexcept
|
||||
{
|
||||
return sample / 32768.f;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@
|
|||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
struct SoundChunk {
|
||||
struct SoundChunk
|
||||
{
|
||||
std::vector<Sample<1>> samples;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,15 +20,18 @@ using srb2::audio::Sample;
|
|||
using srb2::audio::SoundEffectPlayer;
|
||||
using srb2::audio::Source;
|
||||
|
||||
size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) {
|
||||
size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer)
|
||||
{
|
||||
if (!chunk_)
|
||||
return 0;
|
||||
if (position_ >= chunk_->samples.size()) {
|
||||
if (position_ >= chunk_->samples.size())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t written = 0;
|
||||
for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++) {
|
||||
for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++)
|
||||
{
|
||||
float mono_sample = chunk_->samples[position_].amplitudes[0];
|
||||
|
||||
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
|
||||
|
|
@ -41,23 +44,27 @@ size_t SoundEffectPlayer::generate(tcb::span<Sample<2>> buffer) {
|
|||
return written;
|
||||
}
|
||||
|
||||
void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep) {
|
||||
void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep)
|
||||
{
|
||||
this->update(volume, sep);
|
||||
position_ = 0;
|
||||
chunk_ = chunk;
|
||||
}
|
||||
|
||||
void SoundEffectPlayer::update(float volume, float sep) {
|
||||
void SoundEffectPlayer::update(float volume, float sep)
|
||||
{
|
||||
volume_ = volume;
|
||||
sep_ = sep;
|
||||
}
|
||||
|
||||
void SoundEffectPlayer::reset() {
|
||||
void SoundEffectPlayer::reset()
|
||||
{
|
||||
position_ = 0;
|
||||
chunk_ = nullptr;
|
||||
}
|
||||
|
||||
bool SoundEffectPlayer::finished() const {
|
||||
bool SoundEffectPlayer::finished() const
|
||||
{
|
||||
if (!chunk_)
|
||||
return true;
|
||||
if (position_ >= chunk_->samples.size())
|
||||
|
|
@ -65,7 +72,8 @@ bool SoundEffectPlayer::finished() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const {
|
||||
bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const
|
||||
{
|
||||
return chunk_ == chunk;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@
|
|||
#include "sound_chunk.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class SoundEffectPlayer : public Source<2> {
|
||||
class SoundEffectPlayer : public Source<2>
|
||||
{
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override final;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class Source {
|
||||
class Source
|
||||
{
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> buffer) = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,13 @@
|
|||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
using namespace srb2;
|
||||
using srb2::audio::Wav;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr const uint32_t kMagicRIFF = 0x46464952;
|
||||
constexpr const uint32_t kMagicWAVE = 0x45564157;
|
||||
|
|
@ -27,17 +30,20 @@ constexpr const uint16_t kFormatPcm = 1;
|
|||
|
||||
constexpr const std::size_t kRiffHeaderLength = 8;
|
||||
|
||||
struct RiffHeader {
|
||||
struct RiffHeader
|
||||
{
|
||||
uint32_t magic;
|
||||
std::size_t filesize;
|
||||
};
|
||||
|
||||
struct TagHeader {
|
||||
struct TagHeader
|
||||
{
|
||||
uint32_t type;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
struct FmtTag {
|
||||
struct FmtTag
|
||||
{
|
||||
uint16_t format;
|
||||
uint16_t channels;
|
||||
uint32_t rate;
|
||||
|
|
@ -46,9 +52,12 @@ struct FmtTag {
|
|||
uint16_t bit_width;
|
||||
};
|
||||
|
||||
struct DataTag {};
|
||||
struct DataTag
|
||||
{
|
||||
};
|
||||
|
||||
RiffHeader parse_riff_header(io::SpanStream& stream) {
|
||||
RiffHeader parse_riff_header(io::SpanStream& stream)
|
||||
{
|
||||
if (io::remaining(stream) < kRiffHeaderLength)
|
||||
throw std::runtime_error("insufficient bytes remaining in stream");
|
||||
|
||||
|
|
@ -58,7 +67,8 @@ RiffHeader parse_riff_header(io::SpanStream& stream) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
TagHeader parse_tag_header(io::SpanStream& stream) {
|
||||
TagHeader parse_tag_header(io::SpanStream& stream)
|
||||
{
|
||||
if (io::remaining(stream) < 8)
|
||||
throw std::runtime_error("insufficient bytes remaining in stream");
|
||||
|
||||
|
|
@ -68,7 +78,8 @@ TagHeader parse_tag_header(io::SpanStream& stream) {
|
|||
return header;
|
||||
}
|
||||
|
||||
FmtTag parse_fmt_tag(io::SpanStream& stream) {
|
||||
FmtTag parse_fmt_tag(io::SpanStream& stream)
|
||||
{
|
||||
if (io::remaining(stream) < 16)
|
||||
throw std::runtime_error("insufficient bytes in stream");
|
||||
|
||||
|
|
@ -84,14 +95,16 @@ FmtTag parse_fmt_tag(io::SpanStream& stream) {
|
|||
}
|
||||
|
||||
template <typename Visitor>
|
||||
void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header) {
|
||||
void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header)
|
||||
{
|
||||
if (io::remaining(stream) < header.length)
|
||||
throw std::runtime_error("insufficient bytes in stream");
|
||||
|
||||
const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0);
|
||||
const io::StreamSize dest = start + header.length;
|
||||
|
||||
switch (header.type) {
|
||||
switch (header.type)
|
||||
{
|
||||
case kMagicFmt:
|
||||
{
|
||||
FmtTag fmt_tag {parse_fmt_tag(stream)};
|
||||
|
|
@ -112,87 +125,96 @@ void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header
|
|||
stream.seek(io::SeekFrom::kStart, dest);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) {
|
||||
std::vector<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count)
|
||||
{
|
||||
std::vector<uint8_t> samples;
|
||||
samples.reserve(count);
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
for (std::size_t i = 0; i < count; i++)
|
||||
{
|
||||
samples.push_back(io::read_uint8(stream));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) {
|
||||
std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count)
|
||||
{
|
||||
std::vector<int16_t> samples;
|
||||
samples.reserve(count);
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
for (std::size_t i = 0; i < count; i++)
|
||||
{
|
||||
samples.push_back(io::read_int16(stream));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
struct OverloadVisitor : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
OverloadVisitor(Ts...) -> OverloadVisitor<Ts...>;
|
||||
|
||||
} // namespace
|
||||
|
||||
Wav::Wav() = default;
|
||||
|
||||
Wav::Wav(tcb::span<std::byte> data) {
|
||||
Wav::Wav(tcb::span<std::byte> data)
|
||||
{
|
||||
io::SpanStream stream {data};
|
||||
|
||||
auto [magic, filesize] = parse_riff_header(stream);
|
||||
|
||||
if (magic != kMagicRIFF) {
|
||||
if (magic != kMagicRIFF)
|
||||
{
|
||||
throw std::runtime_error("invalid RIFF magic");
|
||||
}
|
||||
|
||||
if (io::remaining(stream) < filesize) {
|
||||
if (io::remaining(stream) < filesize)
|
||||
{
|
||||
throw std::runtime_error("insufficient data in stream for RIFF's reported filesize");
|
||||
}
|
||||
|
||||
const io::StreamSize riff_end = stream.seek(io::SeekFrom::kCurrent, 0) + filesize;
|
||||
|
||||
uint32_t type = io::read_uint32(stream);
|
||||
if (type != kMagicWAVE) {
|
||||
if (type != kMagicWAVE)
|
||||
{
|
||||
throw std::runtime_error("RIFF in stream is not a WAVE");
|
||||
}
|
||||
|
||||
std::optional<FmtTag> read_fmt;
|
||||
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples;
|
||||
|
||||
while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) {
|
||||
while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end)
|
||||
{
|
||||
TagHeader tag_header {parse_tag_header(stream)};
|
||||
if (io::remaining(stream) < tag_header.length) {
|
||||
if (io::remaining(stream) < tag_header.length)
|
||||
{
|
||||
throw std::runtime_error("WAVE tag length exceeds stream length");
|
||||
}
|
||||
|
||||
auto tag_visitor = OverloadVisitor {
|
||||
[&](const FmtTag& fmt) {
|
||||
if (read_fmt) {
|
||||
auto tag_visitor = srb2::Overload {
|
||||
[&](const FmtTag& fmt)
|
||||
{
|
||||
if (read_fmt)
|
||||
{
|
||||
throw std::runtime_error("WAVE has multiple 'fmt' tags");
|
||||
}
|
||||
if (fmt.format != kFormatPcm) {
|
||||
if (fmt.format != kFormatPcm)
|
||||
{
|
||||
throw std::runtime_error("Unsupported WAVE format (only PCM is supported)");
|
||||
}
|
||||
read_fmt = fmt;
|
||||
},
|
||||
[&](const DataTag& data) {
|
||||
if (!read_fmt) {
|
||||
[&](const DataTag& data)
|
||||
{
|
||||
if (!read_fmt)
|
||||
{
|
||||
throw std::runtime_error("unable to read data tag because no fmt tag was read");
|
||||
}
|
||||
|
||||
if (tag_header.length % read_fmt->bytes_per_sample != 0) {
|
||||
if (tag_header.length % read_fmt->bytes_per_sample != 0)
|
||||
{
|
||||
throw std::runtime_error("data tag length not divisible by bytes_per_sample");
|
||||
}
|
||||
|
||||
const std::size_t sample_count = tag_header.length / read_fmt->bytes_per_sample;
|
||||
|
||||
switch (read_fmt->bit_width) {
|
||||
switch (read_fmt->bit_width)
|
||||
{
|
||||
case 8:
|
||||
interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count));
|
||||
break;
|
||||
|
|
@ -207,7 +229,8 @@ Wav::Wav(tcb::span<std::byte> data) {
|
|||
visit_tag(tag_visitor, stream, tag_header);
|
||||
}
|
||||
|
||||
if (!read_fmt) {
|
||||
if (!read_fmt)
|
||||
{
|
||||
throw std::runtime_error("WAVE did not have a fmt tag");
|
||||
}
|
||||
|
||||
|
|
@ -216,27 +239,34 @@ Wav::Wav(tcb::span<std::byte> data) {
|
|||
sample_rate_ = read_fmt->rate;
|
||||
}
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
std::size_t read_samples(std::size_t channels,
|
||||
std::size_t offset,
|
||||
const std::vector<T>& samples,
|
||||
tcb::span<audio::Sample<1>> buffer) noexcept {
|
||||
std::size_t read_samples(
|
||||
std::size_t channels,
|
||||
std::size_t offset,
|
||||
const std::vector<T>& samples,
|
||||
tcb::span<audio::Sample<1>> buffer
|
||||
) noexcept
|
||||
{
|
||||
const std::size_t offset_interleaved = offset * channels;
|
||||
const std::size_t samples_size = samples.size();
|
||||
const std::size_t buffer_size = buffer.size();
|
||||
|
||||
if (offset_interleaved >= samples_size) {
|
||||
if (offset_interleaved >= samples_size)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::size_t remainder = (samples_size - offset_interleaved) / channels;
|
||||
const std::size_t samples_to_read = std::min(buffer_size, remainder);
|
||||
|
||||
for (std::size_t i = 0; i < samples_to_read; i++) {
|
||||
for (std::size_t i = 0; i < samples_to_read; i++)
|
||||
{
|
||||
buffer[i].amplitudes[0] = 0.f;
|
||||
for (std::size_t j = 0; j < channels; j++) {
|
||||
for (std::size_t j = 0; j < channels; j++)
|
||||
{
|
||||
buffer[i].amplitudes[0] += audio::sample_to_float(samples[i * channels + j + offset_interleaved]);
|
||||
}
|
||||
buffer[i].amplitudes[0] /= static_cast<float>(channels);
|
||||
|
|
@ -247,18 +277,20 @@ std::size_t read_samples(std::size_t channels,
|
|||
|
||||
} // namespace
|
||||
|
||||
std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept {
|
||||
auto samples_visitor = OverloadVisitor {
|
||||
std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept
|
||||
{
|
||||
auto samples_visitor = srb2::Overload {
|
||||
[&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); },
|
||||
[&](const std::vector<int16_t>& samples) {
|
||||
return read_samples<int16_t>(channels(), offset, samples, buffer);
|
||||
}};
|
||||
[&](const std::vector<int16_t>& samples)
|
||||
{ return read_samples<int16_t>(channels(), offset, samples, buffer); }};
|
||||
|
||||
return std::visit(samples_visitor, interleaved_samples_);
|
||||
}
|
||||
|
||||
std::size_t Wav::interleaved_length() const noexcept {
|
||||
auto samples_visitor = OverloadVisitor {[](const std::vector<uint8_t>& samples) { return samples.size(); },
|
||||
[](const std::vector<int16_t>& samples) { return samples.size(); }};
|
||||
std::size_t Wav::interleaved_length() const noexcept
|
||||
{
|
||||
auto samples_visitor = srb2::Overload {
|
||||
[](const std::vector<uint8_t>& samples) { return samples.size(); },
|
||||
[](const std::vector<int16_t>& samples) { return samples.size(); }};
|
||||
return std::visit(samples_visitor, interleaved_samples_);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@
|
|||
#include "../io/streams.hpp"
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class Wav final {
|
||||
class Wav final
|
||||
{
|
||||
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_;
|
||||
std::size_t channels_ = 1;
|
||||
std::size_t sample_rate_ = 44100;
|
||||
|
|
@ -41,7 +43,8 @@ public:
|
|||
};
|
||||
|
||||
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Wav load_wav(I& stream) {
|
||||
inline Wav load_wav(I& stream)
|
||||
{
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Wav {data};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ using namespace srb2;
|
|||
|
||||
using srb2::audio::WavPlayer;
|
||||
|
||||
WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) {
|
||||
WavPlayer::WavPlayer() : WavPlayer(audio::Wav {})
|
||||
{
|
||||
}
|
||||
|
||||
WavPlayer::WavPlayer(const WavPlayer& rhs) = default;
|
||||
|
|
@ -24,20 +25,25 @@ WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default;
|
|||
|
||||
WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default;
|
||||
|
||||
WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward<Wav>(wav)), position_(0), looping_(false) {
|
||||
WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward<Wav>(wav)), position_(0), looping_(false)
|
||||
{
|
||||
}
|
||||
|
||||
std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> buffer) {
|
||||
std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> buffer)
|
||||
{
|
||||
std::size_t samples_read = 0;
|
||||
while (samples_read < buffer.size()) {
|
||||
while (samples_read < buffer.size())
|
||||
{
|
||||
const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read));
|
||||
position_ += read_this_time;
|
||||
samples_read += read_this_time;
|
||||
|
||||
if (position_ > wav_.length() && looping_) {
|
||||
if (position_ > wav_.length() && looping_)
|
||||
{
|
||||
position_ = 0;
|
||||
}
|
||||
if (read_this_time == 0 && !looping_) {
|
||||
if (read_this_time == 0 && !looping_)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@
|
|||
#include "source.hpp"
|
||||
#include "wav.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class WavPlayer final : public Source<1> {
|
||||
class WavPlayer final : public Source<1>
|
||||
{
|
||||
Wav wav_;
|
||||
std::size_t position_;
|
||||
bool looping_;
|
||||
|
|
|
|||
|
|
@ -16,11 +16,14 @@
|
|||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
XmpException::XmpException(int code) : code_(code) {
|
||||
XmpException::XmpException(int code) : code_(code)
|
||||
{
|
||||
}
|
||||
|
||||
const char* XmpException::what() const noexcept {
|
||||
switch (code_) {
|
||||
const char* XmpException::what() const noexcept
|
||||
{
|
||||
switch (code_)
|
||||
{
|
||||
case -XMP_ERROR_INTERNAL:
|
||||
return "XMP_ERROR_INTERNAL";
|
||||
case -XMP_ERROR_FORMAT:
|
||||
|
|
@ -41,23 +44,27 @@ const char* XmpException::what() const noexcept {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false)
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(std::vector<std::byte> data)
|
||||
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false)
|
||||
{
|
||||
_init();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(tcb::span<std::byte> data)
|
||||
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false)
|
||||
{
|
||||
_init();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
|
||||
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>()
|
||||
{
|
||||
std::swap(data_, rhs.data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
std::swap(module_loaded_, rhs.module_loaded_);
|
||||
|
|
@ -65,7 +72,8 @@ Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept {
|
||||
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept
|
||||
{
|
||||
std::swap(data_, rhs.data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
std::swap(module_loaded_, rhs.module_loaded_);
|
||||
|
|
@ -75,15 +83,18 @@ Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept {
|
|||
};
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::~Xmp() {
|
||||
if (instance_) {
|
||||
Xmp<C>::~Xmp()
|
||||
{
|
||||
if (instance_)
|
||||
{
|
||||
xmp_free_context(instance_);
|
||||
instance_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) {
|
||||
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
SRB2_ASSERT(module_loaded_ == true);
|
||||
|
||||
|
|
@ -99,7 +110,8 @@ std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void Xmp<C>::reset() {
|
||||
void Xmp<C>::reset()
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
SRB2_ASSERT(module_loaded_ == true);
|
||||
|
||||
|
|
@ -107,7 +119,8 @@ void Xmp<C>::reset() {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
float Xmp<C>::duration_seconds() const {
|
||||
float Xmp<C>::duration_seconds() const
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
SRB2_ASSERT(module_loaded_ == true);
|
||||
|
||||
|
|
@ -117,7 +130,8 @@ float Xmp<C>::duration_seconds() const {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void Xmp<C>::seek(int position_ms) {
|
||||
void Xmp<C>::seek(int position_ms)
|
||||
{
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
SRB2_ASSERT(module_loaded_ == true);
|
||||
|
||||
|
|
@ -127,7 +141,8 @@ void Xmp<C>::seek(int position_ms) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
void Xmp<C>::_init() {
|
||||
void Xmp<C>::_init()
|
||||
{
|
||||
if (instance_)
|
||||
return;
|
||||
|
||||
|
|
@ -137,12 +152,14 @@ void Xmp<C>::_init() {
|
|||
throw std::logic_error("Insufficient data from stream");
|
||||
|
||||
instance_ = xmp_create_context();
|
||||
if (instance_ == nullptr) {
|
||||
if (instance_ == nullptr)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size());
|
||||
if (result != 0) {
|
||||
if (result != 0)
|
||||
{
|
||||
xmp_free_context(instance_);
|
||||
instance_ = nullptr;
|
||||
throw XmpException(result);
|
||||
|
|
@ -150,11 +167,13 @@ void Xmp<C>::_init() {
|
|||
module_loaded_ = true;
|
||||
|
||||
int flags = 0;
|
||||
if constexpr (C == 1) {
|
||||
if constexpr (C == 1)
|
||||
{
|
||||
flags |= XMP_FORMAT_MONO;
|
||||
}
|
||||
result = xmp_start_player(instance_, 44100, flags);
|
||||
if (result != 0) {
|
||||
if (result != 0)
|
||||
{
|
||||
xmp_release_module(instance_);
|
||||
module_loaded_ = false;
|
||||
xmp_free_context(instance_);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
// See the 'LICENSE' file for more details.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef __SRB2_AUDIO_XMP_HPP__
|
||||
#ifndef __SRB2_AUDIO_XMP_HPP__
|
||||
#define __SRB2_AUDIO_XMP_HPP__
|
||||
|
||||
#include <array>
|
||||
|
|
@ -22,9 +22,11 @@
|
|||
|
||||
#include "../io/streams.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
class XmpException : public std::exception {
|
||||
class XmpException : public std::exception
|
||||
{
|
||||
int code_;
|
||||
|
||||
public:
|
||||
|
|
@ -33,7 +35,8 @@ public:
|
|||
};
|
||||
|
||||
template <size_t C>
|
||||
class Xmp final {
|
||||
class Xmp final
|
||||
{
|
||||
std::vector<std::byte> data_;
|
||||
xmp_context instance_;
|
||||
bool module_loaded_;
|
||||
|
|
@ -68,7 +71,8 @@ extern template class Xmp<1>;
|
|||
extern template class Xmp<2>;
|
||||
|
||||
template <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Xmp<C> load_xmp(I& stream) {
|
||||
inline Xmp<C> load_xmp(I& stream)
|
||||
{
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Xmp<C> {std::move(data)};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ using namespace srb2;
|
|||
using namespace srb2::audio;
|
||||
|
||||
template <size_t C>
|
||||
XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_() {
|
||||
XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_()
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
|
|
@ -28,14 +29,17 @@ template <size_t C>
|
|||
XmpPlayer<C>::~XmpPlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer)
|
||||
{
|
||||
buf_.resize(buffer.size());
|
||||
std::size_t read = xmp_.play_buffer(tcb::make_span(buf_));
|
||||
buf_.resize(read);
|
||||
std::size_t ret = std::min(buffer.size(), buf_.size());
|
||||
|
||||
for (std::size_t i = 0; i < ret; i++) {
|
||||
for (std::size_t j = 0; j < C; j++) {
|
||||
for (std::size_t i = 0; i < ret; i++)
|
||||
{
|
||||
for (std::size_t j = 0; j < C; j++)
|
||||
{
|
||||
buffer[i].amplitudes[j] = buf_[i][j] / 32768.f;
|
||||
}
|
||||
}
|
||||
|
|
@ -44,12 +48,14 @@ std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
|||
}
|
||||
|
||||
template <size_t C>
|
||||
float XmpPlayer<C>::duration_seconds() const {
|
||||
float XmpPlayer<C>::duration_seconds() const
|
||||
{
|
||||
return xmp_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void XmpPlayer<C>::seek(float position_seconds) {
|
||||
void XmpPlayer<C>::seek(float position_seconds)
|
||||
{
|
||||
xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@
|
|||
#include "source.hpp"
|
||||
#include "xmp.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
namespace srb2::audio
|
||||
{
|
||||
|
||||
template <size_t C>
|
||||
class XmpPlayer final : public Source<C> {
|
||||
class XmpPlayer final : public Source<C>
|
||||
{
|
||||
Xmp<C> xmp_;
|
||||
std::vector<std::array<int16_t, C>> buf_;
|
||||
|
||||
|
|
|
|||
3
src/core/CMakeLists.txt
Normal file
3
src/core/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
static_vec.hpp
|
||||
)
|
||||
230
src/core/static_vec.hpp
Normal file
230
src/core/static_vec.hpp
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
#ifndef __SRB2_CORE_STATIC_VEC_HPP__
|
||||
#define __SRB2_CORE_STATIC_VEC_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
namespace srb2
|
||||
{
|
||||
|
||||
template <typename T, size_t Limit>
|
||||
class StaticVec
|
||||
{
|
||||
std::array<T, Limit> arr_ {{}};
|
||||
size_t size_ = 0;
|
||||
|
||||
public:
|
||||
constexpr StaticVec() {}
|
||||
|
||||
StaticVec(const StaticVec& rhs)
|
||||
{
|
||||
for (size_t i = size_; i > 0; i--)
|
||||
{
|
||||
arr_[i] = T();
|
||||
}
|
||||
size_ = rhs.size();
|
||||
for (size_t i = 0; i < size_; i++)
|
||||
{
|
||||
arr_[i] = rhs.arr_[i];
|
||||
}
|
||||
}
|
||||
|
||||
StaticVec(StaticVec&& rhs) noexcept(std::is_nothrow_move_assignable_v<T>)
|
||||
{
|
||||
for (size_t i = size_; i > 0; i--)
|
||||
{
|
||||
arr_[i] = T();
|
||||
}
|
||||
size_ = rhs.size();
|
||||
for (size_t i = 0; i < size_; i++)
|
||||
{
|
||||
arr_[i] = std::move(rhs.arr_[i]);
|
||||
}
|
||||
while (rhs.size() > 0)
|
||||
{
|
||||
rhs.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr StaticVec(std::initializer_list<T> list) noexcept(std::is_nothrow_move_assignable_v<T>)
|
||||
{
|
||||
size_ = list.size();
|
||||
size_t i = 0;
|
||||
for (auto itr = list.begin(); itr != list.end(); itr++)
|
||||
{
|
||||
arr_[i] = *itr;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
~StaticVec() = default;
|
||||
|
||||
StaticVec& operator=(const StaticVec& rhs)
|
||||
{
|
||||
for (size_t i = size_; i > 0; i--)
|
||||
{
|
||||
arr_[i] = T();
|
||||
}
|
||||
size_ = rhs.size();
|
||||
for (size_t i = 0; i < size_; i++)
|
||||
{
|
||||
arr_[i] = rhs.arr_[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
StaticVec& operator=(StaticVec&& rhs) noexcept(std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
for (size_t i = size_; i > 0; i--)
|
||||
{
|
||||
arr_[i] = T();
|
||||
}
|
||||
size_ = rhs.size();
|
||||
for (size_t i = 0; i < size_; i++)
|
||||
{
|
||||
arr_[i] = std::move(rhs.arr_[i]);
|
||||
}
|
||||
while (rhs.size() > 0)
|
||||
{
|
||||
rhs.pop_back();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void push_back(const T& value) { arr_[size_++] = value; }
|
||||
|
||||
void pop_back() { arr_[size_--] = T(); }
|
||||
|
||||
void resize(size_t size, T value = T())
|
||||
{
|
||||
if (size >= Limit)
|
||||
{
|
||||
throw std::length_error("new size >= Capacity");
|
||||
}
|
||||
|
||||
if (size == size_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (size < size_)
|
||||
{
|
||||
while (size_ > size)
|
||||
{
|
||||
pop_back();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (size_ < size)
|
||||
{
|
||||
push_back(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr T* begin() noexcept { return &arr_[0]; }
|
||||
|
||||
constexpr const T* begin() const noexcept { return cbegin(); }
|
||||
|
||||
constexpr const T* cbegin() const noexcept { return &arr_[0]; }
|
||||
|
||||
constexpr T* end() noexcept { return &arr_[size_]; }
|
||||
|
||||
constexpr const T* end() const noexcept { return cend(); }
|
||||
|
||||
constexpr const T* cend() const noexcept { return &arr_[size_]; }
|
||||
|
||||
constexpr std::reverse_iterator<T*> rbegin() noexcept { return &arr_[size_]; }
|
||||
|
||||
constexpr std::reverse_iterator<const T*> crbegin() const noexcept { return &arr_[size_]; }
|
||||
|
||||
constexpr std::reverse_iterator<T*> rend() noexcept { return &arr_[0]; }
|
||||
|
||||
constexpr std::reverse_iterator<const T*> crend() const noexcept { return &arr_[0]; }
|
||||
|
||||
constexpr bool empty() const noexcept { return size_ == 0; }
|
||||
|
||||
constexpr size_t size() const noexcept { return size_; }
|
||||
|
||||
constexpr size_t capacity() const noexcept { return Limit; }
|
||||
|
||||
constexpr size_t max_size() const noexcept { return Limit; }
|
||||
|
||||
constexpr T& operator[](size_t index) noexcept { return arr_[index]; }
|
||||
|
||||
T& at(size_t index)
|
||||
{
|
||||
if (index >= size_)
|
||||
{
|
||||
throw std::out_of_range("index >= size");
|
||||
}
|
||||
return this[index];
|
||||
}
|
||||
|
||||
constexpr const T& operator[](size_t index) const noexcept { return arr_[index]; }
|
||||
|
||||
const T& at(size_t index) const
|
||||
{
|
||||
if (index >= size_)
|
||||
{
|
||||
throw std::out_of_range("index >= size");
|
||||
}
|
||||
return this[index];
|
||||
}
|
||||
|
||||
T& front() { return *arr_[0]; }
|
||||
|
||||
T& back() { return *arr_[size_ - 1]; }
|
||||
};
|
||||
|
||||
} // namespace srb2
|
||||
|
||||
template <typename T, size_t L1, size_t L2>
|
||||
bool operator==(const srb2::StaticVec<T, L1>& lhs, const srb2::StaticVec<T, L2>& rhs)
|
||||
{
|
||||
const size_t size = lhs.size();
|
||||
if (size != rhs.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < lhs; i++)
|
||||
{
|
||||
if (rhs[i] != lhs[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, size_t L1, size_t L2>
|
||||
bool operator!=(const srb2::StaticVec<T, L1>& lhs, const srb2::StaticVec<T, L2>& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
template <typename T, size_t Limit>
|
||||
struct std::hash<srb2::StaticVec<T, Limit>>
|
||||
{
|
||||
uint64_t operator()(const srb2::StaticVec<T, Limit>& input) const
|
||||
{
|
||||
constexpr const uint64_t prime {0x00000100000001B3};
|
||||
std::size_t ret {0xcbf29ce484222325};
|
||||
|
||||
for (auto itr = input.begin(); itr != input.end(); itr++)
|
||||
{
|
||||
ret = (ret * prime) ^ std::hash<T>(*itr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // __SRB2_CORE_STATIC_VEC_HPP__
|
||||
|
|
@ -62,10 +62,12 @@ struct SourceLocation {
|
|||
class IErrorAssertHandler {
|
||||
public:
|
||||
static void handle(const SourceLocation& source_location, const char* expression) {
|
||||
I_Error("Assertion failed at %s:%u: %s != true",
|
||||
source_location.file_name,
|
||||
source_location.line_number,
|
||||
expression);
|
||||
I_Error(
|
||||
"Assertion failed at %s:%u: %s != true",
|
||||
source_location.file_name,
|
||||
source_location.line_number,
|
||||
expression
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -102,8 +104,10 @@ class NotNull final {
|
|||
T ptr_;
|
||||
|
||||
public:
|
||||
static_assert(std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>,
|
||||
"T is not comparable with nullptr_t");
|
||||
static_assert(
|
||||
std::is_convertible_v<decltype(std::declval<T>() != nullptr), bool>,
|
||||
"T is not comparable with nullptr_t"
|
||||
);
|
||||
|
||||
/// @brief Move-construct from the pointer value U, asserting that it is not null. Allows construction of a
|
||||
/// NotNull<T> from any compatible pointer U, for example with polymorphic classes.
|
||||
|
|
@ -147,6 +151,17 @@ public:
|
|||
template <class T>
|
||||
NotNull(T) -> NotNull<T>;
|
||||
|
||||
/// @brief Utility struct for combining several Callables (e.g. lambdas) into a single Callable with the call operator
|
||||
/// overloaded. Use it to build a visitor for calling std::visit on variants.
|
||||
/// @tparam ...Ts callable types
|
||||
template <typename... Ts>
|
||||
struct Overload : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
Overload(Ts...) -> Overload<Ts...>;
|
||||
|
||||
} // namespace srb2
|
||||
|
||||
#endif // __SRB2_CXXUTIL_HPP__
|
||||
|
|
|
|||
|
|
@ -1141,6 +1141,7 @@ static void IdentifyVersion(void)
|
|||
#if defined(DEVELOP) && defined(UNLOCKTESTING)
|
||||
D_AddFile(startupiwads, va(pandf,srb2waddir,UNLOCKNAME));
|
||||
#endif
|
||||
D_AddFile(startupiwads, va(pandf,srb2waddir,"shaders.pk3"));
|
||||
////
|
||||
#undef TEXTURESNAME
|
||||
#undef MAPSNAME
|
||||
|
|
@ -1466,6 +1467,7 @@ void D_SRB2Main(void)
|
|||
#endif
|
||||
|
||||
#endif //ifndef DEVELOP
|
||||
mainwads++; // shaders.pk3
|
||||
|
||||
// Do it before P_InitMapData because PNG patch
|
||||
// conversion sometimes needs the palette
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ static boolean AddFileToSendQueue(INT32 node, const char *filename, UINT8 fileid
|
|||
|
||||
#ifdef HAVE_CURL
|
||||
size_t curlwrite_data(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
int curlprogress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);
|
||||
int curlprogress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
||||
#endif
|
||||
|
||||
// Sender structure
|
||||
|
|
@ -123,8 +123,8 @@ static CURL *http_handle;
|
|||
static CURLM *multi_handle;
|
||||
boolean curl_running = false;
|
||||
boolean curl_failedwebdownload = false;
|
||||
static double curl_dlnow;
|
||||
static double curl_dltotal;
|
||||
static curl_off_t curl_dlnow;
|
||||
static curl_off_t curl_dltotal;
|
||||
static time_t curl_starttime;
|
||||
INT32 curl_transfers = 0;
|
||||
static int curl_runninghandles = 0;
|
||||
|
|
@ -1763,7 +1763,7 @@ size_t curlwrite_data(void *ptr, size_t size, size_t nmemb, FILE *stream)
|
|||
return written;
|
||||
}
|
||||
|
||||
int curlprogress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)
|
||||
int curlprogress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
|
||||
{
|
||||
(void)clientp;
|
||||
(void)ultotal;
|
||||
|
|
@ -1802,7 +1802,11 @@ void CURLPrepareFile(const char* url, int dfilenum)
|
|||
curl_easy_setopt(http_handle, CURLOPT_URL, va("%s/%s", url, curl_realname));
|
||||
|
||||
// Only allow HTTP and HTTPS
|
||||
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS);
|
||||
#if LIBCURL_VERSION_MAJOR > 7 || (LIBCURL_VERSION_MAJOR == 7 && LIBCURL_VERSION_MINOR >= 85)
|
||||
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS_STR, "http,https");
|
||||
#else
|
||||
curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); // deprecated in 7.85.0
|
||||
#endif
|
||||
|
||||
curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("Ring Racers/v%d.%d", VERSION, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents.
|
||||
|
||||
|
|
@ -1826,7 +1830,7 @@ void CURLPrepareFile(const char* url, int dfilenum)
|
|||
curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, curl_curfile->file);
|
||||
curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, curlwrite_data);
|
||||
curl_easy_setopt(http_handle, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(http_handle, CURLOPT_PROGRESSFUNCTION, curlprogress_callback);
|
||||
curl_easy_setopt(http_handle, CURLOPT_XFERINFOFUNCTION, curlprogress_callback);
|
||||
|
||||
curl_curfile->status = FS_DOWNLOADING;
|
||||
lastfilenum = dfilenum;
|
||||
|
|
|
|||
8
src/hwr2/CMakeLists.txt
Normal file
8
src/hwr2/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
pass_imgui.cpp
|
||||
pass_imgui.hpp
|
||||
pass_software.cpp
|
||||
pass_software.hpp
|
||||
pass.cpp
|
||||
pass.hpp
|
||||
)
|
||||
3
src/hwr2/pass.cpp
Normal file
3
src/hwr2/pass.cpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "pass.hpp"
|
||||
|
||||
srb2::hwr2::Pass::~Pass() = default;
|
||||
35
src/hwr2/pass.hpp
Normal file
35
src/hwr2/pass.hpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef __SRB2_HWR2_PASS_HPP__
|
||||
#define __SRB2_HWR2_PASS_HPP__
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
/// @brief A rendering pass which performs logic during each phase of a frame render.
|
||||
/// During rendering, all registered Pass's individual stages will be run together.
|
||||
struct Pass {
|
||||
virtual ~Pass();
|
||||
|
||||
/// @brief Perform rendering logic and create necessary GPU resources.
|
||||
/// @param rhi
|
||||
virtual void prepass(rhi::Rhi& rhi) = 0;
|
||||
|
||||
/// @brief Upload contents for needed GPU resources.
|
||||
/// @param rhi
|
||||
/// @param ctx
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) = 0;
|
||||
|
||||
/// @brief Issue draw calls.
|
||||
/// @param rhi
|
||||
/// @param ctx
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) = 0;
|
||||
|
||||
/// @brief Cleanup GPU resources. Transient resources should be cleaned up here.
|
||||
/// @param rhi
|
||||
virtual void postpass(rhi::Rhi& rhi) = 0;
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_HPP__
|
||||
255
src/hwr2/pass_imgui.cpp
Normal file
255
src/hwr2/pass_imgui.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#include "pass_imgui.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "../v_video.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
static const PipelineDesc kPipelineDesc =
|
||||
{
|
||||
PipelineProgram::kUnshaded,
|
||||
{
|
||||
{
|
||||
{sizeof(ImDrawVert)}
|
||||
},
|
||||
{
|
||||
{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12},
|
||||
{VertexAttributeName::kColor, 0, 24}
|
||||
}
|
||||
},
|
||||
{{
|
||||
{{UniformName::kProjection}},
|
||||
{{UniformName::kModelView, UniformName::kTexCoord0Transform}}
|
||||
}},
|
||||
{{
|
||||
SamplerName::kSampler0
|
||||
}},
|
||||
PipelineDepthAttachmentDesc {
|
||||
PixelFormat::kDepth16,
|
||||
CompareFunc::kAlways,
|
||||
true
|
||||
},
|
||||
{
|
||||
PixelFormat::kRGBA8,
|
||||
BlendDesc {
|
||||
BlendFactor::kSourceAlpha,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd,
|
||||
BlendFactor::kOne,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd
|
||||
},
|
||||
{true, true, true, true}
|
||||
},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
|
||||
ImguiPass::~ImguiPass() = default;
|
||||
|
||||
void ImguiPass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!pipeline_)
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kPipelineDesc);
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
if (!font_atlas_)
|
||||
{
|
||||
unsigned char* pixels;
|
||||
int width;
|
||||
int height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
uint32_t uwidth = static_cast<uint32_t>(width);
|
||||
uint32_t uheight = static_cast<uint32_t>(height);
|
||||
|
||||
font_atlas_ = rhi.create_texture({TextureFormat::kRGBA, uwidth, uheight});
|
||||
io.Fonts->SetTexID(font_atlas_);
|
||||
}
|
||||
|
||||
ImGui::Render();
|
||||
|
||||
ImDrawData* data = ImGui::GetDrawData();
|
||||
ImVec2 clip_off(data->DisplayPos);
|
||||
ImVec2 clip_scale(data->FramebufferScale);
|
||||
tcb::span<ImDrawList*> draw_lists = tcb::span(data->CmdLists, data->CmdListsCount);
|
||||
|
||||
for (auto list : draw_lists)
|
||||
{
|
||||
Handle<Buffer> vbo = rhi.create_buffer(
|
||||
{
|
||||
static_cast<uint32_t>(list->VtxBuffer.size_in_bytes()),
|
||||
BufferType::kVertexBuffer,
|
||||
BufferUsage::kImmutable
|
||||
}
|
||||
);
|
||||
Handle<Buffer> ibo = rhi.create_buffer(
|
||||
{
|
||||
static_cast<uint32_t>(list->IdxBuffer.size_in_bytes()),
|
||||
BufferType::kIndexBuffer,
|
||||
BufferUsage::kImmutable
|
||||
}
|
||||
);
|
||||
|
||||
DrawList hwr2_list;
|
||||
hwr2_list.list = list;
|
||||
hwr2_list.vbo = vbo;
|
||||
hwr2_list.ibo = ibo;
|
||||
|
||||
for (auto& cmd : list->CmdBuffer)
|
||||
{
|
||||
if (cmd.UserCallback)
|
||||
{
|
||||
cmd.UserCallback(list, &cmd);
|
||||
continue;
|
||||
}
|
||||
|
||||
ImVec2 clip_min((cmd.ClipRect.x - clip_off.x) * clip_scale.x, (cmd.ClipRect.y - clip_off.y) * clip_scale.y);
|
||||
ImVec2 clip_max((cmd.ClipRect.z - clip_off.x) * clip_scale.x, (cmd.ClipRect.w - clip_off.y) * clip_scale.y);
|
||||
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DrawCmd draw_cmd;
|
||||
ImTextureID tex_id = cmd.GetTexID();
|
||||
draw_cmd.tex = tex_id;
|
||||
draw_cmd.v_offset = cmd.VtxOffset;
|
||||
draw_cmd.i_offset = cmd.IdxOffset;
|
||||
draw_cmd.elems = cmd.ElemCount;
|
||||
draw_cmd.clip =
|
||||
{
|
||||
static_cast<int32_t>(clip_min.x),
|
||||
static_cast<int32_t>((data->DisplaySize.y * data->FramebufferScale.y) - clip_max.y),
|
||||
static_cast<uint32_t>(clip_max.x - clip_min.x),
|
||||
static_cast<uint32_t>(clip_max.y - clip_min.y)
|
||||
};
|
||||
hwr2_list.cmds.push_back(std::move(draw_cmd));
|
||||
}
|
||||
draw_lists_.push_back(std::move(hwr2_list));
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
{
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
font_atlas_,
|
||||
{0, 0, static_cast<uint32_t>(width), static_cast<uint32_t>(height)},
|
||||
rhi::PixelFormat::kRGBA8,
|
||||
tcb::as_bytes(tcb::span(pixels, static_cast<size_t>(width * height * 4)))
|
||||
);
|
||||
}
|
||||
|
||||
for (auto& draw_list : draw_lists_)
|
||||
{
|
||||
Handle<Buffer> vbo = draw_list.vbo;
|
||||
Handle<Buffer> ibo = draw_list.ibo;
|
||||
|
||||
ImDrawList* im_list = static_cast<ImDrawList*>(draw_list.list);
|
||||
|
||||
for (auto& vtx : im_list->VtxBuffer)
|
||||
{
|
||||
vtx.pos.z = 0.f;
|
||||
vtx.colf[0] = ((vtx.col & 0xFF) >> 0) / 255.f;
|
||||
vtx.colf[1] = ((vtx.col & 0xFF00) >> 8) / 255.f;
|
||||
vtx.colf[2] = ((vtx.col & 0xFF0000) >> 16) / 255.f;
|
||||
vtx.colf[3] = ((vtx.col & 0xFF000000) >> 24) / 255.f;
|
||||
}
|
||||
|
||||
tcb::span<ImDrawVert> vert_span = tcb::span(im_list->VtxBuffer.Data, im_list->VtxBuffer.size());
|
||||
rhi.update_buffer_contents(ctx, vbo, 0, tcb::as_bytes(vert_span));
|
||||
|
||||
tcb::span<ImDrawIdx> index_span = tcb::span(im_list->IdxBuffer.Data, im_list->IdxBuffer.size());
|
||||
rhi.update_buffer_contents(ctx, ibo, 0, tcb::as_bytes(index_span));
|
||||
|
||||
// Uniform sets
|
||||
std::array<UniformVariant, 1> g1_uniforms =
|
||||
{{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{2.f / vid.realwidth, 0.f, 0.f, 0.f},
|
||||
{0.f, 2.f / vid.realheight, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{-1.f, 1.f, 0.f, 1.f}
|
||||
}},
|
||||
}};
|
||||
std::array<UniformVariant, 2> g2_uniforms =
|
||||
{{
|
||||
// ModelView
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{1.f, 0.f, 0.f, 0.f},
|
||||
{0.f, -1.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0, 0.f, 1.f}
|
||||
}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3>
|
||||
{{
|
||||
{1.f, 0.f, 0.f},
|
||||
{0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 1.f}
|
||||
}}
|
||||
}};
|
||||
Handle<UniformSet> us_1 = rhi.create_uniform_set(ctx, {g1_uniforms});
|
||||
Handle<UniformSet> us_2 = rhi.create_uniform_set(ctx, {g2_uniforms});
|
||||
|
||||
draw_list.us_1 = us_1;
|
||||
draw_list.us_2 = us_2;
|
||||
|
||||
for (auto& draw_cmd : draw_list.cmds)
|
||||
{
|
||||
// Binding set
|
||||
std::array<rhi::VertexAttributeBufferBinding, 1> vbos = {{0, vbo}};
|
||||
std::array<rhi::TextureBinding, 1> tbs = {{{rhi::SamplerName::kSampler0, draw_cmd.tex}}};
|
||||
rhi::Handle<rhi::BindingSet> binding_set = rhi.create_binding_set(ctx, pipeline_, {vbos, tbs});
|
||||
draw_cmd.binding_set = binding_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImguiPass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
rhi.begin_default_render_pass(ctx, false);
|
||||
rhi.bind_pipeline(ctx, pipeline_);
|
||||
for (auto& draw_list : draw_lists_)
|
||||
{
|
||||
rhi.bind_uniform_set(ctx, 0, draw_list.us_1);
|
||||
rhi.bind_uniform_set(ctx, 1, draw_list.us_2);
|
||||
for (auto& cmd : draw_list.cmds)
|
||||
{
|
||||
rhi.bind_binding_set(ctx, cmd.binding_set);
|
||||
rhi.bind_index_buffer(ctx, draw_list.ibo);
|
||||
rhi.set_scissor(ctx, cmd.clip);
|
||||
rhi.draw_indexed(ctx, cmd.elems, cmd.i_offset);
|
||||
}
|
||||
}
|
||||
rhi.end_render_pass(ctx);
|
||||
}
|
||||
|
||||
void ImguiPass::postpass(Rhi& rhi)
|
||||
{
|
||||
for (auto& list : draw_lists_)
|
||||
{
|
||||
rhi.destroy_buffer(list.vbo);
|
||||
rhi.destroy_buffer(list.ibo);
|
||||
}
|
||||
draw_lists_.clear();
|
||||
}
|
||||
52
src/hwr2/pass_imgui.hpp
Normal file
52
src/hwr2/pass_imgui.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef __SRB2_HWR2_PASS_IMGUI_HPP__
|
||||
#define __SRB2_HWR2_PASS_IMGUI_HPP__
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class ImguiPass : public Pass
|
||||
{
|
||||
struct DrawCmd
|
||||
{
|
||||
rhi::Handle<rhi::Texture> tex;
|
||||
uint32_t v_offset;
|
||||
uint32_t elems;
|
||||
uint32_t i_offset;
|
||||
rhi::Rect clip;
|
||||
rhi::Handle<rhi::BindingSet> binding_set;
|
||||
};
|
||||
struct DrawList
|
||||
{
|
||||
void* list;
|
||||
rhi::Handle<rhi::Buffer> vbo;
|
||||
rhi::Handle<rhi::Buffer> ibo;
|
||||
rhi::Handle<rhi::UniformSet> us_1;
|
||||
rhi::Handle<rhi::UniformSet> us_2;
|
||||
std::vector<DrawCmd> cmds;
|
||||
};
|
||||
|
||||
rhi::Handle<rhi::Pipeline> pipeline_;
|
||||
rhi::Handle<rhi::Texture> font_atlas_;
|
||||
|
||||
std::vector<DrawList> draw_lists_;
|
||||
|
||||
public:
|
||||
virtual ~ImguiPass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_IMGUI_HPP__
|
||||
284
src/hwr2/pass_software.cpp
Normal file
284
src/hwr2/pass_software.cpp
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
#include "pass_software.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "../d_netcmd.h"
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
#include "../discord.h"
|
||||
#endif
|
||||
#include "../doomstat.h"
|
||||
#include "../st_stuff.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../v_video.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
SoftwareBlitPass::~SoftwareBlitPass() = default;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SwBlitVertex
|
||||
{
|
||||
float x = 0.f;
|
||||
float y = 0.f;
|
||||
float z = 0.f;
|
||||
float u = 0.f;
|
||||
float v = 0.f;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static const SwBlitVertex kVerts[] =
|
||||
{
|
||||
{-.5f, -.5f, 0.f, 0.f, 0.f},
|
||||
{.5f, -.5f, 0.f, 1.f, 0.f},
|
||||
{-.5f, .5f, 0.f, 0.f, 1.f},
|
||||
{.5f, .5f, 0.f, 1.f, 1.f}
|
||||
};
|
||||
|
||||
static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2};
|
||||
|
||||
static const PipelineDesc kPipelineDescription =
|
||||
{
|
||||
PipelineProgram::kUnshadedPaletted,
|
||||
{
|
||||
{
|
||||
{sizeof(SwBlitVertex)}
|
||||
},
|
||||
{
|
||||
{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12}
|
||||
}
|
||||
},
|
||||
{{
|
||||
{{UniformName::kProjection}},
|
||||
{{UniformName::kModelView, UniformName::kTexCoord0Transform}}
|
||||
}},
|
||||
{{
|
||||
// R8 index texture
|
||||
SamplerName::kSampler0,
|
||||
// 256x1 palette texture
|
||||
SamplerName::kSampler1
|
||||
}},
|
||||
std::nullopt,
|
||||
{
|
||||
PixelFormat::kRGBA8,
|
||||
std::nullopt,
|
||||
{true, true, true, true}
|
||||
},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
|
||||
static uint32_t next_pow_of_2(uint32_t in)
|
||||
{
|
||||
in--;
|
||||
in |= in >> 1;
|
||||
in |= in >> 2;
|
||||
in |= in >> 4;
|
||||
in |= in >> 8;
|
||||
in |= in >> 16;
|
||||
in++;
|
||||
return in;
|
||||
}
|
||||
|
||||
static void temp_legacy_finishupdate_draws()
|
||||
{
|
||||
SCR_CalculateFPS();
|
||||
if (st_overlay)
|
||||
{
|
||||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || ! server_lagless ))
|
||||
{
|
||||
if (server_lagless)
|
||||
{
|
||||
if (consoleplayer != serverplayer)
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
int player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
SCR_DisplayLocalPing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
|
||||
if (marathonmode)
|
||||
SCR_DisplayMarathonInfo();
|
||||
|
||||
// draw captions if enabled
|
||||
if (cv_closedcaptioning.value)
|
||||
SCR_ClosedCaptions();
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
if (discordRequestList != NULL)
|
||||
ST_AskToJoinEnvelope();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!pipeline_)
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kPipelineDescription);
|
||||
}
|
||||
|
||||
if (!quad_vbo_)
|
||||
{
|
||||
quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable});
|
||||
quad_vbo_needs_upload_ = true;
|
||||
}
|
||||
|
||||
if (!quad_ibo_)
|
||||
{
|
||||
quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable});
|
||||
quad_ibo_needs_upload_ = true;
|
||||
}
|
||||
|
||||
temp_legacy_finishupdate_draws();
|
||||
|
||||
uint32_t vid_width = static_cast<uint32_t>(vid.width);
|
||||
uint32_t vid_height = static_cast<uint32_t>(vid.height);
|
||||
|
||||
if (screen_tex_ && (screen_tex_width_ < vid_width || screen_tex_height_ < vid_height))
|
||||
{
|
||||
rhi.destroy_texture(screen_tex_);
|
||||
screen_tex_ = kNullHandle;
|
||||
}
|
||||
|
||||
if (!screen_tex_)
|
||||
{
|
||||
screen_tex_width_ = next_pow_of_2(vid_width);
|
||||
screen_tex_height_ = next_pow_of_2(vid_height);
|
||||
screen_tex_ = rhi.create_texture({TextureFormat::kLuminance, screen_tex_width_, screen_tex_height_});
|
||||
}
|
||||
|
||||
if (!palette_tex_)
|
||||
{
|
||||
palette_tex_ = rhi.create_texture({TextureFormat::kRGBA, 256, 1});
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::upload_screen(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
rhi::Rect screen_rect = {
|
||||
0,
|
||||
0,
|
||||
static_cast<uint32_t>(vid.width),
|
||||
static_cast<uint32_t>(vid.height)
|
||||
};
|
||||
|
||||
tcb::span<uint8_t> screen_span = tcb::span(vid.buffer, static_cast<size_t>(vid.width * vid.height));
|
||||
rhi.update_texture(ctx, screen_tex_, screen_rect, rhi::PixelFormat::kR8, tcb::as_bytes(screen_span));
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::upload_palette(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
// Unfortunately, pMasterPalette must be swizzled to get a linear layout.
|
||||
// Maybe some adjustments to palette storage can make this a straight upload.
|
||||
std::array<byteColor_t, 256> palette_32;
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
{
|
||||
palette_32[i] = pMasterPalette[i].s;
|
||||
}
|
||||
rhi.update_texture(ctx, palette_tex_, {0, 0, 256, 1}, rhi::PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32)));
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
if (quad_vbo_needs_upload_ && quad_vbo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts)));
|
||||
quad_vbo_needs_upload_ = false;
|
||||
}
|
||||
|
||||
if (quad_ibo_needs_upload_ && quad_ibo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices)));
|
||||
quad_ibo_needs_upload_ = false;
|
||||
}
|
||||
|
||||
upload_screen(rhi, ctx);
|
||||
upload_palette(rhi, ctx);
|
||||
|
||||
// Calculate aspect ratio for black borders
|
||||
float aspect = static_cast<float>(vid.width) / static_cast<float>(vid.height);
|
||||
float real_aspect = static_cast<float>(vid.realwidth) / static_cast<float>(vid.realheight);
|
||||
bool taller = aspect > real_aspect;
|
||||
|
||||
std::array<rhi::UniformVariant, 1> g1_uniforms = {{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4> {{
|
||||
{taller ? 1.f : 1.f / real_aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? -1.f / (1.f / real_aspect) : -1.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
}},
|
||||
}};
|
||||
|
||||
std::array<rhi::UniformVariant, 2> g2_uniforms =
|
||||
{{
|
||||
// ModelView
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{taller ? 2.f : 2.f * aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? 2.f * (1.f / aspect) : 2.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3>
|
||||
{{
|
||||
{vid.width / static_cast<float>(screen_tex_width_), 0.f, 0.f},
|
||||
{0.f, vid.height / static_cast<float>(screen_tex_height_), 0.f},
|
||||
{0.f, 0.f, 1.f}
|
||||
}}
|
||||
}};
|
||||
|
||||
uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms});
|
||||
uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms});
|
||||
|
||||
std::array<rhi::VertexAttributeBufferBinding, 1> vbs = {{{0, quad_vbo_}}};
|
||||
std::array<rhi::TextureBinding, 2> tbs = {{
|
||||
{rhi::SamplerName::kSampler0, screen_tex_},
|
||||
{rhi::SamplerName::kSampler1, palette_tex_}
|
||||
}};
|
||||
binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs});
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
rhi.begin_default_render_pass(ctx, true);
|
||||
rhi.bind_pipeline(ctx, pipeline_);
|
||||
rhi.bind_uniform_set(ctx, 0, uniform_sets_[0]);
|
||||
rhi.bind_uniform_set(ctx, 1, uniform_sets_[1]);
|
||||
rhi.bind_binding_set(ctx, binding_set_);
|
||||
rhi.bind_index_buffer(ctx, quad_ibo_);
|
||||
rhi.draw_indexed(ctx, 6, 0);
|
||||
rhi.end_render_pass(ctx);
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::postpass(Rhi& rhi)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
44
src/hwr2/pass_software.hpp
Normal file
44
src/hwr2/pass_software.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
#define __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class SoftwareBlitPass : public Pass
|
||||
{
|
||||
rhi::Handle<rhi::Pipeline> pipeline_;
|
||||
rhi::Handle<rhi::Texture> screen_tex_;
|
||||
rhi::Handle<rhi::Texture> palette_tex_;
|
||||
rhi::Handle<rhi::Buffer> quad_vbo_;
|
||||
rhi::Handle<rhi::Buffer> quad_ibo_;
|
||||
std::array<rhi::Handle<rhi::UniformSet>, 2> uniform_sets_;
|
||||
rhi::Handle<rhi::BindingSet> binding_set_;
|
||||
|
||||
uint32_t screen_tex_width_ = 0;
|
||||
uint32_t screen_tex_height_ = 0;
|
||||
bool quad_vbo_needs_upload_ = false;
|
||||
bool quad_ibo_needs_upload_ = false;
|
||||
|
||||
void upload_screen(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx);
|
||||
void upload_palette(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx);
|
||||
|
||||
public:
|
||||
virtual ~SoftwareBlitPass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
|
|
@ -17,6 +17,17 @@
|
|||
#include "doomtype.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "rhi/rhi.hpp"
|
||||
|
||||
namespace srb2::sys {
|
||||
|
||||
extern rhi::Handle<rhi::Rhi> g_current_rhi;
|
||||
|
||||
rhi::Rhi* get_rhi(rhi::Handle<rhi::Rhi> handle);
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
|
@ -44,10 +55,6 @@ extern rendermode_t rendermode;
|
|||
*/
|
||||
extern rendermode_t chosenrendermode;
|
||||
|
||||
/** \brief use highcolor modes if true
|
||||
*/
|
||||
extern boolean highcolor;
|
||||
|
||||
/** \brief setup video mode
|
||||
*/
|
||||
void I_StartupGraphics(void);
|
||||
|
|
|
|||
171
src/i_video_common.cpp
Normal file
171
src/i_video_common.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include "i_video.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "cxxutil.hpp"
|
||||
#include "hwr2/pass_imgui.hpp"
|
||||
#include "hwr2/pass_software.hpp"
|
||||
#include "v_video.h"
|
||||
|
||||
// KILL THIS WHEN WE KILL OLD OGL SUPPORT PLEASE
|
||||
#include "sdl/ogl_sdl.h"
|
||||
#include "st_stuff.h" // kill
|
||||
#include "d_netcmd.h" // kill
|
||||
#include "doomstat.h" // kill
|
||||
#include "s_sound.h" // kill
|
||||
#include "discord.h" // kill
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
static SoftwareBlitPass g_sw_pass;
|
||||
static ImguiPass g_imgui_pass;
|
||||
|
||||
Handle<Rhi> srb2::sys::g_current_rhi = kNullHandle;
|
||||
|
||||
static bool rhi_changed()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef HWRENDER
|
||||
static void finish_legacy_ogl_update()
|
||||
{
|
||||
int player;
|
||||
|
||||
SCR_CalculateFPS();
|
||||
|
||||
if (st_overlay)
|
||||
{
|
||||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || ! server_lagless ))
|
||||
{
|
||||
if (server_lagless)
|
||||
{
|
||||
if (consoleplayer != serverplayer)
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
SCR_DisplayLocalPing();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cv_mindelay.value && consoleplayer == serverplayer && Playing())
|
||||
SCR_DisplayLocalPing();
|
||||
}
|
||||
|
||||
if (marathonmode)
|
||||
SCR_DisplayMarathonInfo();
|
||||
|
||||
// draw captions if enabled
|
||||
if (cv_closedcaptioning.value)
|
||||
SCR_ClosedCaptions();
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
if (discordRequestList != NULL)
|
||||
ST_AskToJoinEnvelope();
|
||||
#endif
|
||||
|
||||
OglSdlFinishUpdate(cv_vidwait.value);
|
||||
}
|
||||
#endif
|
||||
|
||||
void I_FinishUpdate(void)
|
||||
{
|
||||
if (rendermode == render_none)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HWRENDER
|
||||
if (rendermode == render_opengl)
|
||||
{
|
||||
finish_legacy_ogl_update();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO move this to srb2loop
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize.x = vid.realwidth;
|
||||
io.DisplaySize.y = vid.realheight;
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (rhi_changed())
|
||||
{
|
||||
// reinitialize passes
|
||||
g_sw_pass = SoftwareBlitPass();
|
||||
g_imgui_pass = ImguiPass();
|
||||
}
|
||||
|
||||
rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi);
|
||||
|
||||
if (rhi == nullptr)
|
||||
{
|
||||
// ???
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare phase
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.prepass(*rhi);
|
||||
}
|
||||
g_imgui_pass.prepass(*rhi);
|
||||
|
||||
// Transfer phase
|
||||
Handle<TransferContext> tc;
|
||||
tc = rhi->begin_transfer();
|
||||
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.transfer(*rhi, tc);
|
||||
}
|
||||
g_imgui_pass.transfer(*rhi, tc);
|
||||
|
||||
rhi->end_transfer(tc);
|
||||
|
||||
// Graphics phase
|
||||
Handle<GraphicsContext> gc;
|
||||
gc = rhi->begin_graphics();
|
||||
|
||||
// Standard drawing passes...
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.graphics(*rhi, gc);
|
||||
}
|
||||
g_imgui_pass.graphics(*rhi, gc);
|
||||
|
||||
rhi->end_graphics(gc);
|
||||
|
||||
// Postpass phase
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.postpass(*rhi);
|
||||
}
|
||||
g_imgui_pass.postpass(*rhi);
|
||||
|
||||
// Present
|
||||
|
||||
rhi->present();
|
||||
|
||||
rhi->finish();
|
||||
}
|
||||
|
|
@ -315,7 +315,8 @@ void K_HandleFollower(player_t *player)
|
|||
|
||||
// don't do anything if we can't have a follower to begin with.
|
||||
// (It gets removed under those conditions)
|
||||
if (player->spectator || player->followerskin < 0)
|
||||
if (player->spectator || player->followerskin < 0
|
||||
|| player->mo == NULL || P_MobjWasRemoved(player->mo))
|
||||
{
|
||||
if (player->follower)
|
||||
{
|
||||
|
|
@ -377,12 +378,15 @@ void K_HandleFollower(player_t *player)
|
|||
// Set follower colour
|
||||
color = K_GetEffectiveFollowerColor(player->followercolor, player->skincolor);
|
||||
|
||||
if (player->follower == NULL) // follower doesn't exist / isn't valid
|
||||
if (player->follower == NULL || P_MobjWasRemoved(player->follower)) // follower doesn't exist / isn't valid
|
||||
{
|
||||
//CONS_Printf("Spawning follower...\n");
|
||||
|
||||
// so let's spawn one!
|
||||
P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER));
|
||||
if (player->follower == NULL)
|
||||
return;
|
||||
|
||||
K_UpdateFollowerState(player->follower, fl.idlestate, FOLLOWERSTATE_IDLE);
|
||||
|
||||
P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear
|
||||
|
|
@ -402,11 +406,9 @@ void K_HandleFollower(player_t *player)
|
|||
}
|
||||
else // follower exists, woo!
|
||||
{
|
||||
// Safety net (2)
|
||||
|
||||
if (P_MobjWasRemoved(player->follower))
|
||||
if (player->follower->hitlag != 0)
|
||||
{
|
||||
P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing.
|
||||
// Don't update frames in hitlag
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -625,4 +627,11 @@ void K_HandleFollower(player_t *player)
|
|||
K_UpdateFollowerState(player->follower, fl.idlestate, FOLLOWERSTATE_IDLE);
|
||||
}
|
||||
}
|
||||
|
||||
if (player->mo->hitlag)
|
||||
{
|
||||
player->follower->hitlag = player->mo->hitlag;
|
||||
player->follower->eflags |= (player->mo->eflags & MFE_DAMAGEHITLAG);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3386,7 +3386,7 @@ fixed_t K_GetNewSpeed(player_t *player)
|
|||
|
||||
if (player->curshield == KSHIELD_TOP)
|
||||
{
|
||||
p_speed = 11 * p_speed / 10;
|
||||
p_speed = 15 * p_speed / 10;
|
||||
}
|
||||
|
||||
if (K_PlayerUsesBotMovement(player) == true && player->botvars.rubberband > 0)
|
||||
|
|
@ -9873,7 +9873,9 @@ void K_AdjustPlayerFriction(player_t *player)
|
|||
player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->tiregrease;
|
||||
}
|
||||
|
||||
if (player->curshield == KSHIELD_TOP)
|
||||
// Less friction on Top unless grinding
|
||||
if (player->curshield == KSHIELD_TOP &&
|
||||
K_GetForwardMove(player) > 0)
|
||||
{
|
||||
player->mo->friction += 1024;
|
||||
}
|
||||
|
|
|
|||
20
src/k_menu.h
20
src/k_menu.h
|
|
@ -556,6 +556,7 @@ extern boolean menuwipe;
|
|||
|
||||
extern consvar_t cv_showfocuslost;
|
||||
extern consvar_t cv_chooseskin, cv_serversort, cv_menujam_update;
|
||||
extern consvar_t cv_autorecord;
|
||||
|
||||
void M_SetMenuDelay(UINT8 i);
|
||||
|
||||
|
|
@ -569,6 +570,17 @@ void M_MapMenuControls(event_t *ev);
|
|||
boolean M_Responder(event_t *ev);
|
||||
boolean M_MenuButtonPressed(UINT8 pid, UINT32 bt);
|
||||
boolean M_MenuButtonHeld(UINT8 pid, UINT32 bt);
|
||||
|
||||
boolean M_ChangeStringCvar(INT32 choice);
|
||||
|
||||
boolean M_NextOpt(void);
|
||||
boolean M_PrevOpt(void);
|
||||
|
||||
boolean M_MenuConfirmPressed(UINT8 pid);
|
||||
boolean M_MenuBackPressed(UINT8 pid);
|
||||
boolean M_MenuExtraPressed(UINT8 pid);
|
||||
boolean M_MenuExtraHeld(UINT8 pid);
|
||||
|
||||
void M_StartControlPanel(void);
|
||||
void M_ClearMenus(boolean callexitmenufunc);
|
||||
void M_SelectableClearMenus(INT32 choice);
|
||||
|
|
@ -577,6 +589,8 @@ void M_GoBack(INT32 choice);
|
|||
void M_Ticker(void);
|
||||
void M_Init(void);
|
||||
|
||||
void M_MenuTypingInput(INT32 key);
|
||||
|
||||
extern menu_t MessageDef;
|
||||
void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype);
|
||||
void M_StopMessage(INT32 choice);
|
||||
|
|
@ -679,6 +693,7 @@ extern consvar_t *setup_playercvars[MAXSPLITSCREENPLAYERS][SPLITCV_MAX];
|
|||
|
||||
void M_CharacterSelectInit(void);
|
||||
void M_CharacterSelect(INT32 choice);
|
||||
void M_SetupReadyExplosions(boolean charsel, UINT16 basex, UINT16 basey, UINT16 color);
|
||||
boolean M_CharacterSelectHandler(INT32 choice);
|
||||
void M_CharacterSelectTick(void);
|
||||
boolean M_CharacterSelectQuit(void);
|
||||
|
|
@ -723,6 +738,8 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch);
|
|||
UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch);
|
||||
UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch);
|
||||
UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch);
|
||||
void M_LevelSelectScrollDest(void);
|
||||
boolean M_LevelListFromGametype(INT16 gt);
|
||||
|
||||
void M_LevelSelectInit(INT32 choice);
|
||||
void M_CupSelectHandler(INT32 choice);
|
||||
|
|
@ -730,6 +747,8 @@ void M_CupSelectTick(void);
|
|||
void M_LevelSelectHandler(INT32 choice);
|
||||
void M_LevelSelectTick(void);
|
||||
|
||||
void M_LevelSelected(INT16 add);
|
||||
|
||||
// dummy consvars for GP & match race setup
|
||||
extern consvar_t cv_dummygpdifficulty;
|
||||
extern consvar_t cv_dummykartspeed;
|
||||
|
|
@ -916,6 +935,7 @@ void M_CheckProfileData(INT32 choice); // check if we have profiles.
|
|||
|
||||
// profile selection menu
|
||||
void M_ProfileSelectInit(INT32 choice);
|
||||
void M_FirstPickProfile(INT32 c);
|
||||
void M_HandleProfileSelect(INT32 ch);
|
||||
|
||||
// profile edition
|
||||
|
|
|
|||
1771
src/k_menudef.c
1771
src/k_menudef.c
File diff suppressed because it is too large
Load diff
6801
src/k_menufunc.c
6801
src/k_menufunc.c
File diff suppressed because it is too large
Load diff
|
|
@ -661,6 +661,42 @@ static void K_DropDashWait(player_t *player)
|
|||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------
|
||||
static boolean K_CanDropDash(player_t *player)
|
||||
|
||||
Checks if you can use the Drop Dash maneuver.
|
||||
|
||||
Input Arguments:-
|
||||
player - Player to check.
|
||||
|
||||
Return:-
|
||||
Whether a Drop Dash should be allowed.
|
||||
--------------------------------------------------*/
|
||||
static boolean K_CanDropDash(player_t *player)
|
||||
{
|
||||
const UINT16 buttons = K_GetKartButtons(player);
|
||||
|
||||
if (!(buttons & BT_ACCELERATE))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since we're letting players spin out on respawn, don't let them charge a dropdash in this state. (It wouldn't work anyway)
|
||||
if (player->spinouttimer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Garden Top is overpowered enough
|
||||
if (player->curshield == KSHIELD_TOP)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------
|
||||
static void K_HandleDropDash(player_t *player)
|
||||
|
||||
|
|
@ -699,7 +735,7 @@ static void K_HandleDropDash(player_t *player)
|
|||
// The old behavior was stupid and prone to accidental usage.
|
||||
// Let's rip off Mania instead, and turn this into a Drop Dash!
|
||||
|
||||
if ((buttons & BT_ACCELERATE) && !player->spinouttimer) // Since we're letting players spin out on respawn, don't let them charge a dropdash in this state. (It wouldn't work anyway)
|
||||
if (K_CanDropDash(player))
|
||||
{
|
||||
player->respawn.dropdash++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -966,26 +966,13 @@ void K_HandleFootstepParticles(mobj_t *mo)
|
|||
return;
|
||||
}
|
||||
|
||||
if (!(mo->flags & MF_APPLYTERRAIN))
|
||||
if (!(mo->flags & MF_APPLYTERRAIN) || mo->terrain == NULL)
|
||||
{
|
||||
// No TERRAIN effects for this object.
|
||||
return;
|
||||
}
|
||||
|
||||
if (mo->terrain == NULL || mo->terrain->footstepID == SIZE_MAX)
|
||||
{
|
||||
// If no terrain, check for offroad.
|
||||
// If we're in offroad, use the default particle.
|
||||
|
||||
if (mo->player != NULL && mo->player->boostpower < FRACUNIT)
|
||||
{
|
||||
fs = K_GetFootstepByIndex(defaultOffroadFootstep);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fs = K_GetFootstepByIndex(mo->terrain->footstepID);
|
||||
}
|
||||
fs = K_GetFootstepByIndex(mo->terrain->footstepID);
|
||||
|
||||
if (fs == NULL || fs->mobjType == MT_NULL || fs->frequency <= 0)
|
||||
{
|
||||
|
|
|
|||
43
src/menus/CMakeLists.txt
Normal file
43
src/menus/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
extras-1.c
|
||||
extras-addons.c
|
||||
extras-challenges.c
|
||||
extras-replay-hut.c
|
||||
extras-statistics.c
|
||||
main-1.c
|
||||
main-profile-select.c
|
||||
options-1.c
|
||||
options-data-1.c
|
||||
options-data-addons.c
|
||||
options-data-discord.c
|
||||
options-data-erase-1.c
|
||||
options-data-erase-profile.c
|
||||
options-data-replays.c
|
||||
options-data-screenshots.c
|
||||
options-gameplay-1.c
|
||||
options-gameplay-item-toggles.c
|
||||
options-hud-1.c
|
||||
options-hud-online.c
|
||||
options-profiles-1.c
|
||||
options-profiles-edit-1.c
|
||||
options-profiles-edit-controls.c
|
||||
options-server-1.c
|
||||
options-server-advanced.c
|
||||
options-sound.c
|
||||
options-video-1.c
|
||||
options-video-gl.c
|
||||
options-video-modes.c
|
||||
play-1.c
|
||||
play-char-select.c
|
||||
play-local-1.c
|
||||
play-local-race-1.c
|
||||
play-local-race-difficulty.c
|
||||
play-local-race-time-attack.c
|
||||
play-online-1.c
|
||||
play-online-host.c
|
||||
play-online-join-ip.c
|
||||
play-online-room-select.c
|
||||
play-online-server-browser.c
|
||||
)
|
||||
|
||||
add_subdirectory(transient)
|
||||
139
src/menus/extras-1.c
Normal file
139
src/menus/extras-1.c
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/// \file menus/extras-1.c
|
||||
/// \brief Extras Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t EXTRAS_Main[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.",
|
||||
NULL, {.routine = M_Addons}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!",
|
||||
NULL, {.routine = M_Challenges}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Replay Hut", "Play the replays you've saved throughout your many races & battles!",
|
||||
NULL, {.routine = M_ReplayHut}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!",
|
||||
NULL, {.routine = M_Statistics}, 0, 0},
|
||||
};
|
||||
|
||||
// the extras menu essentially reuses the options menu stuff
|
||||
menu_t EXTRAS_MainDef = {
|
||||
sizeof (EXTRAS_Main) / sizeof (menuitem_t),
|
||||
&MainDef,
|
||||
0,
|
||||
EXTRAS_Main,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawExtras,
|
||||
M_ExtrasTick,
|
||||
NULL,
|
||||
NULL,
|
||||
M_ExtrasInputs
|
||||
};
|
||||
|
||||
// Extras menu;
|
||||
// this is copypasted from the options menu but all of these are different functions in case we ever want it to look more unique
|
||||
|
||||
struct extrasmenu_s extrasmenu;
|
||||
|
||||
void M_InitExtras(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
extrasmenu.ticker = 0;
|
||||
extrasmenu.offset = 0;
|
||||
|
||||
extrasmenu.extx = 0;
|
||||
extrasmenu.exty = 0;
|
||||
extrasmenu.textx = 0;
|
||||
extrasmenu.texty = 0;
|
||||
|
||||
M_SetupNextMenu(&EXTRAS_MainDef, false);
|
||||
}
|
||||
|
||||
// For statistics, will maybe remain unused for a while
|
||||
boolean M_ExtrasQuit(void)
|
||||
{
|
||||
extrasmenu.textx = 140-1;
|
||||
extrasmenu.texty = 70+1;
|
||||
|
||||
return true; // Always allow quitting, duh.
|
||||
}
|
||||
|
||||
void M_ExtrasTick(void)
|
||||
{
|
||||
extrasmenu.offset /= 2;
|
||||
extrasmenu.ticker++;
|
||||
|
||||
extrasmenu.extx += (extrasmenu.textx - extrasmenu.extx)/2;
|
||||
extrasmenu.exty += (extrasmenu.texty - extrasmenu.exty)/2;
|
||||
|
||||
if (abs(extrasmenu.extx - extrasmenu.exty) < 2)
|
||||
{
|
||||
extrasmenu.extx = extrasmenu.textx;
|
||||
extrasmenu.exty = extrasmenu.texty; // Avoid awkward 1 px errors.
|
||||
}
|
||||
|
||||
// Move the button for cool animations
|
||||
if (currentMenu == &EXTRAS_MainDef)
|
||||
{
|
||||
M_ExtrasQuit(); // reset the options button.
|
||||
}
|
||||
else
|
||||
{
|
||||
extrasmenu.textx = 160;
|
||||
extrasmenu.texty = 50;
|
||||
}
|
||||
}
|
||||
|
||||
boolean M_ExtrasInputs(INT32 ch)
|
||||
{
|
||||
|
||||
const UINT8 pid = 0;
|
||||
(void) ch;
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
extrasmenu.offset += 48;
|
||||
M_NextOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
|
||||
if (itemOn == 0)
|
||||
extrasmenu.offset -= currentMenu->numitems*48;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
extrasmenu.offset -= 48;
|
||||
M_PrevOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
|
||||
if (itemOn == currentMenu->numitems-1)
|
||||
extrasmenu.offset += currentMenu->numitems*48;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
|
||||
if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT)
|
||||
return true; // No.
|
||||
|
||||
extrasmenu.extx = 140;
|
||||
extrasmenu.exty = 70; // Default position for the currently selected option.
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
return false; // Don't eat.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
359
src/menus/extras-addons.c
Normal file
359
src/menus/extras-addons.c
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/// \file menus/extras-addons.c
|
||||
/// \brief Addons menu!
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../filesrch.h" // Addfile
|
||||
#include "../d_main.h"
|
||||
#include "../z_zone.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../v_video.h"
|
||||
|
||||
menuitem_t MISC_AddonsMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, NULL,
|
||||
NULL, {.cvar = &cv_dummyaddonsearch}, 0, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, NULL,
|
||||
NULL, {.routine = M_HandleAddons}, 0, 0}, // dummy menuitem for the control func
|
||||
};
|
||||
|
||||
menu_t MISC_AddonsDef = {
|
||||
sizeof (MISC_AddonsMenu)/sizeof (menuitem_t),
|
||||
NULL,
|
||||
0,
|
||||
MISC_AddonsMenu,
|
||||
50, 28,
|
||||
0, 0,
|
||||
0, 0,
|
||||
M_DrawAddons,
|
||||
M_AddonsRefresh,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
// Addons menu: (Merely copypasted, original code by toaster)
|
||||
|
||||
static void M_UpdateAddonsSearch(void);
|
||||
consvar_t cv_dummyaddonsearch = CVAR_INIT ("dummyaddonsearch", "", CV_HIDDEN|CV_CALL|CV_NOINIT, NULL, M_UpdateAddonsSearch);
|
||||
|
||||
void M_Addons(INT32 choice)
|
||||
{
|
||||
const char *pathname = ".";
|
||||
|
||||
(void)choice;
|
||||
|
||||
#if 1
|
||||
if (cv_addons_option.value == 0)
|
||||
pathname = addonsdir;
|
||||
else if (cv_addons_option.value == 1)
|
||||
pathname = srb2home;
|
||||
else if (cv_addons_option.value == 2)
|
||||
pathname = srb2path;
|
||||
else
|
||||
#endif
|
||||
if (cv_addons_option.value == 3 && *cv_addons_folder.string != '\0')
|
||||
pathname = cv_addons_folder.string;
|
||||
|
||||
strlcpy(menupath, pathname, 1024);
|
||||
menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath) + 1;
|
||||
|
||||
if (menupath[menupathindex[menudepthleft]-2] != PATHSEP[0])
|
||||
{
|
||||
menupath[menupathindex[menudepthleft]-1] = PATHSEP[0];
|
||||
menupath[menupathindex[menudepthleft]] = 0;
|
||||
}
|
||||
else
|
||||
--menupathindex[menudepthleft];
|
||||
|
||||
if (!preparefilemenu(false, false))
|
||||
{
|
||||
M_StartMessage(va("No files/folders found.\n\n%s\n\nPress (B)\n", LOCATIONSTRING1),NULL,MM_NOTHING);
|
||||
return;
|
||||
}
|
||||
else
|
||||
dir_on[menudepthleft] = 0;
|
||||
|
||||
MISC_AddonsDef.lastOn = 0; // Always start on search
|
||||
|
||||
MISC_AddonsDef.prevMenu = currentMenu;
|
||||
M_SetupNextMenu(&MISC_AddonsDef, false);
|
||||
}
|
||||
|
||||
|
||||
char *M_AddonsHeaderPath(void)
|
||||
{
|
||||
UINT32 len;
|
||||
static char header[1024];
|
||||
|
||||
strlcpy(header, va("%s folder%s", cv_addons_option.string, menupath+menupathindex[menudepth-1]-1), 1024);
|
||||
len = strlen(header);
|
||||
if (len > 34)
|
||||
{
|
||||
len = len-34;
|
||||
header[len] = header[len+1] = header[len+2] = '.';
|
||||
}
|
||||
else
|
||||
len = 0;
|
||||
|
||||
return header+len;
|
||||
}
|
||||
|
||||
#define UNEXIST S_StartSound(NULL, sfx_s26d);\
|
||||
M_SetupNextMenu(MISC_AddonsDef.prevMenu, false);\
|
||||
M_StartMessage(va("\x82%s\x80\nThis folder no longer exists!\nAborting to main menu.\n\nPress (B)\n", M_AddonsHeaderPath()),NULL,MM_NOTHING)
|
||||
|
||||
#define CLEARNAME Z_Free(refreshdirname);\
|
||||
refreshdirname = NULL
|
||||
|
||||
static boolean prevmajormods = false;
|
||||
|
||||
static void M_AddonsClearName(INT32 choice)
|
||||
{
|
||||
if (!majormods || prevmajormods)
|
||||
{
|
||||
CLEARNAME;
|
||||
}
|
||||
M_StopMessage(choice);
|
||||
}
|
||||
|
||||
// Handles messages for addon errors.
|
||||
void M_AddonsRefresh(void)
|
||||
{
|
||||
if ((refreshdirmenu & REFRESHDIR_NORMAL) && !preparefilemenu(true, false))
|
||||
{
|
||||
UNEXIST;
|
||||
if (refreshdirname)
|
||||
{
|
||||
CLEARNAME;
|
||||
}
|
||||
return;// true;
|
||||
}
|
||||
|
||||
#ifdef DEVELOP
|
||||
prevmajormods = majormods;
|
||||
#else
|
||||
if (!majormods && prevmajormods)
|
||||
prevmajormods = false;
|
||||
#endif
|
||||
|
||||
if ((refreshdirmenu & REFRESHDIR_ADDFILE) || (majormods && !prevmajormods))
|
||||
{
|
||||
char *message = NULL;
|
||||
|
||||
if (refreshdirmenu & REFRESHDIR_NOTLOADED)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s26d);
|
||||
if (refreshdirmenu & REFRESHDIR_MAX)
|
||||
message = va("%c%s\x80\nMaximum number of addons reached.\nA file could not be loaded.\nIf you wish to play with this addon, restart the game to clear existing ones.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
|
||||
else
|
||||
message = va("%c%s\x80\nA file was not loaded.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
|
||||
}
|
||||
else if (refreshdirmenu & (REFRESHDIR_WARNING|REFRESHDIR_ERROR))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s224);
|
||||
message = va("%c%s\x80\nA file was loaded with %s.\nCheck the console log for more info.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname, ((refreshdirmenu & REFRESHDIR_ERROR) ? "errors" : "warnings"));
|
||||
}
|
||||
else if (majormods && !prevmajormods)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s221);
|
||||
message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname);
|
||||
prevmajormods = majormods;
|
||||
}
|
||||
|
||||
if (message)
|
||||
{
|
||||
M_StartMessage(message,FUNCPTRCAST(M_AddonsClearName),MM_YESNO);
|
||||
return;// true;
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s221);
|
||||
CLEARNAME;
|
||||
}
|
||||
|
||||
return;// false;
|
||||
}
|
||||
|
||||
static void M_AddonExec(INT32 ch)
|
||||
{
|
||||
if (ch == MA_YES)
|
||||
{
|
||||
S_StartSound(NULL, sfx_zoom);
|
||||
COM_BufAddText(va("exec \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
|
||||
}
|
||||
}
|
||||
|
||||
static void M_UpdateAddonsSearch(void)
|
||||
{
|
||||
menusearch[0] = strlen(cv_dummyaddonsearch.string);
|
||||
strlcpy(menusearch+1, cv_dummyaddonsearch.string, MAXSTRINGLENGTH);
|
||||
if (!cv_addons_search_case.value)
|
||||
strupr(menusearch+1);
|
||||
|
||||
#if 0 // much slower
|
||||
if (!preparefilemenu(true, false))
|
||||
{
|
||||
UNEXIST;
|
||||
return;
|
||||
}
|
||||
#else // streamlined
|
||||
searchfilemenu(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void M_HandleAddons(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
boolean exitmenu = false; // exit to previous menu
|
||||
|
||||
(void) choice;
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
if (dir_on[menudepthleft] < sizedirmenu-1)
|
||||
{
|
||||
dir_on[menudepthleft]++;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
}
|
||||
else if (M_NextOpt())
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
}
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
if (dir_on[menudepthleft])
|
||||
{
|
||||
dir_on[menudepthleft]--;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
}
|
||||
else if (M_PrevOpt())
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
}
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuButtonPressed(pid, MBT_L))
|
||||
{
|
||||
UINT8 i;
|
||||
for (i = numaddonsshown; i && (dir_on[menudepthleft] < sizedirmenu-1); i--)
|
||||
dir_on[menudepthleft]++;
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuButtonPressed(pid, MBT_R))
|
||||
{
|
||||
UINT8 i;
|
||||
for (i = numaddonsshown; i && (dir_on[menudepthleft]); i--)
|
||||
dir_on[menudepthleft]--;
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
boolean refresh = true;
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
if (!dirmenu[dir_on[menudepthleft]])
|
||||
S_StartSound(NULL, sfx_s26d);
|
||||
else
|
||||
{
|
||||
switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE])
|
||||
{
|
||||
case EXT_FOLDER:
|
||||
strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING);
|
||||
if (menudepthleft)
|
||||
{
|
||||
menupathindex[--menudepthleft] = strlen(menupath);
|
||||
menupath[menupathindex[menudepthleft]] = 0;
|
||||
|
||||
if (!preparefilemenu(false, false))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s224);
|
||||
M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
|
||||
menupath[menupathindex[++menudepthleft]] = 0;
|
||||
|
||||
if (!preparefilemenu(true, false))
|
||||
{
|
||||
UNEXIST;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
dir_on[menudepthleft] = 1;
|
||||
}
|
||||
refresh = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(NULL, sfx_s26d);
|
||||
M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
|
||||
menupath[menupathindex[menudepthleft]] = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case EXT_UP:
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
menupath[menupathindex[++menudepthleft]] = 0;
|
||||
if (!preparefilemenu(false, false))
|
||||
{
|
||||
UNEXIST;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case EXT_TXT:
|
||||
M_StartMessage(va("%c%s\x80\nThis file may not be a console script.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO);
|
||||
break;
|
||||
|
||||
case EXT_CFG:
|
||||
M_StartMessage(va("%c%s\x80\nThis file may modify your settings.\nAttempt to run anyways? \n\nPress (A) to confirm or (B) to cancel\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), dirmenu[dir_on[menudepthleft]]+DIR_STRING),FUNCPTRCAST(M_AddonExec),MM_YESNO);
|
||||
break;
|
||||
|
||||
case EXT_LUA:
|
||||
case EXT_SOC:
|
||||
case EXT_WAD:
|
||||
#ifdef USE_KART
|
||||
case EXT_KART:
|
||||
#endif
|
||||
case EXT_PK3:
|
||||
COM_BufAddText(va("addfile \"%s%s\"", menupath, dirmenu[dir_on[menudepthleft]]+DIR_STRING));
|
||||
break;
|
||||
|
||||
default:
|
||||
S_StartSound(NULL, sfx_s26d);
|
||||
}
|
||||
|
||||
if (refresh)
|
||||
refreshdirmenu |= REFRESHDIR_NORMAL;
|
||||
}
|
||||
}
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
exitmenu = true;
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
|
||||
if (exitmenu)
|
||||
{
|
||||
closefilemenu(true);
|
||||
|
||||
// Secret menu!
|
||||
//MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
|
||||
|
||||
if (currentMenu->prevMenu)
|
||||
M_SetupNextMenu(currentMenu->prevMenu, false);
|
||||
else
|
||||
M_ClearMenus(true);
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
601
src/menus/extras-challenges.c
Normal file
601
src/menus/extras-challenges.c
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
/// \file menus/extras-challenges.c
|
||||
/// \brief Challenges.
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
#include "../m_random.h" // And just some randomness for the exits.
|
||||
#include "../z_zone.h"
|
||||
#include "../r_skins.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t MISC_ChallengesStatsDummyMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t MISC_ChallengesDef = {
|
||||
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
|
||||
&MainDef,
|
||||
0,
|
||||
MISC_ChallengesStatsDummyMenu,
|
||||
BASEVIDWIDTH/2, 32,
|
||||
0, 0,
|
||||
98, 0,
|
||||
M_DrawChallenges,
|
||||
M_ChallengesTick,
|
||||
NULL,
|
||||
NULL,
|
||||
M_ChallengesInputs,
|
||||
};
|
||||
|
||||
// This must be defined here so it can take sizeof
|
||||
// MISC_ChallengesStatsDummyMenu :V
|
||||
menu_t MISC_StatisticsDef = {
|
||||
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
|
||||
&MainDef,
|
||||
0,
|
||||
MISC_ChallengesStatsDummyMenu,
|
||||
280, 185,
|
||||
0, 0,
|
||||
98, 0,
|
||||
M_DrawStatistics,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
M_StatisticsInputs,
|
||||
};
|
||||
|
||||
struct challengesmenu_s challengesmenu;
|
||||
|
||||
menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
|
||||
{
|
||||
UINT8 i;
|
||||
|
||||
M_UpdateUnlockablesAndExtraEmblems(false);
|
||||
|
||||
if ((challengesmenu.pending = challengesmenu.requestnew = (M_GetNextAchievedUnlock() < MAXUNLOCKABLES)))
|
||||
{
|
||||
MISC_ChallengesDef.prevMenu = desiredmenu;
|
||||
}
|
||||
|
||||
if (challengesmenu.pending || desiredmenu == NULL)
|
||||
{
|
||||
challengesmenu.currentunlock = MAXUNLOCKABLES;
|
||||
challengesmenu.unlockcondition = NULL;
|
||||
|
||||
M_PopulateChallengeGrid();
|
||||
if (gamedata->challengegrid)
|
||||
challengesmenu.extradata = M_ChallengeGridExtraData();
|
||||
|
||||
memset(setup_explosions, 0, sizeof(setup_explosions));
|
||||
memset(&challengesmenu.unlockcount, 0, sizeof(challengesmenu.unlockcount));
|
||||
for (i = 0; i < MAXUNLOCKABLES; i++)
|
||||
{
|
||||
if (!unlockables[i].conditionset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
challengesmenu.unlockcount[CC_TOTAL]++;
|
||||
|
||||
if (!gamedata->unlocked[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
challengesmenu.unlockcount[CC_UNLOCKED]++;
|
||||
}
|
||||
|
||||
return &MISC_ChallengesDef;
|
||||
}
|
||||
|
||||
return desiredmenu;
|
||||
}
|
||||
|
||||
static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh)
|
||||
{
|
||||
UINT8 i;
|
||||
SINT8 work;
|
||||
|
||||
if (unlockid >= MAXUNLOCKABLES)
|
||||
return;
|
||||
|
||||
challengesmenu.currentunlock = unlockid;
|
||||
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
|
||||
challengesmenu.unlockanim = 0;
|
||||
|
||||
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL)
|
||||
return;
|
||||
|
||||
for (i = 0; i < (CHALLENGEGRIDHEIGHT * gamedata->challengegridwidth); i++)
|
||||
{
|
||||
if (gamedata->challengegrid[i] != unlockid)
|
||||
{
|
||||
// Not what we're looking for.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (challengesmenu.extradata[i] & CHE_CONNECTEDLEFT)
|
||||
{
|
||||
// no need to check for CHE_CONNECTEDUP in linear iteration
|
||||
continue;
|
||||
}
|
||||
|
||||
// Helper calculation for non-fresh scrolling.
|
||||
work = (challengesmenu.col + challengesmenu.focusx);
|
||||
|
||||
challengesmenu.col = challengesmenu.hilix = i/CHALLENGEGRIDHEIGHT;
|
||||
challengesmenu.row = challengesmenu.hiliy = i%CHALLENGEGRIDHEIGHT;
|
||||
|
||||
if (fresh)
|
||||
{
|
||||
// We're just entering the menu. Immediately jump to the desired position...
|
||||
challengesmenu.focusx = 0;
|
||||
// ...and since the menu is even-width, randomly select whether it's left or right of center.
|
||||
if (!unlockables[unlockid].majorunlock
|
||||
&& M_RandomChance(FRACUNIT/2))
|
||||
challengesmenu.focusx--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're jumping between multiple unlocks in sequence. Get the difference (looped from -range/2 < work <= range/2).
|
||||
work -= challengesmenu.col;
|
||||
if (work <= -gamedata->challengegridwidth/2)
|
||||
work += gamedata->challengegridwidth;
|
||||
else if (work >= gamedata->challengegridwidth/2)
|
||||
work -= gamedata->challengegridwidth;
|
||||
|
||||
if (work > 0)
|
||||
{
|
||||
// We only need to scroll as far as the rightward edge.
|
||||
if (unlockables[unlockid].majorunlock)
|
||||
{
|
||||
work--;
|
||||
challengesmenu.col++;
|
||||
if (challengesmenu.col >= gamedata->challengegridwidth)
|
||||
challengesmenu.col = 0;
|
||||
}
|
||||
|
||||
// Offset right, scroll left?
|
||||
if (work > LEFTUNLOCKSCROLL)
|
||||
{
|
||||
work -= LEFTUNLOCKSCROLL;
|
||||
challengesmenu.focusx = LEFTUNLOCKSCROLL;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.focusx = work;
|
||||
work = 0;
|
||||
}
|
||||
}
|
||||
else if (work < 0)
|
||||
{
|
||||
// Offset left, scroll right?
|
||||
if (work < -RIGHTUNLOCKSCROLL)
|
||||
{
|
||||
challengesmenu.focusx = -RIGHTUNLOCKSCROLL;
|
||||
work += RIGHTUNLOCKSCROLL;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.focusx = work;
|
||||
work = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're right where we want to be.
|
||||
challengesmenu.focusx = 0;
|
||||
}
|
||||
|
||||
// And put the pixel-based scrolling in play, too.
|
||||
challengesmenu.offset = -work*16;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void M_Challenges(INT32 choice)
|
||||
{
|
||||
UINT8 i;
|
||||
(void)choice;
|
||||
|
||||
M_InterruptMenuWithChallenges(NULL);
|
||||
MISC_ChallengesDef.prevMenu = currentMenu;
|
||||
|
||||
if (gamedata->challengegrid != NULL && !challengesmenu.pending)
|
||||
{
|
||||
UINT8 selection[MAXUNLOCKABLES];
|
||||
UINT8 numunlocks = 0;
|
||||
|
||||
// Get a random available unlockable.
|
||||
for (i = 0; i < MAXUNLOCKABLES; i++)
|
||||
{
|
||||
if (!unlockables[i].conditionset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!gamedata->unlocked[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
selection[numunlocks++] = i;
|
||||
}
|
||||
|
||||
if (!numunlocks)
|
||||
{
|
||||
// ...OK, get a random unlockable.
|
||||
for (i = 0; i < MAXUNLOCKABLES; i++)
|
||||
{
|
||||
if (!unlockables[i].conditionset)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
selection[numunlocks++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true);
|
||||
}
|
||||
|
||||
M_SetupNextMenu(&MISC_ChallengesDef, false);
|
||||
}
|
||||
|
||||
void M_ChallengesTick(void)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
UINT8 i, newunlock = MAXUNLOCKABLES;
|
||||
boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES);
|
||||
|
||||
// Ticking
|
||||
challengesmenu.ticker++;
|
||||
challengesmenu.offset /= 2;
|
||||
for (i = 0; i < CSEXPLOSIONS; i++)
|
||||
{
|
||||
if (setup_explosions[i].tics > 0)
|
||||
setup_explosions[i].tics--;
|
||||
}
|
||||
if (challengesmenu.unlockcount[CC_ANIM] > 0)
|
||||
challengesmenu.unlockcount[CC_ANIM]--;
|
||||
M_CupSelectTick();
|
||||
|
||||
if (challengesmenu.pending)
|
||||
{
|
||||
// Pending mode.
|
||||
|
||||
if (challengesmenu.requestnew)
|
||||
{
|
||||
// The menu apparatus is requesting a new unlock.
|
||||
challengesmenu.requestnew = false;
|
||||
if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES)
|
||||
{
|
||||
// We got one!
|
||||
M_ChallengesAutoFocus(newunlock, fresh);
|
||||
}
|
||||
else
|
||||
{
|
||||
// All done! Let's save the unlocks we've busted open.
|
||||
challengesmenu.pending = false;
|
||||
G_SaveGameData();
|
||||
}
|
||||
}
|
||||
else if (challengesmenu.fade < 5)
|
||||
{
|
||||
// Fade increase.
|
||||
challengesmenu.fade++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unlock sequence.
|
||||
tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME;
|
||||
|
||||
if (++challengesmenu.unlockanim >= nexttime)
|
||||
{
|
||||
challengesmenu.requestnew = true;
|
||||
}
|
||||
|
||||
if (challengesmenu.currentunlock < MAXUNLOCKABLES
|
||||
&& challengesmenu.unlockanim == UNLOCKTIME)
|
||||
{
|
||||
// Unlock animation... also tied directly to the actual unlock!
|
||||
gamedata->unlocked[challengesmenu.currentunlock] = true;
|
||||
M_UpdateUnlockablesAndExtraEmblems(true);
|
||||
|
||||
// Update shown description just in case..?
|
||||
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
|
||||
|
||||
challengesmenu.unlockcount[CC_TALLY]++;
|
||||
challengesmenu.unlockcount[CC_ANIM]++;
|
||||
|
||||
Z_Free(challengesmenu.extradata);
|
||||
if ((challengesmenu.extradata = M_ChallengeGridExtraData()))
|
||||
{
|
||||
unlockable_t *ref = &unlockables[challengesmenu.currentunlock];
|
||||
UINT16 bombcolor = SKINCOLOR_NONE;
|
||||
|
||||
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
|
||||
{
|
||||
bombcolor = ref->color;
|
||||
}
|
||||
else switch (ref->type)
|
||||
{
|
||||
case SECRET_SKIN:
|
||||
{
|
||||
INT32 skin = M_UnlockableSkinNum(ref);
|
||||
if (skin != -1)
|
||||
{
|
||||
bombcolor = skins[skin].prefcolor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SECRET_FOLLOWER:
|
||||
{
|
||||
INT32 skin = M_UnlockableFollowerNum(ref);
|
||||
if (skin != -1)
|
||||
{
|
||||
bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (bombcolor == SKINCOLOR_NONE)
|
||||
{
|
||||
bombcolor = cv_playercolor[0].value;
|
||||
}
|
||||
|
||||
i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0;
|
||||
M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor);
|
||||
if (ref->majorunlock)
|
||||
{
|
||||
M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor);
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s3k4e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
// Tick down the tally. (currently not visible)
|
||||
/*if ((challengesmenu.ticker & 1)
|
||||
&& challengesmenu.unlockcount[CC_TALLY] > 0)
|
||||
{
|
||||
challengesmenu.unlockcount[CC_TALLY]--;
|
||||
challengesmenu.unlockcount[CC_UNLOCKED]++;
|
||||
}*/
|
||||
|
||||
if (challengesmenu.fade > 0)
|
||||
{
|
||||
// Fade decrease.
|
||||
challengesmenu.fade--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean M_ChallengesInputs(INT32 ch)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
UINT8 i;
|
||||
const boolean start = M_MenuButtonPressed(pid, MBT_START);
|
||||
const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0);
|
||||
(void) ch;
|
||||
|
||||
if (challengesmenu.fade)
|
||||
{
|
||||
;
|
||||
}
|
||||
#ifdef DEVELOP
|
||||
else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging
|
||||
{
|
||||
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
|
||||
{
|
||||
Z_Free(gamedata->challengegrid);
|
||||
gamedata->challengegrid = NULL;
|
||||
gamedata->challengegridwidth = 0;
|
||||
M_PopulateChallengeGrid();
|
||||
Z_Free(challengesmenu.extradata);
|
||||
challengesmenu.extradata = M_ChallengeGridExtraData();
|
||||
|
||||
M_ChallengesAutoFocus(challengesmenu.currentunlock, true);
|
||||
|
||||
challengesmenu.pending = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
if (M_MenuBackPressed(pid) || start)
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
Z_Free(challengesmenu.extradata);
|
||||
challengesmenu.extradata = NULL;
|
||||
|
||||
challengesmenu.unlockcondition = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (challengesmenu.extradata != NULL && move)
|
||||
{
|
||||
// Determine movement around the grid
|
||||
// For right/down movement, we can pre-determine the number of steps based on extradata.
|
||||
// For left/up movement, we can't - we have to be ready to iterate twice, and break early if we don't run into a large tile.
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
i = 2;
|
||||
while (i > 0)
|
||||
{
|
||||
if (challengesmenu.row < CHALLENGEGRIDHEIGHT-1)
|
||||
{
|
||||
challengesmenu.row++;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.row = 0;
|
||||
}
|
||||
if (!(challengesmenu.extradata[
|
||||
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
|
||||
+ challengesmenu.row]
|
||||
& CHE_CONNECTEDUP))
|
||||
{
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
i = (challengesmenu.extradata[
|
||||
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
|
||||
+ challengesmenu.row]
|
||||
& CHE_CONNECTEDUP) ? 2 : 1;
|
||||
while (i > 0)
|
||||
{
|
||||
if (challengesmenu.row > 0)
|
||||
{
|
||||
challengesmenu.row--;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.row = CHALLENGEGRIDHEIGHT-1;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
if (menucmd[pid].dpad_lr > 0)
|
||||
{
|
||||
i = 2;
|
||||
while (i > 0)
|
||||
{
|
||||
// Slide the focus counter to movement, if we can.
|
||||
if (challengesmenu.focusx > -RIGHTUNLOCKSCROLL)
|
||||
{
|
||||
challengesmenu.focusx--;
|
||||
}
|
||||
|
||||
// Step the actual column right.
|
||||
if (challengesmenu.col < gamedata->challengegridwidth-1)
|
||||
{
|
||||
challengesmenu.col++;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.col = 0;
|
||||
}
|
||||
|
||||
if (!(challengesmenu.extradata[
|
||||
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
|
||||
+ challengesmenu.row]
|
||||
& CHE_CONNECTEDLEFT))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
i = (challengesmenu.extradata[
|
||||
(challengesmenu.col * CHALLENGEGRIDHEIGHT)
|
||||
+ challengesmenu.row]
|
||||
& CHE_CONNECTEDLEFT) ? 2 : 1;
|
||||
while (i > 0)
|
||||
{
|
||||
// Slide the focus counter to movement, if we can.
|
||||
if (challengesmenu.focusx < LEFTUNLOCKSCROLL)
|
||||
{
|
||||
challengesmenu.focusx++;
|
||||
}
|
||||
|
||||
// Step the actual column left.
|
||||
if (challengesmenu.col > 0)
|
||||
{
|
||||
challengesmenu.col--;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.col = gamedata->challengegridwidth-1;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
// After movement has been determined, figure out the current selection.
|
||||
i = (challengesmenu.col * CHALLENGEGRIDHEIGHT) + challengesmenu.row;
|
||||
challengesmenu.currentunlock = (gamedata->challengegrid[i]);
|
||||
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
|
||||
|
||||
challengesmenu.hilix = challengesmenu.col;
|
||||
challengesmenu.hiliy = challengesmenu.row;
|
||||
|
||||
if (challengesmenu.currentunlock < MAXUNLOCKABLES
|
||||
&& unlockables[challengesmenu.currentunlock].majorunlock)
|
||||
{
|
||||
// Adjust highlight coordinates up/to the left for large tiles.
|
||||
|
||||
if (challengesmenu.hiliy > 0 && (challengesmenu.extradata[i] & CHE_CONNECTEDUP))
|
||||
{
|
||||
challengesmenu.hiliy--;
|
||||
}
|
||||
|
||||
if ((challengesmenu.extradata[i] & CHE_CONNECTEDLEFT))
|
||||
{
|
||||
if (challengesmenu.hilix > 0)
|
||||
{
|
||||
challengesmenu.hilix--;
|
||||
}
|
||||
else
|
||||
{
|
||||
challengesmenu.hilix = gamedata->challengegridwidth-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (M_MenuConfirmPressed(pid)
|
||||
&& challengesmenu.currentunlock < MAXUNLOCKABLES
|
||||
&& gamedata->unlocked[challengesmenu.currentunlock])
|
||||
{
|
||||
switch (unlockables[challengesmenu.currentunlock].type)
|
||||
{
|
||||
case SECRET_ALTTITLE:
|
||||
CV_AddValue(&cv_alttitle, 1);
|
||||
S_StartSound(NULL, sfx_s3kc3s);
|
||||
M_SetMenuDelay(pid);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
292
src/menus/extras-replay-hut.c
Normal file
292
src/menus/extras-replay-hut.c
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/// \file menus/extras-replay-hut.c
|
||||
/// \brief Extras Menu: Replay Hut
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../filesrch.h" // Addfile
|
||||
#include "../d_main.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../v_video.h"
|
||||
#include "../z_zone.h"
|
||||
|
||||
// extras menu: replay hut
|
||||
menuitem_t EXTRAS_ReplayHut[] =
|
||||
{
|
||||
{IT_KEYHANDLER|IT_NOTHING, "", "", // Dummy menuitem for the replay list
|
||||
NULL, {.routine = M_HandleReplayHutList}, 0, 0},
|
||||
|
||||
{IT_NOTHING, "", "", // Dummy for handling wrapping to the top of the menu..
|
||||
NULL, {NULL}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t EXTRAS_ReplayHutDef =
|
||||
{
|
||||
sizeof (EXTRAS_ReplayHut)/sizeof (menuitem_t),
|
||||
&EXTRAS_MainDef,
|
||||
0,
|
||||
EXTRAS_ReplayHut,
|
||||
30, 80,
|
||||
0, 0,
|
||||
0, 0,
|
||||
M_DrawReplayHut,
|
||||
NULL,
|
||||
NULL,
|
||||
M_QuitReplayHut,
|
||||
NULL
|
||||
};
|
||||
|
||||
menuitem_t EXTRAS_ReplayStart[] =
|
||||
{
|
||||
{IT_CALL |IT_STRING, "Load Addons and Watch", NULL,
|
||||
NULL, {.routine = M_HutStartReplay}, 0, 0},
|
||||
|
||||
{IT_CALL |IT_STRING, "Load Without Addons", NULL,
|
||||
NULL, {.routine = M_HutStartReplay}, 10, 0},
|
||||
|
||||
{IT_CALL |IT_STRING, "Watch Replay", NULL,
|
||||
NULL, {.routine = M_HutStartReplay}, 10, 0},
|
||||
|
||||
{IT_SUBMENU |IT_STRING, "Go Back", NULL,
|
||||
NULL, {.submenu = &EXTRAS_ReplayHutDef}, 30, 0},
|
||||
};
|
||||
|
||||
|
||||
menu_t EXTRAS_ReplayStartDef =
|
||||
{
|
||||
sizeof (EXTRAS_ReplayStart)/sizeof (menuitem_t),
|
||||
&EXTRAS_ReplayHutDef,
|
||||
0,
|
||||
EXTRAS_ReplayStart,
|
||||
27, 80,
|
||||
0, 0,
|
||||
0, 0,
|
||||
M_DrawReplayStartMenu,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
void M_PrepReplayList(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (extrasmenu.demolist)
|
||||
Z_Free(extrasmenu.demolist);
|
||||
|
||||
extrasmenu.demolist = Z_Calloc(sizeof(menudemo_t) * sizedirmenu, PU_STATIC, NULL);
|
||||
|
||||
for (i = 0; i < sizedirmenu; i++)
|
||||
{
|
||||
if (dirmenu[i][DIR_TYPE] == EXT_UP)
|
||||
{
|
||||
extrasmenu.demolist[i].type = MD_SUBDIR;
|
||||
sprintf(extrasmenu.demolist[i].title, "UP");
|
||||
}
|
||||
else if (dirmenu[i][DIR_TYPE] == EXT_FOLDER)
|
||||
{
|
||||
extrasmenu.demolist[i].type = MD_SUBDIR;
|
||||
strncpy(extrasmenu.demolist[i].title, dirmenu[i] + DIR_STRING, 64);
|
||||
}
|
||||
else
|
||||
{
|
||||
extrasmenu.demolist[i].type = MD_NOTLOADED;
|
||||
snprintf(extrasmenu.demolist[i].filepath, sizeof extrasmenu.demolist[i].filepath,
|
||||
// 255 = UINT8 limit. dirmenu entries are restricted to this length (see DIR_LEN).
|
||||
"%s%.255s", menupath, dirmenu[i] + DIR_STRING);
|
||||
sprintf(extrasmenu.demolist[i].title, ".....");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void M_ReplayHut(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
extrasmenu.replayScrollTitle = 0;
|
||||
extrasmenu.replayScrollDelay = TICRATE;
|
||||
extrasmenu.replayScrollDir = 1;
|
||||
|
||||
if (!demo.inreplayhut)
|
||||
{
|
||||
snprintf(menupath, 1024, "%s"PATHSEP"media"PATHSEP"replay"PATHSEP"online"PATHSEP, srb2home);
|
||||
menupathindex[(menudepthleft = menudepth-1)] = strlen(menupath);
|
||||
}
|
||||
if (!preparefilemenu(false, true))
|
||||
{
|
||||
M_StartMessage("No replays found.\n\nPress (B)\n", NULL, MM_NOTHING);
|
||||
return;
|
||||
}
|
||||
else if (!demo.inreplayhut)
|
||||
dir_on[menudepthleft] = 0;
|
||||
demo.inreplayhut = true;
|
||||
|
||||
extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1;
|
||||
|
||||
M_PrepReplayList();
|
||||
|
||||
menuactive = true;
|
||||
M_SetupNextMenu(&EXTRAS_ReplayHutDef, false);
|
||||
//G_SetGamestate(GS_TIMEATTACK);
|
||||
//titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
|
||||
|
||||
demo.rewinding = false;
|
||||
CL_ClearRewinds();
|
||||
|
||||
//S_ChangeMusicInternal("replst", true);
|
||||
}
|
||||
|
||||
// key handler
|
||||
void M_HandleReplayHutList(INT32 choice)
|
||||
{
|
||||
|
||||
const UINT8 pid = 0;
|
||||
(void) choice;
|
||||
|
||||
if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
if (dir_on[menudepthleft])
|
||||
dir_on[menudepthleft]--;
|
||||
else
|
||||
return;
|
||||
//M_PrevOpt();
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1;
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
if (dir_on[menudepthleft] < sizedirmenu-1)
|
||||
dir_on[menudepthleft]++;
|
||||
else
|
||||
return;
|
||||
//itemOn = 0; // Not M_NextOpt because that would take us to the extra dummy item
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1;
|
||||
}
|
||||
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
M_QuitReplayHut();
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
switch (dirmenu[dir_on[menudepthleft]][DIR_TYPE])
|
||||
{
|
||||
case EXT_FOLDER:
|
||||
strcpy(&menupath[menupathindex[menudepthleft]],dirmenu[dir_on[menudepthleft]]+DIR_STRING);
|
||||
if (menudepthleft)
|
||||
{
|
||||
menupathindex[--menudepthleft] = strlen(menupath);
|
||||
menupath[menupathindex[menudepthleft]] = 0;
|
||||
|
||||
if (!preparefilemenu(false, true))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s224);
|
||||
M_StartMessage(va("%c%s\x80\nThis folder is empty.\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
|
||||
menupath[menupathindex[++menudepthleft]] = 0;
|
||||
|
||||
if (!preparefilemenu(true, true))
|
||||
{
|
||||
M_QuitReplayHut();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
dir_on[menudepthleft] = 1;
|
||||
M_PrepReplayList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(NULL, sfx_s26d);
|
||||
M_StartMessage(va("%c%s\x80\nThis folder is too deep to navigate to!\n\nPress (B)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), M_AddonsHeaderPath()),NULL,MM_NOTHING);
|
||||
menupath[menupathindex[menudepthleft]] = 0;
|
||||
}
|
||||
break;
|
||||
case EXT_UP:
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
menupath[menupathindex[++menudepthleft]] = 0;
|
||||
if (!preparefilemenu(false, true))
|
||||
{
|
||||
M_QuitReplayHut();
|
||||
return;
|
||||
}
|
||||
M_PrepReplayList();
|
||||
break;
|
||||
default:
|
||||
// We can't just use M_SetupNextMenu because that'll run ReplayDef's quitroutine and boot us back to the title screen!
|
||||
currentMenu->lastOn = itemOn;
|
||||
currentMenu = &EXTRAS_ReplayStartDef;
|
||||
|
||||
extrasmenu.replayScrollTitle = 0; extrasmenu.replayScrollDelay = TICRATE; extrasmenu.replayScrollDir = 1;
|
||||
|
||||
switch (extrasmenu.demolist[dir_on[menudepthleft]].addonstatus)
|
||||
{
|
||||
case DFILE_ERROR_CANNOTLOAD:
|
||||
// Only show "Watch Replay Without Addons"
|
||||
EXTRAS_ReplayStart[0].status = IT_DISABLED;
|
||||
EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING;
|
||||
//EXTRAS_ReplayStart[1].alphaKey = 0;
|
||||
EXTRAS_ReplayStart[2].status = IT_DISABLED;
|
||||
itemOn = 1;
|
||||
break;
|
||||
|
||||
case DFILE_ERROR_NOTLOADED:
|
||||
case DFILE_ERROR_INCOMPLETEOUTOFORDER:
|
||||
// Show "Load Addons and Watch Replay" and "Watch Replay Without Addons"
|
||||
EXTRAS_ReplayStart[0].status = IT_CALL|IT_STRING;
|
||||
EXTRAS_ReplayStart[1].status = IT_CALL|IT_STRING;
|
||||
//EXTRAS_ReplayStart[1].alphaKey = 10;
|
||||
EXTRAS_ReplayStart[2].status = IT_DISABLED;
|
||||
itemOn = 0;
|
||||
break;
|
||||
|
||||
case DFILE_ERROR_EXTRAFILES:
|
||||
case DFILE_ERROR_OUTOFORDER:
|
||||
default:
|
||||
// Show "Watch Replay"
|
||||
EXTRAS_ReplayStart[0].status = IT_DISABLED;
|
||||
EXTRAS_ReplayStart[1].status = IT_DISABLED;
|
||||
EXTRAS_ReplayStart[2].status = IT_CALL|IT_STRING;
|
||||
//EXTRAS_ReplayStart[2].alphaKey = 0;
|
||||
itemOn = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean M_QuitReplayHut(void)
|
||||
{
|
||||
// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
|
||||
menuactive = false;
|
||||
D_StartTitle();
|
||||
|
||||
if (extrasmenu.demolist)
|
||||
Z_Free(extrasmenu.demolist);
|
||||
extrasmenu.demolist = NULL;
|
||||
|
||||
demo.inreplayhut = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void M_HutStartReplay(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
M_ClearMenus(false);
|
||||
demo.loadfiles = (itemOn == 0);
|
||||
demo.ignorefiles = (itemOn != 0);
|
||||
|
||||
G_DoPlayDemo(extrasmenu.demolist[dir_on[menudepthleft]].filepath);
|
||||
}
|
||||
99
src/menus/extras-statistics.c
Normal file
99
src/menus/extras-statistics.c
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/// \file menus/extras-challenges.c
|
||||
/// \brief Statistics menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../z_zone.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
#include "../s_sound.h"
|
||||
|
||||
struct statisticsmenu_s statisticsmenu;
|
||||
|
||||
void M_Statistics(INT32 choice)
|
||||
{
|
||||
UINT16 i = 0;
|
||||
|
||||
(void)choice;
|
||||
|
||||
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL);
|
||||
statisticsmenu.nummaps = 0;
|
||||
|
||||
for (i = 0; i < nummapheaders; i++)
|
||||
{
|
||||
if (!mapheaderinfo[i])
|
||||
continue;
|
||||
|
||||
// Check for no visibility + legacy box
|
||||
if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))
|
||||
continue;
|
||||
|
||||
// Check for completion
|
||||
if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED)
|
||||
&& !(mapheaderinfo[i]->mapvisited & MV_BEATEN))
|
||||
continue;
|
||||
|
||||
// Check for unlock
|
||||
if (M_MapLocked(i+1))
|
||||
continue;
|
||||
|
||||
statisticsmenu.maplist[statisticsmenu.nummaps++] = i;
|
||||
}
|
||||
statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID;
|
||||
statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10;
|
||||
statisticsmenu.location = 0;
|
||||
|
||||
if (statisticsmenu.maxscroll < 0)
|
||||
{
|
||||
statisticsmenu.maxscroll = 0;
|
||||
}
|
||||
|
||||
MISC_StatisticsDef.prevMenu = currentMenu;
|
||||
M_SetupNextMenu(&MISC_StatisticsDef, false);
|
||||
}
|
||||
|
||||
boolean M_StatisticsInputs(INT32 ch)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
|
||||
(void)ch;
|
||||
|
||||
if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
Z_Free(statisticsmenu.maplist);
|
||||
statisticsmenu.maplist = NULL;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (M_MenuExtraPressed(pid))
|
||||
{
|
||||
if (statisticsmenu.location > 0)
|
||||
{
|
||||
statisticsmenu.location = 0;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
if (statisticsmenu.location < statisticsmenu.maxscroll)
|
||||
{
|
||||
statisticsmenu.location++;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
if (statisticsmenu.location > 0)
|
||||
{
|
||||
statisticsmenu.location--;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
112
src/menus/main-1.c
Normal file
112
src/menus/main-1.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/// \file menus/main-1.c
|
||||
/// \brief Main Menu
|
||||
|
||||
// ==========================================================================
|
||||
// ORGANIZATION START.
|
||||
// ==========================================================================
|
||||
// Note: Never should we be jumping from one category of menu options to another
|
||||
// without first going to the Main Menu.
|
||||
// Note: Ignore the above if you're working with the Pause menu.
|
||||
// Note: (Prefix)_MainMenu should be the target of all Main Menu options that
|
||||
// point to submenus.
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_random.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../i_time.h"
|
||||
#include "../v_video.h"
|
||||
#include "../z_zone.h"
|
||||
#include "../i_video.h" // I_FinishUpdate
|
||||
#include "../i_system.h" // I_Sleep
|
||||
|
||||
menuitem_t MainMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Play",
|
||||
"Cut to the chase and start the race!", NULL,
|
||||
{.routine = M_CharacterSelect}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Extras",
|
||||
"Check out some bonus features.", "MENUI001",
|
||||
{.routine = M_InitExtras}, 0, 0},
|
||||
|
||||
{IT_STRING, "Options",
|
||||
"Configure your controls, settings, and preferences.", NULL,
|
||||
{.routine = M_InitOptions}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Quit",
|
||||
"Exit \"Dr. Robotnik's Ring Racers\".", NULL,
|
||||
{.routine = M_QuitSRB2}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t MainDef = KARTGAMEMODEMENU(MainMenu, NULL);
|
||||
|
||||
// Quit Game
|
||||
static INT32 quitsounds[] =
|
||||
{
|
||||
// holy shit we're changing things up!
|
||||
// srb2kart: you ain't seen nothing yet
|
||||
sfx_kc2e,
|
||||
sfx_kc2f,
|
||||
sfx_cdfm01,
|
||||
sfx_ddash,
|
||||
sfx_s3ka2,
|
||||
sfx_s3k49,
|
||||
sfx_slip,
|
||||
sfx_tossed,
|
||||
sfx_s3k7b,
|
||||
sfx_itrolf,
|
||||
sfx_itrole,
|
||||
sfx_cdpcm9,
|
||||
sfx_s3k4e,
|
||||
sfx_s259,
|
||||
sfx_3db06,
|
||||
sfx_s3k3a,
|
||||
sfx_peel,
|
||||
sfx_cdfm28,
|
||||
sfx_s3k96,
|
||||
sfx_s3kc0s,
|
||||
sfx_cdfm39,
|
||||
sfx_hogbom,
|
||||
sfx_kc5a,
|
||||
sfx_kc46,
|
||||
sfx_s3k92,
|
||||
sfx_s3k42,
|
||||
sfx_kpogos,
|
||||
sfx_screec
|
||||
};
|
||||
|
||||
void M_QuitSRB2(INT32 choice)
|
||||
{
|
||||
// We pick index 0 which is language sensitive, or one at random,
|
||||
// between 1 and maximum number.
|
||||
(void)choice;
|
||||
M_StartMessage("Are you sure you want to quit playing?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_QuitResponse), MM_YESNO);
|
||||
}
|
||||
|
||||
void M_QuitResponse(INT32 ch)
|
||||
{
|
||||
tic_t ptime;
|
||||
INT32 mrand;
|
||||
|
||||
if (ch == MA_YES)
|
||||
{
|
||||
if (!(netgame || cht_debug))
|
||||
{
|
||||
mrand = M_RandomKey(sizeof(quitsounds) / sizeof(INT32));
|
||||
if (quitsounds[mrand])
|
||||
S_StartSound(NULL, quitsounds[mrand]);
|
||||
|
||||
//added : 12-02-98: do that instead of I_WaitVbl which does not work
|
||||
ptime = I_GetTime() + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds Tails 03-26-2001
|
||||
while (ptime > I_GetTime())
|
||||
{
|
||||
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
|
||||
V_DrawSmallScaledPatch(0, 0, 0, W_CachePatchName("GAMEQUIT", PU_CACHE)); // Demo 3 Quit Screen Tails 06-16-2001
|
||||
I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
|
||||
I_Sleep(cv_sleep.value);
|
||||
I_UpdateTime(cv_timescale.value);
|
||||
}
|
||||
}
|
||||
I_Quit();
|
||||
}
|
||||
}
|
||||
24
src/menus/main-profile-select.c
Normal file
24
src/menus/main-profile-select.c
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/// \file menus/main-profile-select.c
|
||||
/// \brief Duplicate for main profile select.
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t MAIN_Profiles[] = {
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Select a profile to use or create a new Profile.",
|
||||
NULL, {.routine = M_HandleProfileSelect}, 0, 0}, // dummy menuitem for the control func
|
||||
};
|
||||
|
||||
menu_t MAIN_ProfilesDef = {
|
||||
sizeof (MAIN_Profiles) / sizeof (menuitem_t),
|
||||
NULL,
|
||||
0,
|
||||
MAIN_Profiles,
|
||||
32, 80,
|
||||
SKINCOLOR_ULTRAMARINE, 0,
|
||||
2, 5,
|
||||
M_DrawProfileSelect,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
230
src/menus/options-1.c
Normal file
230
src/menus/options-1.c
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/// \file menus/options-1.c
|
||||
/// \brief Options Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../k_grandprix.h" // K_CanChangeRules
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
#include "../s_sound.h"
|
||||
|
||||
// options menu -- see mopt_e
|
||||
menuitem_t OPTIONS_Main[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CALL, "Profile Setup", "Remap keys & buttons to your likings.",
|
||||
NULL, {.routine = M_ProfileSelectInit}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Video Options", "Change video settings such as the resolution.",
|
||||
NULL, {.submenu = &OPTIONS_VideoDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Sound Options", "Adjust various sound settings such as the volume.",
|
||||
NULL, {.submenu = &OPTIONS_SoundDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "HUD Options", "Options related to the Heads-Up Display.",
|
||||
NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Gameplay Options", "Change various game related options",
|
||||
NULL, {.submenu = &OPTIONS_GameplayDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Server Options", "Change various specific options for your game server.",
|
||||
NULL, {.submenu = &OPTIONS_ServerDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Data Options", "Miscellaneous data options such as the screenshot format.",
|
||||
NULL, {.submenu = &OPTIONS_DataDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Tricks & Secrets", "Those who bother reading a game manual always get the edge over those who don't!",
|
||||
NULL, {.routine = M_Manual}, 0, 0},
|
||||
};
|
||||
|
||||
// For options menu, the 'extra1' field will determine the background colour to use for... the background! (What a concept!)
|
||||
menu_t OPTIONS_MainDef = {
|
||||
sizeof (OPTIONS_Main) / sizeof (menuitem_t),
|
||||
&MainDef,
|
||||
0,
|
||||
OPTIONS_Main,
|
||||
0, 0,
|
||||
SKINCOLOR_SLATE, 0,
|
||||
2, 5,
|
||||
M_DrawOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
M_OptionsInputs
|
||||
};
|
||||
|
||||
struct optionsmenu_s optionsmenu;
|
||||
|
||||
void M_ResetOptions(void)
|
||||
{
|
||||
optionsmenu.ticker = 0;
|
||||
optionsmenu.offset = 0;
|
||||
|
||||
optionsmenu.optx = 0;
|
||||
optionsmenu.opty = 0;
|
||||
optionsmenu.toptx = 0;
|
||||
optionsmenu.topty = 0;
|
||||
|
||||
// BG setup:
|
||||
optionsmenu.currcolour = OPTIONS_MainDef.extra1;
|
||||
optionsmenu.lastcolour = 0;
|
||||
optionsmenu.fade = 0;
|
||||
|
||||
// For profiles:
|
||||
memset(setup_player, 0, sizeof(setup_player));
|
||||
optionsmenu.profile = NULL;
|
||||
}
|
||||
|
||||
void M_InitOptions(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_TRANSTEXT;
|
||||
OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_TRANSTEXT;
|
||||
|
||||
// enable gameplay & server options under the right circumstances.
|
||||
if (gamestate == GS_MENU
|
||||
|| ((server || IsPlayerAdmin(consoleplayer)) && K_CanChangeRules(false)))
|
||||
{
|
||||
OPTIONS_MainDef.menuitems[mopt_gameplay].status = IT_STRING | IT_SUBMENU;
|
||||
OPTIONS_MainDef.menuitems[mopt_server].status = IT_STRING | IT_SUBMENU;
|
||||
OPTIONS_GameplayDef.menuitems[gopt_encore].status =
|
||||
(M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED);
|
||||
}
|
||||
|
||||
OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU
|
||||
? (IT_STRING | IT_SUBMENU)
|
||||
: (IT_TRANSTEXT2 | IT_SPACE));
|
||||
|
||||
M_ResetOptions();
|
||||
|
||||
// So that pause doesn't go to the main menu...
|
||||
OPTIONS_MainDef.prevMenu = currentMenu;
|
||||
|
||||
// This will disable or enable the textboxes of the affected menus before we get to them.
|
||||
Screenshot_option_Onchange();
|
||||
Moviemode_mode_Onchange();
|
||||
Moviemode_option_Onchange();
|
||||
Addons_option_Onchange();
|
||||
|
||||
M_SetupNextMenu(&OPTIONS_MainDef, false);
|
||||
}
|
||||
|
||||
// Prepares changing the colour of the background
|
||||
void M_OptionsChangeBGColour(INT16 newcolour)
|
||||
{
|
||||
optionsmenu.fade = 10;
|
||||
optionsmenu.lastcolour = optionsmenu.currcolour;
|
||||
optionsmenu.currcolour = newcolour;
|
||||
}
|
||||
|
||||
boolean M_OptionsQuit(void)
|
||||
{
|
||||
optionsmenu.toptx = 140-1;
|
||||
optionsmenu.topty = 70+1;
|
||||
|
||||
// Reset button behaviour because profile menu is different, since of course it is.
|
||||
if (optionsmenu.resetprofilemenu)
|
||||
{
|
||||
optionsmenu.profilemenu = false;
|
||||
optionsmenu.profile = NULL;
|
||||
optionsmenu.resetprofilemenu = false;
|
||||
}
|
||||
|
||||
return true; // Always allow quitting, duh.
|
||||
}
|
||||
|
||||
void M_OptionsTick(void)
|
||||
{
|
||||
optionsmenu.offset /= 2;
|
||||
optionsmenu.ticker++;
|
||||
|
||||
optionsmenu.optx += (optionsmenu.toptx - optionsmenu.optx)/2;
|
||||
optionsmenu.opty += (optionsmenu.topty - optionsmenu.opty)/2;
|
||||
|
||||
if (abs(optionsmenu.optx - optionsmenu.opty) < 2)
|
||||
{
|
||||
optionsmenu.optx = optionsmenu.toptx;
|
||||
optionsmenu.opty = optionsmenu.topty; // Avoid awkward 1 px errors.
|
||||
}
|
||||
|
||||
// Move the button for cool animations
|
||||
if (currentMenu == &OPTIONS_MainDef)
|
||||
{
|
||||
M_OptionsQuit(); // ...So now this is used here.
|
||||
}
|
||||
else if (optionsmenu.profile == NULL) // Not currently editing a profile (otherwise we're using these variables for other purposes....)
|
||||
{
|
||||
// I don't like this, it looks like shit but it needs to be done..........
|
||||
if (optionsmenu.profilemenu)
|
||||
{
|
||||
optionsmenu.toptx = 420;
|
||||
optionsmenu.topty = 70+1;
|
||||
}
|
||||
else if (currentMenu == &OPTIONS_GameplayItemsDef)
|
||||
{
|
||||
optionsmenu.toptx = -160; // off the side of the screen
|
||||
optionsmenu.topty = 50;
|
||||
}
|
||||
else
|
||||
{
|
||||
optionsmenu.toptx = 160;
|
||||
optionsmenu.topty = 50;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the background stuff:
|
||||
if (optionsmenu.fade)
|
||||
optionsmenu.fade--;
|
||||
|
||||
// change the colour if we aren't matching the current menu colour
|
||||
if (optionsmenu.currcolour != currentMenu->extra1)
|
||||
M_OptionsChangeBGColour(currentMenu->extra1);
|
||||
|
||||
// And one last giggle...
|
||||
if (shitsfree)
|
||||
shitsfree--;
|
||||
}
|
||||
|
||||
boolean M_OptionsInputs(INT32 ch)
|
||||
{
|
||||
|
||||
const UINT8 pid = 0;
|
||||
(void)ch;
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
optionsmenu.offset += 48;
|
||||
M_NextOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
|
||||
if (itemOn == 0)
|
||||
optionsmenu.offset -= currentMenu->numitems*48;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
optionsmenu.offset -= 48;
|
||||
M_PrevOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
|
||||
if (itemOn == currentMenu->numitems-1)
|
||||
optionsmenu.offset += currentMenu->numitems*48;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
|
||||
if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT)
|
||||
return true; // No.
|
||||
|
||||
optionsmenu.optx = 140;
|
||||
optionsmenu.opty = 70; // Default position for the currently selected option.
|
||||
return false; // Don't eat.
|
||||
}
|
||||
return false;
|
||||
}
|
||||
45
src/menus/options-data-1.c
Normal file
45
src/menus/options-data-1.c
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/// \file menus/options-data-1.c
|
||||
/// \brief Data Options -- see dopt_e
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
// data options menu -- see dopt_e
|
||||
menuitem_t OPTIONS_Data[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Screenshot Options...", "Set options relative to screenshot and GIF capture.",
|
||||
NULL, {.submenu = &OPTIONS_DataScreenshotDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Addon Options...", "Set options relative to the addons menu.",
|
||||
NULL, {.submenu = &OPTIONS_DataAddonDef}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Replay Options...", "Set options relative to replays.",
|
||||
NULL, {.submenu = &OPTIONS_DataReplayDef}, 0, 0},
|
||||
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
{IT_STRING | IT_SUBMENU, "Discord Options...", "Set options relative to Discord Rich Presence.",
|
||||
NULL, {.submenu = &OPTIONS_DataDiscordDef}, 0, 0},
|
||||
#endif
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "\x85""Erase Data...", "Erase specific data. Be careful, what's deleted is gone forever!",
|
||||
NULL, {.submenu = &OPTIONS_DataEraseDef}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataDef = {
|
||||
sizeof (OPTIONS_Data) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Data,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
60
src/menus/options-data-addons.c
Normal file
60
src/menus/options-data-addons.c
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/// \file menus/options-data-addons.c
|
||||
/// \brief Addon Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../filesrch.h" // addons cvars
|
||||
|
||||
menuitem_t OPTIONS_DataAddon[] =
|
||||
{
|
||||
|
||||
{IT_HEADER, "MENU", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Location", "Where to start searching addons from in the menu.",
|
||||
NULL, {.cvar = &cv_addons_option}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to start searching from if the location is set to custom.",
|
||||
NULL, {.cvar = &cv_addons_folder}, 24, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Identify Addons via", "Set whether to consider the extension or contents of a file.",
|
||||
NULL, {.cvar = &cv_addons_md5}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Show Unsupported Files", "Sets whether non-addon files should be shown.",
|
||||
NULL, {.cvar = &cv_addons_showall}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_HEADER, "SEARCH", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Matching", "Set where to check for the text pattern when looking up addons via name.",
|
||||
NULL, {.cvar = &cv_addons_search_type}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Case Sensitivity", "Set whether to consider the case when searching for addons..",
|
||||
NULL, {.cvar = &cv_addons_search_case}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataAddonDef = {
|
||||
sizeof (OPTIONS_DataAddon) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataDef,
|
||||
0,
|
||||
OPTIONS_DataAddon,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void Addons_option_Onchange(void)
|
||||
{
|
||||
// Option 2 will always be the textbar.
|
||||
// (keep in mind this is a 0 indexed array and the first element is a header...)
|
||||
OPTIONS_DataAddon[2].status =
|
||||
(cv_addons_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
|
||||
}
|
||||
42
src/menus/options-data-discord.c
Normal file
42
src/menus/options-data-discord.c
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/// \file menus/options-data-discord.c
|
||||
/// \brief Discord Rich Presence Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../discord.h" // discord rpc cvars
|
||||
|
||||
menuitem_t OPTIONS_DataDiscord[] =
|
||||
{
|
||||
{IT_STRING | IT_CVAR, "Rich Presence", "Allow Discord to display game info on your status.",
|
||||
NULL, {.cvar = &cv_discordrp}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_HEADER, "RICH PRESENCE SETTINGS", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Streamer Mode", "Prevents the logging of some account information such as your tag in the console.",
|
||||
NULL, {.cvar = &cv_discordstreamer}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Allow Ask to Join", "Allow other people to request joining your game from Discord.",
|
||||
NULL, {.cvar = &cv_discordasks}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Allow Invites", "Set who is allowed to generate Discord invites to your game.",
|
||||
NULL, {.cvar = &cv_discordinvites}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataDiscordDef = {
|
||||
sizeof (OPTIONS_DataDiscord) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataDef,
|
||||
0,
|
||||
OPTIONS_DataDiscord,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
84
src/menus/options-data-erase-1.c
Normal file
84
src/menus/options-data-erase-1.c
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/// \file menus/options-data-erase-1.c
|
||||
/// \brief Erase Data Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
#include "../f_finale.h"
|
||||
|
||||
menuitem_t OPTIONS_DataErase[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CALL, "Erase Time Attack Data", "Be careful! What's deleted is gone forever!",
|
||||
NULL, {.routine = M_EraseData}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Erase Unlockable Data", "Be careful! What's deleted is gone forever!",
|
||||
NULL, {.routine = M_EraseData}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Erase Profile Data...", "Select a Profile to erase.",
|
||||
NULL, {.routine = M_CheckProfileData}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "\x85\x45rase all Data", "Be careful! What's deleted is gone forever!",
|
||||
NULL, {.routine = M_EraseData}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataEraseDef = {
|
||||
sizeof (OPTIONS_DataErase) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataDef,
|
||||
0,
|
||||
OPTIONS_DataErase,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void M_EraseDataResponse(INT32 ch)
|
||||
{
|
||||
if (ch == MA_NO)
|
||||
return;
|
||||
|
||||
S_StartSound(NULL, sfx_itrole); // bweh heh heh
|
||||
|
||||
// Delete the data
|
||||
if (optionsmenu.erasecontext == 2)
|
||||
{
|
||||
// SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets
|
||||
gamedata->totalplaytime = 0;
|
||||
gamedata->matchesplayed = 0;
|
||||
}
|
||||
if (optionsmenu.erasecontext != 1)
|
||||
G_ClearRecords();
|
||||
if (optionsmenu.erasecontext != 0)
|
||||
M_ClearSecrets();
|
||||
|
||||
F_StartIntro();
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
|
||||
void M_EraseData(INT32 choice)
|
||||
{
|
||||
const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n");
|
||||
|
||||
optionsmenu.erasecontext = (UINT8)choice;
|
||||
|
||||
if (choice == 0)
|
||||
eschoice = M_GetText("Time Attack data");
|
||||
else if (choice == 1)
|
||||
eschoice = M_GetText("Secrets data");
|
||||
else
|
||||
eschoice = M_GetText("ALL game data");
|
||||
|
||||
M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO);
|
||||
}
|
||||
107
src/menus/options-data-erase-profile.c
Normal file
107
src/menus/options-data-erase-profile.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/// \file menus/options-data-erase-profile.c
|
||||
/// \brief Erase Profile Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../f_finale.h"
|
||||
|
||||
menuitem_t OPTIONS_DataProfileErase[] =
|
||||
{
|
||||
{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_HandleProfileErase}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataProfileEraseDef = {
|
||||
sizeof (OPTIONS_DataProfileErase) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataEraseDef,
|
||||
0,
|
||||
OPTIONS_DataProfileErase,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawProfileErase,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
// Check if we have any profile loaded.
|
||||
void M_CheckProfileData(INT32 choice)
|
||||
{
|
||||
UINT8 np = PR_GetNumProfiles();
|
||||
(void) choice;
|
||||
|
||||
if (np < 2)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k7b);
|
||||
M_StartMessage("There are no custom profiles.\n\nPress (B)", NULL, MM_NOTHING);
|
||||
return;
|
||||
}
|
||||
|
||||
optionsmenu.eraseprofilen = 1;
|
||||
M_SetupNextMenu(&OPTIONS_DataProfileEraseDef, false);
|
||||
}
|
||||
|
||||
static void M_EraseProfileResponse(INT32 choice)
|
||||
{
|
||||
if (choice == MA_YES)
|
||||
{
|
||||
S_StartSound(NULL, sfx_itrole); // bweh heh heh
|
||||
|
||||
PR_DeleteProfile(optionsmenu.eraseprofilen);
|
||||
|
||||
// Did we bust our current profile..!?
|
||||
if (cv_currprofile.value == -1)
|
||||
{
|
||||
F_StartIntro();
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
else if (optionsmenu.eraseprofilen > PR_GetNumProfiles()-1)
|
||||
{
|
||||
optionsmenu.eraseprofilen--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void M_HandleProfileErase(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
const UINT8 np = PR_GetNumProfiles()-1;
|
||||
(void) choice;
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
optionsmenu.eraseprofilen++;
|
||||
|
||||
if (optionsmenu.eraseprofilen > np)
|
||||
optionsmenu.eraseprofilen = 1;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
|
||||
if (optionsmenu.eraseprofilen == 1)
|
||||
optionsmenu.eraseprofilen = np;
|
||||
else
|
||||
optionsmenu.eraseprofilen--;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
if (optionsmenu.eraseprofilen == cv_currprofile.value)
|
||||
M_StartMessage("Your ""\x85""current profile""\x80"" will be erased.\nAre you sure you want to proceed?\nDeleting this profile will also\nreturn you to the title screen.\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO);
|
||||
else
|
||||
M_StartMessage("This profile will be erased.\nAre you sure you want to proceed?\n\nPress (A) to confirm or (B) to cancel", FUNCPTRCAST(M_EraseProfileResponse), MM_YESNO);
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
28
src/menus/options-data-replays.c
Normal file
28
src/menus/options-data-replays.c
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/// \file menus/options-data-replays.c
|
||||
/// \brief Replay Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t OPTIONS_DataReplay[] =
|
||||
{
|
||||
{IT_STRING | IT_CVAR, "Record Replays", "Select when to save replays.",
|
||||
NULL, {.cvar = &cv_recordmultiplayerdemos}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Synch. Check Interval", "How often to check for synchronization while playing back a replay.",
|
||||
NULL, {.cvar = &cv_netdemosyncquality}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataReplayDef = {
|
||||
sizeof (OPTIONS_DataReplay) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataDef,
|
||||
0,
|
||||
OPTIONS_DataReplay,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
86
src/menus/options-data-screenshots.c
Normal file
86
src/menus/options-data-screenshots.c
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/// \file menus/options-data-screenshots.c
|
||||
/// \brief Screeshot Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_misc.h" // screenshot cvars
|
||||
|
||||
menuitem_t OPTIONS_DataScreenshot[] =
|
||||
{
|
||||
|
||||
{IT_HEADER, "SCREENSHOTS (F8)", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Storage Location", "Sets where to store screenshots.",
|
||||
NULL, {.cvar = &cv_screenshot_option}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save screenshots in.",
|
||||
NULL, {.cvar = &cv_screenshot_folder}, 24, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_HEADER, "GIF RECORDING (F9)", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Storage Location", "Sets where to store GIFs",
|
||||
NULL, {.cvar = &cv_movie_option}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Custom Folder", "Specify which folder to save GIFs in.",
|
||||
NULL, {.cvar = &cv_movie_folder}, 24, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_DataScreenshotDef = {
|
||||
sizeof (OPTIONS_DataScreenshot) / sizeof (menuitem_t),
|
||||
&OPTIONS_DataDef,
|
||||
0,
|
||||
OPTIONS_DataScreenshot,
|
||||
48, 80,
|
||||
SKINCOLOR_BLUEBERRY, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void Screenshot_option_Onchange(void)
|
||||
{
|
||||
// Screenshot opt is at #3, 0 based array obv.
|
||||
OPTIONS_DataScreenshot[2].status =
|
||||
(cv_screenshot_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
|
||||
|
||||
}
|
||||
|
||||
void Moviemode_mode_Onchange(void)
|
||||
{
|
||||
#if 0
|
||||
INT32 i, cstart, cend;
|
||||
for (i = op_screenshot_gif_start; i <= op_screenshot_apng_end; ++i)
|
||||
OP_ScreenshotOptionsMenu[i].status = IT_DISABLED;
|
||||
|
||||
switch (cv_moviemode.value)
|
||||
{
|
||||
case MM_GIF:
|
||||
cstart = op_screenshot_gif_start;
|
||||
cend = op_screenshot_gif_end;
|
||||
break;
|
||||
case MM_APNG:
|
||||
cstart = op_screenshot_apng_start;
|
||||
cend = op_screenshot_apng_end;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
for (i = cstart; i <= cend; ++i)
|
||||
OP_ScreenshotOptionsMenu[i].status = IT_STRING|IT_CVAR;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Moviemode_option_Onchange(void)
|
||||
{
|
||||
// opt 7 in a 0 based array, you get the idea...
|
||||
OPTIONS_DataScreenshot[6].status =
|
||||
(cv_movie_option.value == 3 ? IT_CVAR|IT_STRING|IT_CV_STRING : IT_DISABLED);
|
||||
}
|
||||
60
src/menus/options-gameplay-1.c
Normal file
60
src/menus/options-gameplay-1.c
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/// \file menus/options-gameplay-1.c
|
||||
/// \brief Gameplay Options -- see gopt_e
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t OPTIONS_Gameplay[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "Game Speed", "Change Game Speed for the next map.",
|
||||
NULL, {.cvar = &cv_kartspeed}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Base Lap Count", "Change how many laps must be completed per race.",
|
||||
NULL, {.cvar = &cv_numlaps}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!",
|
||||
NULL, {.cvar = &cv_kartfrantic}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Encore Mode", "Forces Encore Mode on for the next map.",
|
||||
NULL, {.cvar = &cv_kartencore}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Exit Countdown", "How long players have to finish after 1st place finishes.",
|
||||
NULL, {.cvar = &cv_countdowntime}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Time Limit", "Change the time limit for Battle rounds.",
|
||||
NULL, {.cvar = &cv_timelimit}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Starting Bumpers", "Change how many bumpers player start with in Battle.",
|
||||
NULL, {.cvar = &cv_kartbumpers}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Minimum Input Delay", "Practice for online play! Higher = more delay.",
|
||||
NULL, {.cvar = &cv_mindelay}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Random Item Toggles...", "Change which items to enable for your games.",
|
||||
NULL, {.submenu = &OPTIONS_GameplayItemsDef}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_GameplayDef = {
|
||||
sizeof (OPTIONS_Gameplay) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Gameplay,
|
||||
48, 80,
|
||||
SKINCOLOR_SCARLET, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
182
src/menus/options-gameplay-item-toggles.c
Normal file
182
src/menus/options-gameplay-item-toggles.c
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/// \file menus/options-gameplay-item-toggles.c
|
||||
/// \brief Random Item Toggles
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t OPTIONS_GameplayItems[] =
|
||||
{
|
||||
// Mostly handled by the drawing function.
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Super Rings", NULL, {.routine = M_HandleItemToggles}, KITEM_SUPERRING, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Self-Propelled Bombs", NULL, {.routine = M_HandleItemToggles}, KITEM_SPB, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Eggman Marks", NULL, {.routine = M_HandleItemToggles}, KITEM_EGGMAN, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Toggle All", NULL, {.routine = M_HandleItemToggles}, 0, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_SNEAKER, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALSNEAKER, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Sneakers x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLESNEAKER, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Rocket Sneakers", NULL, {.routine = M_HandleItemToggles}, KITEM_ROCKETSNEAKER, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas", NULL, {.routine = M_HandleItemToggles}, KITEM_BANANA, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Bananas x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEBANANA, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Proximity Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_MINE, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts", NULL, {.routine = M_HandleItemToggles}, KITEM_ORBINAUT, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x3", NULL, {.routine = M_HandleItemToggles}, KRITEM_TRIPLEORBINAUT, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Orbinauts x4", NULL, {.routine = M_HandleItemToggles}, KRITEM_QUADORBINAUT, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Land Mines", NULL, {.routine = M_HandleItemToggles}, KITEM_LANDMINE, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz", NULL, {.routine = M_HandleItemToggles}, KITEM_JAWZ, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Jawz x2", NULL, {.routine = M_HandleItemToggles}, KRITEM_DUALJAWZ, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Ballhogs", NULL, {.routine = M_HandleItemToggles}, KITEM_BALLHOG, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Drop Targets", NULL, {.routine = M_HandleItemToggles}, KITEM_DROPTARGET, sfx_s258},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Lightning Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_LIGHTNINGSHIELD, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Bubble Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_BUBBLESHIELD, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Flame Shields", NULL, {.routine = M_HandleItemToggles}, KITEM_FLAMESHIELD, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Hyudoros", NULL, {.routine = M_HandleItemToggles}, KITEM_HYUDORO, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Invinciblity", NULL, {.routine = M_HandleItemToggles}, KITEM_INVINCIBILITY, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Grow", NULL, {.routine = M_HandleItemToggles}, KITEM_GROW, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Shrink", NULL, {.routine = M_HandleItemToggles}, KITEM_SHRINK, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0},
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Pogo Springs", NULL, {.routine = M_HandleItemToggles}, KITEM_POGOSPRING, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Kitchen Sinks", NULL, {.routine = M_HandleItemToggles}, KITEM_KITCHENSINK, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0},
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, NULL, NULL, {.routine = M_HandleItemToggles}, 255, 0}
|
||||
};
|
||||
|
||||
menu_t OPTIONS_GameplayItemsDef = {
|
||||
sizeof (OPTIONS_GameplayItems) / sizeof (menuitem_t),
|
||||
&OPTIONS_GameplayDef,
|
||||
0,
|
||||
OPTIONS_GameplayItems,
|
||||
14, 40,
|
||||
SKINCOLOR_SCARLET, 0,
|
||||
2, 5,
|
||||
M_DrawItemToggles,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void M_HandleItemToggles(INT32 choice)
|
||||
{
|
||||
const INT32 width = 8, height = 4;
|
||||
INT32 column = itemOn/height, row = itemOn%height;
|
||||
INT16 next;
|
||||
UINT8 i;
|
||||
boolean exitmenu = false;
|
||||
const UINT8 pid = 0;
|
||||
|
||||
(void) choice;
|
||||
|
||||
if (menucmd[pid].dpad_lr > 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
column++;
|
||||
if (((column*height)+row) >= currentMenu->numitems)
|
||||
column = 0;
|
||||
next = min(((column*height)+row), currentMenu->numitems-1);
|
||||
itemOn = next;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
column--;
|
||||
if (column < 0)
|
||||
column = width-1;
|
||||
if (((column*height)+row) >= currentMenu->numitems)
|
||||
column--;
|
||||
next = max(((column*height)+row), 0);
|
||||
if (next >= currentMenu->numitems)
|
||||
next = currentMenu->numitems-1;
|
||||
itemOn = next;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
row = (row+1) % height;
|
||||
if (((column*height)+row) >= currentMenu->numitems)
|
||||
row = 0;
|
||||
next = min(((column*height)+row), currentMenu->numitems-1);
|
||||
itemOn = next;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
row = (row-1) % height;
|
||||
if (row < 0)
|
||||
row = height-1;
|
||||
if (((column*height)+row) >= currentMenu->numitems)
|
||||
row--;
|
||||
next = max(((column*height)+row), 0);
|
||||
if (next >= currentMenu->numitems)
|
||||
next = currentMenu->numitems-1;
|
||||
itemOn = next;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
if (currentMenu->menuitems[itemOn].mvar1 == 255)
|
||||
{
|
||||
//S_StartSound(NULL, sfx_s26d);
|
||||
if (!shitsfree)
|
||||
{
|
||||
shitsfree = TICRATE;
|
||||
S_StartSound(NULL, sfx_itfree);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (currentMenu->menuitems[itemOn].mvar1 == 0)
|
||||
{
|
||||
INT32 v = cv_items[0].value;
|
||||
S_StartSound(NULL, sfx_s1b4);
|
||||
for (i = 0; i < NUMKARTRESULTS-1; i++)
|
||||
{
|
||||
if (cv_items[i].value == v)
|
||||
CV_AddValue(&cv_items[i], 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentMenu->menuitems[itemOn].mvar2)
|
||||
{
|
||||
S_StartSound(NULL, currentMenu->menuitems[itemOn].mvar2);
|
||||
}
|
||||
else
|
||||
{
|
||||
S_StartSound(NULL, sfx_s1ba);
|
||||
}
|
||||
CV_AddValue(&cv_items[currentMenu->menuitems[itemOn].mvar1-1], 1);
|
||||
}
|
||||
}
|
||||
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
exitmenu = true;
|
||||
}
|
||||
|
||||
if (exitmenu)
|
||||
{
|
||||
if (currentMenu->prevMenu)
|
||||
M_SetupNextMenu(currentMenu->prevMenu, false);
|
||||
else
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
}
|
||||
57
src/menus/options-hud-1.c
Normal file
57
src/menus/options-hud-1.c
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/// \file menus/options-hud-1.c
|
||||
/// \brief HUD Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../r_main.h" // cv_showhud
|
||||
#include "../v_video.h" // cv_constextsize
|
||||
#include "../console.h" // console cvars
|
||||
|
||||
menuitem_t OPTIONS_HUD[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "Show HUD (F3)", "Toggles HUD display. Great for taking screenshots!",
|
||||
NULL, {.cvar = &cv_showhud}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "HUD Opacity", "Non opaque values may have performance impacts in software mode.",
|
||||
NULL, {.cvar = &cv_translucenthud}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Speedometer", "Choose to what speed unit to display or toggle off the speedometer.",
|
||||
NULL, {.cvar = &cv_kartspeedometer}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Console Text Size", "Size of the text within the console.",
|
||||
NULL, {.cvar = &cv_constextsize}, 0, 0},
|
||||
|
||||
// we spell words properly here.
|
||||
{IT_STRING | IT_CVAR, "Console Tint", "Change the background colour of the console.",
|
||||
NULL, {.cvar = &cons_backcolor}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Show \"FOCUS LOST\"", "Displays \"FOCUS LOST\" when the game window isn't the active window.",
|
||||
NULL, {.cvar = &cv_showfocuslost}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Online HUD Options...", "HUD options related to the online chat box and other features.",
|
||||
NULL, {.submenu = &OPTIONS_HUDOnlineDef}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_HUDDef = {
|
||||
sizeof (OPTIONS_HUD) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_HUD,
|
||||
48, 80,
|
||||
SKINCOLOR_SUNSLAM, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
54
src/menus/options-hud-online.c
Normal file
54
src/menus/options-hud-online.c
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/// \file menus/options-hud-inline.c
|
||||
/// \brief Online HUD Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t OPTIONS_HUDOnline[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "Chat Mode", "Choose whether to display chat in its own window or the console.",
|
||||
NULL, {.cvar = &cv_consolechat}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Chat Box Tint", "Changes the background colour of the chat box.",
|
||||
NULL, {.cvar = &cv_chatbacktint}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Width", "Change the width of the Chat Box",
|
||||
NULL, {.cvar = &cv_chatwidth}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "Chat Box Height", "Change the height of the Chat Box",
|
||||
NULL, {.cvar = &cv_chatheight}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Message Fadeout Time", "How long chat messages stay displayed with the chat closed.",
|
||||
NULL, {.cvar = &cv_chattime}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Spam Protection", "Prevents too many message from a single player from being displayed.",
|
||||
NULL, {.cvar = &cv_chatspamprotection}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Local Ping Display", "In netgames, displays your ping at the lower right corner of the screen.",
|
||||
NULL, {.cvar = &cv_showping}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_HUDOnlineDef = {
|
||||
sizeof (OPTIONS_HUDOnline) / sizeof (menuitem_t),
|
||||
&OPTIONS_HUDDef,
|
||||
0,
|
||||
OPTIONS_HUDOnline,
|
||||
48, 80,
|
||||
SKINCOLOR_SUNSLAM, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
218
src/menus/options-profiles-1.c
Normal file
218
src/menus/options-profiles-1.c
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/// \file menus/options-profiles-1.c
|
||||
/// \brief Profiles Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
// profile select
|
||||
menuitem_t OPTIONS_Profiles[] = {
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Select a Profile.",
|
||||
NULL, {.routine = M_HandleProfileSelect}, 0, 0}, // dummy menuitem for the control func
|
||||
};
|
||||
|
||||
menu_t OPTIONS_ProfilesDef = {
|
||||
sizeof (OPTIONS_Profiles) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Profiles,
|
||||
32, 80,
|
||||
SKINCOLOR_ULTRAMARINE, 0,
|
||||
2, 5,
|
||||
M_DrawProfileSelect,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
consvar_t cv_dummyprofilename = CVAR_INIT ("dummyprofilename", "", CV_HIDDEN, NULL, NULL);
|
||||
consvar_t cv_dummyprofileplayername = CVAR_INIT ("dummyprofileplayername", "", CV_HIDDEN, NULL, NULL);
|
||||
consvar_t cv_dummyprofilekickstart = CVAR_INIT ("dummyprofilekickstart", "Off", CV_HIDDEN, CV_OnOff, NULL);
|
||||
|
||||
void M_ProfileSelectInit(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
optionsmenu.profilemenu = true;
|
||||
|
||||
M_SetupNextMenu(&OPTIONS_ProfilesDef, false);
|
||||
}
|
||||
|
||||
// Select the current profile for menu use and go to maindef.
|
||||
void M_FirstPickProfile(INT32 c)
|
||||
{
|
||||
if (c == MA_YES)
|
||||
{
|
||||
M_ResetOptions(); // Reset all options variables otherwise things are gonna go reaaal bad lol.
|
||||
optionsmenu.profile = NULL; // Make sure to get rid of that, too.
|
||||
|
||||
PR_ApplyProfile(optionsmenu.profilen, 0);
|
||||
M_SetupNextMenu(M_InterruptMenuWithChallenges(&MainDef), false);
|
||||
|
||||
// Tell the game this is the last profile we picked.
|
||||
CV_StealthSetValue(&cv_ttlprofilen, optionsmenu.profilen);
|
||||
|
||||
// Save em!
|
||||
PR_SaveProfiles();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Start menu edition. Call this with MA_YES if not used with a textbox.
|
||||
static void M_StartEditProfile(INT32 c)
|
||||
{
|
||||
|
||||
const INT32 maxp = PR_GetNumProfiles();
|
||||
|
||||
if (c == MA_YES)
|
||||
{
|
||||
if (optionsmenu.profilen == maxp)
|
||||
PR_InitNewProfile(); // initialize the new profile.
|
||||
|
||||
optionsmenu.profile = PR_GetProfile(optionsmenu.profilen);
|
||||
// copy this profile's controls into optionsmenu so that we can edit controls without changing them directly.
|
||||
// we do this so that we don't edit a profile's controls in real-time and end up doing really weird shit.
|
||||
memcpy(&optionsmenu.tempcontrols, optionsmenu.profile->controls, sizeof(gamecontroldefault));
|
||||
|
||||
// This is now used to move the card we've selected.
|
||||
optionsmenu.optx = 160;
|
||||
optionsmenu.opty = 35;
|
||||
optionsmenu.toptx = 130/2;
|
||||
optionsmenu.topty = 0;
|
||||
|
||||
// setup cvars
|
||||
if (optionsmenu.profile->version)
|
||||
{
|
||||
CV_StealthSet(&cv_dummyprofilename, optionsmenu.profile->profilename);
|
||||
CV_StealthSet(&cv_dummyprofileplayername, optionsmenu.profile->playername);
|
||||
CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel);
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_StealthSet(&cv_dummyprofilename, "");
|
||||
CV_StealthSet(&cv_dummyprofileplayername, "");
|
||||
CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off
|
||||
}
|
||||
|
||||
// Setup greyout and stuff.
|
||||
OPTIONS_EditProfile[popt_profilename].status = IT_STRING | IT_CVAR | IT_CV_STRING;
|
||||
OPTIONS_EditProfile[popt_profilepname].status = IT_STRING | IT_CVAR | IT_CV_STRING;
|
||||
OPTIONS_EditProfile[popt_char].status = IT_STRING | IT_CALL;
|
||||
|
||||
if (gamestate != GS_MENU) // If we're modifying things mid game, transtext some of those!
|
||||
{
|
||||
OPTIONS_EditProfile[popt_profilename].status |= IT_TRANSTEXT;
|
||||
OPTIONS_EditProfile[popt_profilepname].status |= IT_TRANSTEXT;
|
||||
OPTIONS_EditProfile[popt_char].status |= IT_TRANSTEXT;
|
||||
}
|
||||
|
||||
M_SetupNextMenu(&OPTIONS_EditProfileDef, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void M_HandleProfileSelect(INT32 ch)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
INT32 maxp = PR_GetNumProfiles();
|
||||
boolean creatable = (maxp < MAXPROFILES);
|
||||
(void) ch;
|
||||
|
||||
if (!creatable)
|
||||
{
|
||||
maxp = MAXPROFILES;
|
||||
}
|
||||
|
||||
if (menucmd[pid].dpad_lr > 0)
|
||||
{
|
||||
optionsmenu.profilen++;
|
||||
optionsmenu.offset += (128 + 128/8);
|
||||
|
||||
if (optionsmenu.profilen > maxp)
|
||||
{
|
||||
optionsmenu.profilen = 0;
|
||||
optionsmenu.offset -= (128 + 128/8)*(maxp+1);
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
}
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
optionsmenu.profilen--;
|
||||
optionsmenu.offset -= (128 + 128/8);
|
||||
|
||||
if (optionsmenu.profilen < 0)
|
||||
{
|
||||
optionsmenu.profilen = maxp;
|
||||
optionsmenu.offset += (128 + 128/8)*(maxp+1);
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
|
||||
// Boot profile setup has already been done.
|
||||
if (cv_currprofile.value > -1)
|
||||
{
|
||||
|
||||
if (optionsmenu.profilen == 0) // Guest profile, you can't edit that one!
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k7b);
|
||||
M_StartMessage(M_GetText("The Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING);
|
||||
M_SetMenuDelay(pid);
|
||||
return;
|
||||
}
|
||||
else if (creatable && optionsmenu.profilen == maxp && gamestate != GS_MENU)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k7b);
|
||||
M_StartMessage(M_GetText("Cannot create a new profile\nmid-game. Return to the\ntitle screen first."), NULL, MM_NOTHING);
|
||||
M_SetMenuDelay(pid);
|
||||
return;
|
||||
}
|
||||
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_StartEditProfile(MA_YES);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're on the profile selection screen.
|
||||
if (creatable && optionsmenu.profilen == maxp)
|
||||
{
|
||||
M_StartEditProfile(MA_YES);
|
||||
M_SetMenuDelay(pid);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if 0
|
||||
if (optionsmenu.profilen == 0)
|
||||
{
|
||||
M_StartMessage(M_GetText("Are you sure you wish\nto use the Guest Profile?\nThis profile cannot be customised.\nIt is recommended to create\na new Profile instead.\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
M_FirstPickProfile(MA_YES);
|
||||
M_SetMenuDelay(pid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
optionsmenu.resetprofilemenu = true;
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
if (menutransition.tics == 0 && optionsmenu.resetprofile)
|
||||
{
|
||||
optionsmenu.profile = NULL; // Make sure to reset that when transitions are done.'
|
||||
optionsmenu.resetprofile = false;
|
||||
}
|
||||
}
|
||||
180
src/menus/options-profiles-edit-1.c
Normal file
180
src/menus/options-profiles-edit-1.c
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/// \file menus/options-profiles-edit-1.c
|
||||
/// \brief Profile Editor
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t OPTIONS_EditProfile[] = {
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Profile Name", "6-character long name to identify this Profile.",
|
||||
NULL, {.cvar = &cv_dummyprofilename}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Player Name", "Name displayed online when using this Profile.",
|
||||
NULL, {.cvar = &cv_dummyprofileplayername}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Character", "Default character and color for this Profile.",
|
||||
NULL, {.routine = M_CharacterSelect}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Controls", "Select the button mappings for this Profile.",
|
||||
NULL, {.routine = M_ProfileDeviceSelect}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Confirm", "Confirm changes.",
|
||||
NULL, {.routine = M_ConfirmProfile}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_EditProfileDef = {
|
||||
sizeof (OPTIONS_EditProfile) / sizeof (menuitem_t),
|
||||
&OPTIONS_ProfilesDef,
|
||||
0,
|
||||
OPTIONS_EditProfile,
|
||||
32, 80,
|
||||
SKINCOLOR_ULTRAMARINE, 0,
|
||||
2, 5,
|
||||
M_DrawEditProfile,
|
||||
M_HandleProfileEdit,
|
||||
NULL,
|
||||
NULL,
|
||||
M_ProfileEditInputs,
|
||||
};
|
||||
|
||||
// Returns true if the profile can be saved, false otherwise. Also starts messages if necessary.
|
||||
static boolean M_ProfileEditEnd(const UINT8 pid)
|
||||
{
|
||||
UINT8 i;
|
||||
|
||||
// Guest profile, you can't edit that one!
|
||||
if (optionsmenu.profilen == 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k7b);
|
||||
M_StartMessage(M_GetText("Guest profile cannot be edited.\nCreate a new profile instead."), NULL, MM_NOTHING);
|
||||
M_SetMenuDelay(pid);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if some profiles have the same name
|
||||
for (i = 0; i < PR_GetNumProfiles(); i++)
|
||||
{
|
||||
profile_t *check = PR_GetProfile(i);
|
||||
|
||||
// For obvious reasons don't check if our name is the same as our name....
|
||||
if (check != optionsmenu.profile)
|
||||
{
|
||||
if (!(strcmp(optionsmenu.profile->profilename, check->profilename)))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k7b);
|
||||
M_StartMessage(M_GetText("Another profile uses the same name.\nThis must be changed to be able to save."), NULL, MM_NOTHING);
|
||||
M_SetMenuDelay(pid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void M_ProfileEditExit(void)
|
||||
{
|
||||
optionsmenu.toptx = 160;
|
||||
optionsmenu.topty = 35;
|
||||
optionsmenu.resetprofile = true; // Reset profile after the transition is done.
|
||||
|
||||
PR_SaveProfiles(); // save profiles after we do that.
|
||||
}
|
||||
|
||||
// For profile edit, just make sure going back resets the card to its position, the rest is taken care of automatically.
|
||||
boolean M_ProfileEditInputs(INT32 ch)
|
||||
{
|
||||
|
||||
(void) ch;
|
||||
const UINT8 pid = 0;
|
||||
|
||||
if (M_MenuBackPressed(pid))
|
||||
{
|
||||
if (M_ProfileEditEnd(pid))
|
||||
{
|
||||
M_ProfileEditExit();
|
||||
if (cv_currprofile.value == -1)
|
||||
M_SetupNextMenu(&MAIN_ProfilesDef, false);
|
||||
else
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
if (currentMenu->menuitems[itemOn].status & IT_TRANSTEXT)
|
||||
return true; // No.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle some actions in profile editing
|
||||
void M_HandleProfileEdit(void)
|
||||
{
|
||||
// Always copy the profile name and player name in the profile.
|
||||
if (optionsmenu.profile)
|
||||
{
|
||||
// Copy the first 6 chars for profile name
|
||||
if (strlen(cv_dummyprofilename.string))
|
||||
{
|
||||
char *s;
|
||||
// convert dummyprofilename to uppercase
|
||||
strncpy(optionsmenu.profile->profilename, cv_dummyprofilename.string, PROFILENAMELEN);
|
||||
s = optionsmenu.profile->profilename;
|
||||
while (*s)
|
||||
{
|
||||
*s = toupper(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(cv_dummyprofileplayername.string))
|
||||
strncpy(optionsmenu.profile->playername, cv_dummyprofileplayername.string, MAXPLAYERNAME);
|
||||
}
|
||||
|
||||
M_OptionsTick(); // Has to be afterwards because this can unset optionsmenu.profile
|
||||
}
|
||||
|
||||
// Confirm Profile edi via button.
|
||||
void M_ConfirmProfile(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
(void) choice;
|
||||
|
||||
if (M_ProfileEditEnd(pid))
|
||||
{
|
||||
if (cv_currprofile.value > -1)
|
||||
{
|
||||
M_ProfileEditExit();
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else
|
||||
{
|
||||
M_StartMessage(M_GetText("Are you sure you wish to\nselect this profile?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_FirstPickProfile), MM_YESNO);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prompt a device selection window (just tap any button on the device you want)
|
||||
void M_ProfileDeviceSelect(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
// While we're here, setup the incoming controls menu to reset the scroll & bind status:
|
||||
optionsmenu.controlscroll = 0;
|
||||
optionsmenu.bindcontrol = 0;
|
||||
optionsmenu.bindtimer = 0;
|
||||
|
||||
optionsmenu.lastkey = 0;
|
||||
optionsmenu.keyheldfor = 0;
|
||||
|
||||
optionsmenu.contx = optionsmenu.tcontx = controlleroffsets[gc_a][0];
|
||||
optionsmenu.conty = optionsmenu.tconty = controlleroffsets[gc_a][1];
|
||||
|
||||
M_SetupNextMenu(&OPTIONS_ProfileControlsDef, false); // Don't set device here anymore.
|
||||
}
|
||||
448
src/menus/options-profiles-edit-controls.c
Normal file
448
src/menus/options-profiles-edit-controls.c
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
/// \file menus/options-profiles-edit-controls.c
|
||||
/// \brief Profile Controls Editor
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../i_joy.h" // for joystick menu controls
|
||||
|
||||
menuitem_t OPTIONS_ProfileControls[] = {
|
||||
|
||||
{IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_CONTROL, "A", "Accelerate / Confirm",
|
||||
"PR_BTA", {.routine = M_ProfileSetControl}, gc_a, 0},
|
||||
|
||||
{IT_CONTROL, "B", "Look backwards / Back",
|
||||
"PR_BTB", {.routine = M_ProfileSetControl}, gc_b, 0},
|
||||
|
||||
{IT_CONTROL, "C", "Spindash / Extra",
|
||||
"PR_BTC", {.routine = M_ProfileSetControl}, gc_c, 0},
|
||||
|
||||
{IT_CONTROL, "X", "Brake / Back",
|
||||
"PR_BTX", {.routine = M_ProfileSetControl}, gc_x, 0},
|
||||
|
||||
// @TODO What does this do???
|
||||
{IT_CONTROL, "Y", "N/A ?",
|
||||
"PR_BTY", {.routine = M_ProfileSetControl}, gc_y, 0},
|
||||
|
||||
{IT_CONTROL, "Z", "N/A ?",
|
||||
"PR_BTZ", {.routine = M_ProfileSetControl}, gc_z, 0},
|
||||
|
||||
{IT_CONTROL, "L", "Use item",
|
||||
"PR_BTL", {.routine = M_ProfileSetControl}, gc_l, 0},
|
||||
|
||||
{IT_CONTROL, "R", "Drift",
|
||||
"PR_BTR", {.routine = M_ProfileSetControl}, gc_r, 0},
|
||||
|
||||
{IT_CONTROL, "Turn Left", "Turn left",
|
||||
"PR_PADL", {.routine = M_ProfileSetControl}, gc_left, 0},
|
||||
|
||||
{IT_CONTROL, "Turn Right", "Turn right",
|
||||
"PR_PADR", {.routine = M_ProfileSetControl}, gc_right, 0},
|
||||
|
||||
{IT_CONTROL, "Aim Forward", "Aim forwards",
|
||||
"PR_PADU", {.routine = M_ProfileSetControl}, gc_up, 0},
|
||||
|
||||
{IT_CONTROL, "Aim Backwards", "Aim backwards",
|
||||
"PR_PADD", {.routine = M_ProfileSetControl}, gc_down, 0},
|
||||
|
||||
{IT_CONTROL, "Start", "Open pause menu",
|
||||
"PR_BTS", {.routine = M_ProfileSetControl}, gc_start, 0},
|
||||
|
||||
{IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_CONTROL, "SCREENSHOT", "Also usable with F8 on Keyboard.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_screenshot, 0},
|
||||
|
||||
{IT_CONTROL, "GIF CAPTURE", "Also usable with F9 on Keyboard.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_recordgif, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN CHAT", "Opens chatbox in online games.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_talk, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN TEAM CHAT", "Do we even have team gamemodes?",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0},
|
||||
|
||||
{IT_CONTROL, "SHOW RANKINGS", "Show mid-game rankings.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_rankings, 0},
|
||||
|
||||
{IT_CONTROL, "OPEN CONSOLE", "Opens the developer options console.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_console, 0},
|
||||
|
||||
{IT_CONTROL, "LUA/A", "May be used by add-ons.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_luaa, 0},
|
||||
|
||||
{IT_CONTROL, "LUA/B", "May be used by add-ons.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_luab, 0},
|
||||
|
||||
{IT_CONTROL, "LUA/C", "May be used by add-ons.",
|
||||
NULL, {.routine = M_ProfileSetControl}, gc_luac, 0},
|
||||
|
||||
{IT_HEADER, "TOGGLES", "For per-player commands",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_CONTROL | IT_CVAR, "KICKSTART ACCEL", "Hold A to auto-accel. Tap it to cancel.",
|
||||
NULL, {.cvar = &cv_dummyprofilekickstart}, 0, 0},
|
||||
|
||||
{IT_HEADER, "EXTRA", "",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "TRY MAPPINGS", "Test your controls.",
|
||||
NULL, {.routine = M_ProfileTryController}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "CONFIRM", "Go back to profile setup.",
|
||||
NULL, {.routine = M_ProfileControlsConfirm}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_ProfileControlsDef = {
|
||||
sizeof (OPTIONS_ProfileControls) / sizeof (menuitem_t),
|
||||
&OPTIONS_EditProfileDef,
|
||||
0,
|
||||
OPTIONS_ProfileControls,
|
||||
32, 80,
|
||||
SKINCOLOR_ULTRAMARINE, 0,
|
||||
3, 5,
|
||||
M_DrawProfileControls,
|
||||
M_HandleProfileControls,
|
||||
NULL,
|
||||
NULL,
|
||||
M_ProfileControlsInputs,
|
||||
};
|
||||
|
||||
// sets whatever device has had its key pressed to the active device.
|
||||
// 20/05/22: Commented out for now but not deleted as it might still find some use in the future?
|
||||
/*
|
||||
static void SetDeviceOnPress(void)
|
||||
{
|
||||
UINT8 i;
|
||||
|
||||
for (i=0; i < MAXDEVICES; i++)
|
||||
{
|
||||
if (deviceResponding[i])
|
||||
{
|
||||
CV_SetValue(&cv_usejoystick[0], i); // Force-set this joystick as the current joystick we're using for P1 (which is the only one controlling menus)
|
||||
CONS_Printf("SetDeviceOnPress: Device for %d set to %d\n", 0, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void M_HandleProfileControls(void)
|
||||
{
|
||||
UINT8 maxscroll = currentMenu->numitems - 5;
|
||||
M_OptionsTick();
|
||||
|
||||
optionsmenu.contx += (optionsmenu.tcontx - optionsmenu.contx)/2;
|
||||
optionsmenu.conty += (optionsmenu.tconty - optionsmenu.conty)/2;
|
||||
|
||||
if (abs(optionsmenu.contx - optionsmenu.tcontx) < 2 && abs(optionsmenu.conty - optionsmenu.tconty) < 2)
|
||||
{
|
||||
optionsmenu.contx = optionsmenu.tcontx;
|
||||
optionsmenu.conty = optionsmenu.tconty; // Avoid awkward 1 px errors.
|
||||
}
|
||||
|
||||
optionsmenu.controlscroll = itemOn - 3; // very barebones scrolling, but it works just fine for our purpose.
|
||||
if (optionsmenu.controlscroll > maxscroll)
|
||||
optionsmenu.controlscroll = maxscroll;
|
||||
|
||||
if (optionsmenu.controlscroll < 0)
|
||||
optionsmenu.controlscroll = 0;
|
||||
|
||||
// bindings, cancel if timer is depleted.
|
||||
if (optionsmenu.bindcontrol)
|
||||
{
|
||||
optionsmenu.bindtimer--;
|
||||
if (!optionsmenu.bindtimer)
|
||||
{
|
||||
optionsmenu.bindcontrol = 0; // we've gone past the max, just stop.
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void M_ProfileTryController(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
optionsmenu.trycontroller = TICRATE*5;
|
||||
|
||||
// Apply these controls right now on P1's end.
|
||||
memcpy(&gamecontrol[0], optionsmenu.tempcontrols, sizeof(gamecontroldefault));
|
||||
}
|
||||
|
||||
static void M_ProfileControlSaveResponse(INT32 choice)
|
||||
{
|
||||
if (choice == MA_YES)
|
||||
{
|
||||
SINT8 belongsto = PR_ProfileUsedBy(optionsmenu.profile);
|
||||
// Save the profile
|
||||
optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value;
|
||||
memcpy(&optionsmenu.profile->controls, optionsmenu.tempcontrols, sizeof(gamecontroldefault));
|
||||
|
||||
// If this profile is in-use by anyone, apply the changes immediately upon exiting.
|
||||
// Don't apply the profile itself as that would lead to issues mid-game.
|
||||
if (belongsto > -1 && belongsto < MAXSPLITSCREENPLAYERS)
|
||||
{
|
||||
memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault));
|
||||
CV_StealthSetValue(&cv_kickstartaccel[belongsto], cv_dummyprofilekickstart.value);
|
||||
}
|
||||
|
||||
M_GoBack(0);
|
||||
}
|
||||
}
|
||||
|
||||
void M_ProfileControlsConfirm(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
//M_StartMessage(M_GetText("Exiting will save the control changes\nfor this Profile.\nIs this okay?\n\nPress (A) to confirm or (B) to cancel"), FUNCPTRCAST(M_ProfileControlSaveResponse), MM_YESNO);
|
||||
// TODO: Add a graphic for controls saving, instead of obnoxious prompt.
|
||||
|
||||
M_ProfileControlSaveResponse(MA_YES);
|
||||
|
||||
optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; // Make sure to save kickstart accel.
|
||||
|
||||
// Reapply player 1's real profile.
|
||||
if (cv_currprofile.value > -1)
|
||||
{
|
||||
PR_ApplyProfile(cv_lastprofile[0].value, 0);
|
||||
}
|
||||
}
|
||||
|
||||
boolean M_ProfileControlsInputs(INT32 ch)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
(void)ch;
|
||||
|
||||
// By default, accept all inputs.
|
||||
if (optionsmenu.trycontroller)
|
||||
{
|
||||
if (menucmd[pid].dpad_ud || menucmd[pid].dpad_lr || menucmd[pid].buttons)
|
||||
{
|
||||
optionsmenu.trycontroller = 5*TICRATE;
|
||||
}
|
||||
else
|
||||
{
|
||||
optionsmenu.trycontroller--;
|
||||
}
|
||||
|
||||
if (optionsmenu.trycontroller == 0)
|
||||
{
|
||||
// Reset controls to that of the current profile.
|
||||
profile_t *cpr = PR_GetProfile(cv_currprofile.value);
|
||||
if (cpr == NULL)
|
||||
cpr = PR_GetProfile(0); // Creating a profile at boot, revert to guest profile
|
||||
memcpy(&gamecontrol[0], cpr->controls, sizeof(gamecontroldefault));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (optionsmenu.bindcontrol)
|
||||
return true; // Eat all inputs there. We'll use a stupid hack in M_Responder instead.
|
||||
|
||||
//SetDeviceOnPress(); // Update device constantly so that we don't stay stuck with otpions saying a device is unavailable just because we're mapping multiple devices...
|
||||
|
||||
if (M_MenuExtraPressed(pid))
|
||||
{
|
||||
// check if we're on a valid menu option...
|
||||
if (currentMenu->menuitems[itemOn].mvar1)
|
||||
{
|
||||
// clear controls for that key
|
||||
INT32 i;
|
||||
|
||||
for (i = 0; i < MAXINPUTMAPPING; i++)
|
||||
optionsmenu.tempcontrols[currentMenu->menuitems[itemOn].mvar1][i] = KEY_NULL;
|
||||
|
||||
S_StartSound(NULL, sfx_s3k66);
|
||||
}
|
||||
M_SetMenuDelay(pid);
|
||||
return true;
|
||||
}
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_ProfileControlsConfirm(0);
|
||||
M_SetMenuDelay(pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void M_ProfileSetControl(INT32 ch)
|
||||
{
|
||||
INT32 controln = currentMenu->menuitems[itemOn].mvar1;
|
||||
UINT8 i;
|
||||
(void) ch;
|
||||
|
||||
optionsmenu.bindcontrol = 1; // Default to control #1
|
||||
|
||||
for (i = 0; i < MAXINPUTMAPPING; i++)
|
||||
{
|
||||
if (optionsmenu.tempcontrols[controln][i] == KEY_NULL)
|
||||
{
|
||||
optionsmenu.bindcontrol = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we could find a null key to map into, map there.
|
||||
// Otherwise, this will stay at 1 which means we'll overwrite the first bound control.
|
||||
|
||||
optionsmenu.bindtimer = TICRATE*5;
|
||||
}
|
||||
|
||||
// Map the event to the profile.
|
||||
|
||||
#define KEYHOLDFOR 1
|
||||
void M_MapProfileControl(event_t *ev)
|
||||
{
|
||||
INT32 c = 0;
|
||||
UINT8 n = optionsmenu.bindcontrol-1; // # of input to bind
|
||||
INT32 controln = currentMenu->menuitems[itemOn].mvar1; // gc_
|
||||
UINT8 where = n; // By default, we'll save the bind where we're supposed to map.
|
||||
INT32 i;
|
||||
|
||||
//SetDeviceOnPress(); // Update cv_usejoystick
|
||||
|
||||
// Only consider keydown and joystick events to make sure we ignore ev_mouse and other events
|
||||
// See also G_MapEventsToControls
|
||||
switch (ev->type)
|
||||
{
|
||||
case ev_keydown:
|
||||
if (ev->data1 < NUMINPUTS)
|
||||
{
|
||||
c = ev->data1;
|
||||
}
|
||||
#ifdef PARANOIA
|
||||
else
|
||||
{
|
||||
CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case ev_joystick:
|
||||
if (ev->data1 >= JOYAXES)
|
||||
{
|
||||
#ifdef PARANOIA
|
||||
CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
INT32 deadzone = deadzone = (JOYAXISRANGE * cv_deadzone[0].value) / FRACUNIT; // TODO how properly account for different deadzone cvars for different devices
|
||||
boolean responsivelr = ((ev->data2 != INT32_MAX) && (abs(ev->data2) >= deadzone));
|
||||
boolean responsiveud = ((ev->data3 != INT32_MAX) && (abs(ev->data3) >= deadzone));
|
||||
|
||||
i = ev->data1;
|
||||
|
||||
if (i >= JOYANALOGS)
|
||||
{
|
||||
// The trigger axes are handled specially.
|
||||
i -= JOYANALOGS;
|
||||
|
||||
if (responsivelr)
|
||||
{
|
||||
c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2);
|
||||
}
|
||||
else if (responsiveud)
|
||||
{
|
||||
c = KEY_AXIS1 + (JOYANALOGS * 4) + (i * 2) + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actual analog sticks
|
||||
|
||||
// Only consider unambiguous assignment.
|
||||
if (responsivelr == responsiveud)
|
||||
return;
|
||||
|
||||
if (responsivelr)
|
||||
{
|
||||
if (ev->data2 < 0)
|
||||
{
|
||||
// Left
|
||||
c = KEY_AXIS1 + (i * 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Right
|
||||
c = KEY_AXIS1 + (i * 4) + 1;
|
||||
}
|
||||
}
|
||||
else //if (responsiveud)
|
||||
{
|
||||
if (ev->data3 < 0)
|
||||
{
|
||||
// Up
|
||||
c = KEY_AXIS1 + (i * 4) + 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Down
|
||||
c = KEY_AXIS1 + (i * 4) + 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// safety result
|
||||
if (!c)
|
||||
return;
|
||||
|
||||
// Set menu delay regardless of what we're doing to avoid stupid stuff.
|
||||
M_SetMenuDelay(0);
|
||||
|
||||
// Check if this particular key (c) is already bound in any slot.
|
||||
// If that's the case, simply do nothing.
|
||||
for (i = 0; i < MAXINPUTMAPPING; i++)
|
||||
{
|
||||
if (optionsmenu.tempcontrols[controln][i] == c)
|
||||
{
|
||||
optionsmenu.bindcontrol = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// With the way we do things, there cannot be instances of 'gaps' within the controls, so we don't need to pretend like we need to handle that.
|
||||
// Unless of course you tamper with the cfg file, but then it's *your* fault, not mine.
|
||||
|
||||
optionsmenu.tempcontrols[controln][where] = c;
|
||||
optionsmenu.bindcontrol = 0; // not binding anymore
|
||||
|
||||
// If possible, reapply the profile...
|
||||
// 19/05/22: Actually, no, don't do that, it just fucks everything up in too many cases.
|
||||
|
||||
/*
|
||||
if (gamestate == GS_MENU) // In menu? Apply this to P1, no questions asked.
|
||||
{
|
||||
// Apply the profile's properties to player 1 but keep the last profile cv to p1's ACTUAL profile to revert once we exit.
|
||||
UINT8 lastp = cv_lastprofile[0].value;
|
||||
PR_ApplyProfile(PR_GetProfileNum(optionsmenu.profile), 0);
|
||||
CV_StealthSetValue(&cv_lastprofile[0], lastp);
|
||||
}
|
||||
else // != GS_MENU
|
||||
{
|
||||
// ONLY apply the profile if it's in use by anything currently.
|
||||
UINT8 pnum = PR_GetProfileNum(optionsmenu.profile);
|
||||
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
||||
{
|
||||
if (cv_lastprofile[i].value == pnum)
|
||||
{
|
||||
PR_ApplyProfile(pnum, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
#undef KEYHOLDFOR
|
||||
64
src/menus/options-server-1.c
Normal file
64
src/menus/options-server-1.c
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/// \file menus/options-server-1.c
|
||||
/// \brief Server Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t OPTIONS_Server[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Server Name", "Change the name of your server.",
|
||||
NULL, {.cvar = &cv_servername}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Intermission", "Set how long to stay on the result screen.",
|
||||
NULL, {.cvar = &cv_inttime}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Map Progression", "Set how the next map is chosen.",
|
||||
NULL, {.cvar = &cv_advancemap}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Vote Timer", "Set how long players have to vote.",
|
||||
NULL, {.cvar = &cv_votetime}, 0, 0},
|
||||
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Maximum Players", "How many players can play at once.",
|
||||
NULL, {.cvar = &cv_maxplayers}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Maximum Connections", "How many players & spectators can connect to the server.",
|
||||
NULL, {.cvar = &cv_maxconnections}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Allow Joining", "Sets whether players can connect to your server.",
|
||||
NULL, {.cvar = &cv_allownewplayer}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Allow Downloads", "Allows joiners to download missing files from you.",
|
||||
NULL, {.cvar = &cv_downloading}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Pause Permissions", "Sets who can pause the game.",
|
||||
NULL, {.cvar = &cv_pause}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Mute Chat", "Prevents non-admins from sending chat messages.",
|
||||
NULL, {.cvar = &cv_mute}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Advanced...", "Advanced options. Be careful when messing with these!",
|
||||
NULL, {.submenu = &OPTIONS_ServerAdvancedDef}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_ServerDef = {
|
||||
sizeof (OPTIONS_Server) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Server,
|
||||
48, 70, // This menu here is slightly higher because there's a lot of options...
|
||||
SKINCOLOR_VIOLET, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
62
src/menus/options-server-advanced.c
Normal file
62
src/menus/options-server-advanced.c
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/// \file menus/options-server-advanced.c
|
||||
/// \brief Advanced Server Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t OPTIONS_ServerAdvanced[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Server Browser Address", "Default is \'https://ms.kartkrew.org/ms/api\'",
|
||||
NULL, {.cvar = &cv_masterserver}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Resynch. Attempts", "How many times to attempt sending data to desynchronized players.",
|
||||
NULL, {.cvar = &cv_resynchattempts}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Ping Limit (ms)", "Players above the ping limit will get kicked from the server.",
|
||||
NULL, {.cvar = &cv_maxping}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Ping Timeout (s)", "Players must be above the ping limit for this long before being kicked.",
|
||||
NULL, {.cvar = &cv_pingtimeout}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Connection Timeout (tics)", "Players not giving any netowrk activity for this long are kicked.",
|
||||
NULL, {.cvar = &cv_nettimeout}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Join Timeout (tics)", "Players taking too long to join are kicked.",
|
||||
NULL, {.cvar = &cv_jointimeout}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Max File Transfer", "Maximum size of the files that can be downloaded from joining clients. (KB)",
|
||||
NULL, {.cvar = &cv_maxsend}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "File Transfer Speed", "File transfer packet rate. Larger values send more data.",
|
||||
NULL, {.cvar = &cv_downloadspeed}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Log Joiner IPs", "Shows the IP of connecting players.",
|
||||
NULL, {.cvar = &cv_showjoinaddress}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Log Resynch", "Shows which players need resynchronization.",
|
||||
NULL, {.cvar = &cv_blamecfail}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Log Transfers", "Shows when clients are downloading files from you.",
|
||||
NULL, {.cvar = &cv_noticedownload}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_ServerAdvancedDef = {
|
||||
sizeof (OPTIONS_ServerAdvanced) / sizeof (menuitem_t),
|
||||
&OPTIONS_ServerDef,
|
||||
0,
|
||||
OPTIONS_ServerAdvanced,
|
||||
48, 70, // This menu here is slightly higher because there's a lot of options...
|
||||
SKINCOLOR_VIOLET, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
66
src/menus/options-sound.c
Normal file
66
src/menus/options-sound.c
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/// \file menus/options-sound.c
|
||||
/// \brief Sound Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h" // sounds consvars
|
||||
#include "../g_game.h" // cv_chatnotifications
|
||||
|
||||
menuitem_t OPTIONS_Sound[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "SFX", "Enable or disable sound effect playback.",
|
||||
NULL, {.cvar = &cv_gamesounds}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "SFX Volume", "Adjust the volume of sound effects.",
|
||||
NULL, {.cvar = &cv_soundvolume}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Music", "Enable or disable music playback.",
|
||||
NULL, {.cvar = &cv_gamedigimusic}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "Music Volume", "Adjust the volume of music playback.",
|
||||
NULL, {.cvar = &cv_digmusicvolume}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Reverse L/R Channels", "Reverse left & right channels for Stereo playback.",
|
||||
NULL, {.cvar = &stereoreverse}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Surround", "Enables or disable Surround sound playback.",
|
||||
NULL, {.cvar = &surround}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Chat Notifications", "Set when to play notification sounds when chat messages are received.",
|
||||
NULL, {.cvar = &cv_chatnotifications}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Character Voices", "Set how often to play character voices in game.",
|
||||
NULL, {.cvar = &cv_kartvoices}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Play Music While Unfocused", "Keeps playing music even if the game is not the active window.",
|
||||
NULL, {.cvar = &cv_playmusicifunfocused}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Play SFX While Unfocused", "Keeps playing sound effects even if the game is not the active window.",
|
||||
NULL, {.cvar = &cv_playsoundifunfocused}, 0, 0},
|
||||
|
||||
// @TODO: Sound test (there's currently no space on this menu, might be better to throw it in extras?)
|
||||
};
|
||||
|
||||
menu_t OPTIONS_SoundDef = {
|
||||
sizeof (OPTIONS_Sound) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Sound,
|
||||
48, 80,
|
||||
SKINCOLOR_THUNDER, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
68
src/menus/options-video-1.c
Normal file
68
src/menus/options-video-1.c
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/// \file menus/options-video-1.c
|
||||
/// \brief Video Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../r_main.h" // cv_skybox
|
||||
#include "../v_video.h" // cv_globalgamma
|
||||
#include "../r_fps.h" // fps cvars
|
||||
|
||||
// options menu
|
||||
menuitem_t OPTIONS_Video[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CALL, "Set Resolution...", "Change the screen resolution for the game.",
|
||||
NULL, {.routine = M_VideoModeMenu}, 0, 0},
|
||||
|
||||
// A check to see if you're not running on a fucking antique potato powered stone i guess???????
|
||||
|
||||
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
|
||||
{IT_STRING | IT_CVAR, "Fullscreen", "Set whether you want to use fullscreen or windowed mode.",
|
||||
NULL, {.cvar = &cv_fullscreen}, 0, 0},
|
||||
#endif
|
||||
|
||||
{IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
// Everytime I see a screenshot at max gamma I die inside
|
||||
{IT_STRING | IT_CVAR | IT_CV_SLIDER, "Gamma", "Adjusts the overall brightness of the game.",
|
||||
NULL, {.cvar = &cv_globalgamma}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "FPS Cap", "Handles the refresh rate of the game (does not affect gamelogic).",
|
||||
NULL, {.cvar = &cv_fpscap}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Enable Skyboxes", "Turning this off will improve performance at the detriment of visuals for many maps.",
|
||||
NULL, {.cvar = &cv_skybox}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Draw Distance", "How far objects can be drawn. Lower values may improve performance at the cost of visibility.",
|
||||
NULL, {.cvar = &cv_drawdist}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Weather Draw Distance", "Affects how far weather visuals can be drawn. Lower values improve performance.",
|
||||
NULL, {.cvar = &cv_drawdist_precip}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Show FPS", "Displays the game framerate at the lower right corner of the screen.",
|
||||
NULL, {.cvar = &cv_ticrate}, 0, 0},
|
||||
|
||||
{IT_NOTHING|IT_SPACE, NULL, "Kanade best waifu! I promise!",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
#ifdef HWRENDER
|
||||
{IT_STRING | IT_SUBMENU, "Hardware Options...", "For usage and configuration of the OpenGL renderer.",
|
||||
NULL, {.submenu = &OPTIONS_VideoOGLDef}, 0, 0},
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_VideoDef = {
|
||||
sizeof (OPTIONS_Video) / sizeof (menuitem_t),
|
||||
&OPTIONS_MainDef,
|
||||
0,
|
||||
OPTIONS_Video,
|
||||
32, 80,
|
||||
SKINCOLOR_PLAGUE, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
60
src/menus/options-video-gl.c
Normal file
60
src/menus/options-video-gl.c
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/// \file menus/options-video-gl.c
|
||||
/// \brief OpenGL Options
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../hardware/hw_main.h" // gl consvars
|
||||
|
||||
menuitem_t OPTIONS_VideoOGL[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "Renderer", "Change renderers between Software and OpenGL",
|
||||
NULL, {.cvar = &cv_renderer}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_HEADER, "OPTIONS BELOW ARE OPENGL ONLY!", "Watch people get confused anyway!!",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "3D Models", "Use 3D models instead of sprites when applicable.",
|
||||
NULL, {.cvar = &cv_glmodels}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Shaders", "Use GLSL Shaders. Turning them off increases performance at the expanse of visual quality.",
|
||||
NULL, {.cvar = &cv_glshaders}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Texture Quality", "Texture depth. Higher values are recommended.",
|
||||
NULL, {.cvar = &cv_scr_depth}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Texture Filter", "Texture Filter. Nearest is recommended.",
|
||||
NULL, {.cvar = &cv_glfiltermode}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Anisotropic", "Lower values will improve performance at a minor quality loss.",
|
||||
NULL, {.cvar = &cv_glanisotropicmode}, 0, 0},
|
||||
|
||||
{IT_SPACE | IT_NOTHING, NULL, NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Sprite Billboarding", "Adjusts sprites when viewed from above or below to not make them appear flat.",
|
||||
NULL, {.cvar = &cv_glspritebillboarding}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Software Perspective", "Emulates Software shearing when looking up or down. Not recommended.",
|
||||
NULL, {.cvar = &cv_glshearing}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t OPTIONS_VideoOGLDef = {
|
||||
sizeof (OPTIONS_VideoOGL) / sizeof (menuitem_t),
|
||||
&OPTIONS_VideoDef,
|
||||
0,
|
||||
OPTIONS_VideoOGL,
|
||||
32, 80,
|
||||
SKINCOLOR_PLAGUE, 0,
|
||||
2, 5,
|
||||
M_DrawGenericOptions,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
194
src/menus/options-video-modes.c
Normal file
194
src/menus/options-video-modes.c
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/// \file menus/options-video-modes.c
|
||||
/// \brief Video modes (resolutions)
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../i_video.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t OPTIONS_VideoModes[] = {
|
||||
|
||||
{IT_KEYHANDLER | IT_NOTHING, NULL, "Select a resolution.",
|
||||
NULL, {.routine = M_HandleVideoModes}, 0, 0}, // dummy menuitem for the control func
|
||||
|
||||
};
|
||||
|
||||
menu_t OPTIONS_VideoModesDef = {
|
||||
sizeof (OPTIONS_VideoModes) / sizeof (menuitem_t),
|
||||
&OPTIONS_VideoDef,
|
||||
0,
|
||||
OPTIONS_VideoModes,
|
||||
48, 80,
|
||||
SKINCOLOR_PLAGUE, 0,
|
||||
2, 5,
|
||||
M_DrawVideoModes,
|
||||
M_OptionsTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// setup video mode menu
|
||||
void M_VideoModeMenu(INT32 choice)
|
||||
{
|
||||
INT32 i, j, vdup, nummodes;
|
||||
UINT32 width, height;
|
||||
const char *desc;
|
||||
|
||||
(void)choice;
|
||||
|
||||
memset(optionsmenu.modedescs, 0, sizeof(optionsmenu.modedescs));
|
||||
|
||||
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
|
||||
VID_PrepareModeList(); // FIXME: hack
|
||||
#endif
|
||||
optionsmenu.vidm_nummodes = 0;
|
||||
optionsmenu.vidm_selected = 0;
|
||||
nummodes = VID_NumModes();
|
||||
|
||||
// DOS does not skip mode 0, because mode 0 is ALWAYS present
|
||||
i = 0;
|
||||
for (; i < nummodes && optionsmenu.vidm_nummodes < MAXMODEDESCS; i++)
|
||||
{
|
||||
desc = VID_GetModeName(i);
|
||||
if (desc)
|
||||
{
|
||||
vdup = 0;
|
||||
|
||||
// when a resolution exists both under VGA and VESA, keep the
|
||||
// VESA mode, which is always a higher modenum
|
||||
for (j = 0; j < optionsmenu.vidm_nummodes; j++)
|
||||
{
|
||||
if (!strcmp(optionsmenu.modedescs[j].desc, desc))
|
||||
{
|
||||
// mode(0): 320x200 is always standard VGA, not vesa
|
||||
if (optionsmenu.modedescs[j].modenum)
|
||||
{
|
||||
optionsmenu.modedescs[j].modenum = i;
|
||||
vdup = 1;
|
||||
|
||||
if (i == vid.modenum)
|
||||
optionsmenu.vidm_selected = j;
|
||||
}
|
||||
else
|
||||
vdup = 1;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!vdup)
|
||||
{
|
||||
optionsmenu.modedescs[optionsmenu.vidm_nummodes].modenum = i;
|
||||
optionsmenu.modedescs[optionsmenu.vidm_nummodes].desc = desc;
|
||||
|
||||
if (i == vid.modenum)
|
||||
optionsmenu.vidm_selected = optionsmenu.vidm_nummodes;
|
||||
|
||||
// Pull out the width and height
|
||||
sscanf(desc, "%u%*c%u", &width, &height);
|
||||
|
||||
// Show multiples of 320x200 as green.
|
||||
if (SCR_IsAspectCorrect(width, height))
|
||||
optionsmenu.modedescs[optionsmenu.vidm_nummodes].goodratio = 1;
|
||||
|
||||
optionsmenu.vidm_nummodes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optionsmenu.vidm_column_size = (optionsmenu.vidm_nummodes+2) / 3;
|
||||
|
||||
M_SetupNextMenu(&OPTIONS_VideoModesDef, false);
|
||||
}
|
||||
|
||||
// special menuitem key handler for video mode list
|
||||
void M_HandleVideoModes(INT32 ch)
|
||||
{
|
||||
|
||||
const UINT8 pid = 0;
|
||||
(void)ch;
|
||||
|
||||
if (optionsmenu.vidm_testingmode > 0)
|
||||
{
|
||||
// change back to the previous mode quickly
|
||||
if (M_MenuBackPressed(pid))
|
||||
{
|
||||
setmodeneeded = optionsmenu.vidm_previousmode + 1;
|
||||
optionsmenu.vidm_testingmode = 0;
|
||||
}
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
optionsmenu.vidm_testingmode = 0; // stop testing
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
if (++optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes)
|
||||
optionsmenu.vidm_selected = 0;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
if (--optionsmenu.vidm_selected < 0)
|
||||
optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
optionsmenu.vidm_selected -= optionsmenu.vidm_column_size;
|
||||
if (optionsmenu.vidm_selected < 0)
|
||||
optionsmenu.vidm_selected = (optionsmenu.vidm_column_size*3) + optionsmenu.vidm_selected;
|
||||
if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes)
|
||||
optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_lr > 0)
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
optionsmenu.vidm_selected += optionsmenu.vidm_column_size;
|
||||
if (optionsmenu.vidm_selected >= (optionsmenu.vidm_column_size*3))
|
||||
optionsmenu.vidm_selected %= optionsmenu.vidm_column_size;
|
||||
if (optionsmenu.vidm_selected >= optionsmenu.vidm_nummodes)
|
||||
optionsmenu.vidm_selected = optionsmenu.vidm_nummodes - 1;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
if (vid.modenum == optionsmenu.modedescs[optionsmenu.vidm_selected].modenum)
|
||||
SCR_SetDefaultMode();
|
||||
else
|
||||
{
|
||||
optionsmenu.vidm_testingmode = 15*TICRATE;
|
||||
optionsmenu.vidm_previousmode = vid.modenum;
|
||||
if (!setmodeneeded) // in case the previous setmode was not finished
|
||||
setmodeneeded = optionsmenu.modedescs[optionsmenu.vidm_selected].modenum + 1;
|
||||
}
|
||||
}
|
||||
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
if (currentMenu->prevMenu)
|
||||
M_SetupNextMenu(currentMenu->prevMenu, false);
|
||||
else
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/menus/play-1.c
Normal file
17
src/menus/play-1.c
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/// \file menus/play-1.c
|
||||
/// \brief Play Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t PLAY_MainMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Local Play", "Play only on this computer.",
|
||||
NULL, {.routine = M_SetupGametypeMenu}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Online", "Connect to other computers.",
|
||||
NULL, {.routine = M_MPOptSelectInit}, /*M_MPRoomSelectInit,*/ 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_MainDef = KARTGAMEMODEMENU(PLAY_MainMenu, &PLAY_CharSelectDef);
|
||||
1484
src/menus/play-char-select.c
Normal file
1484
src/menus/play-char-select.c
Normal file
File diff suppressed because it is too large
Load diff
69
src/menus/play-local-1.c
Normal file
69
src/menus/play-local-1.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/// \file menus/play-local-1.c
|
||||
/// \brief Local Play, gamemode selection menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
|
||||
menuitem_t PLAY_GamemodesMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Race", "A contest to see who's the fastest of them all!",
|
||||
NULL, {.routine = M_SetupRaceMenu}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Battle", "It's last kart standing in this free-for-all!",
|
||||
"MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE},
|
||||
|
||||
{IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!",
|
||||
NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE},
|
||||
|
||||
{IT_STRING | IT_CALL, "Special", "Strike your target and secure the prize!",
|
||||
NULL, {.routine = M_LevelSelectInit}, 1, GT_SPECIAL},
|
||||
|
||||
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_GamemodesDef = KARTGAMEMODEMENU(PLAY_GamemodesMenu, &PLAY_MainDef);
|
||||
|
||||
void M_SetupGametypeMenu(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
PLAY_GamemodesDef.prevMenu = currentMenu;
|
||||
|
||||
// Battle and Capsules (and Special) disabled
|
||||
PLAY_GamemodesMenu[1].status = IT_DISABLED;
|
||||
PLAY_GamemodesMenu[2].status = IT_DISABLED;
|
||||
PLAY_GamemodesMenu[3].status = IT_DISABLED;
|
||||
|
||||
if (cv_splitplayers.value > 1)
|
||||
{
|
||||
// Re-add Battle
|
||||
PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL;
|
||||
}
|
||||
else
|
||||
{
|
||||
boolean anyunlocked = false;
|
||||
|
||||
if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true))
|
||||
{
|
||||
// Re-add Capsules
|
||||
PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL;
|
||||
anyunlocked = true;
|
||||
}
|
||||
|
||||
if (M_SecretUnlocked(SECRET_SPECIALATTACK, true))
|
||||
{
|
||||
// Re-add Special
|
||||
PLAY_GamemodesMenu[3].status = IT_STRING | IT_CALL;
|
||||
anyunlocked = true;
|
||||
}
|
||||
|
||||
if (!anyunlocked)
|
||||
{
|
||||
// Only one non-Back entry, let's skip straight to Race.
|
||||
M_SetupRaceMenu(-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
M_SetupNextMenu(&PLAY_GamemodesDef, false);
|
||||
}
|
||||
40
src/menus/play-local-race-1.c
Normal file
40
src/menus/play-local-race-1.c
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/// \file menus/play-local-race-1.c
|
||||
/// \brief Race Mode Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
|
||||
menuitem_t PLAY_RaceGamemodesMenu[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Grand Prix", "Compete for the best rank over five races!",
|
||||
NULL, {.routine = M_SetupDifficultySelect}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Match Race", "Play by your own rules in a specialized, single race!",
|
||||
"MENIMG01", {.routine = M_SetupDifficultySelect}, 1, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Time Attack", "Record your best time on any track!",
|
||||
NULL, {.routine = M_LevelSelectInit}, 1, GT_RACE},
|
||||
|
||||
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_RaceGamemodesDef = KARTGAMEMODEMENU(PLAY_RaceGamemodesMenu, &PLAY_GamemodesDef);
|
||||
|
||||
void M_SetupRaceMenu(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
PLAY_RaceGamemodesDef.prevMenu = currentMenu;
|
||||
|
||||
// Time Attack disabled
|
||||
PLAY_RaceGamemodesMenu[2].status = IT_DISABLED;
|
||||
|
||||
// Time Attack is 1P only
|
||||
if (cv_splitplayers.value <= 1
|
||||
&& M_SecretUnlocked(SECRET_TIMEATTACK, true))
|
||||
{
|
||||
PLAY_RaceGamemodesMenu[2].status = IT_STRING | IT_CALL;
|
||||
}
|
||||
|
||||
M_SetupNextMenu(&PLAY_RaceGamemodesDef, false);
|
||||
}
|
||||
110
src/menus/play-local-race-difficulty.c
Normal file
110
src/menus/play-local-race-difficulty.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/// \file menus/play-local-race-difficulty.c
|
||||
/// \brief difficulty selection -- see drace_e
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../m_cond.h" // Condition Sets
|
||||
|
||||
menuitem_t PLAY_RaceDifficulty[] =
|
||||
{
|
||||
// For GP
|
||||
{IT_STRING | IT_CVAR, "Difficulty", "Select the game difficulty",
|
||||
NULL, {.cvar = &cv_dummygpdifficulty}, 0, 0},
|
||||
|
||||
// Match Race
|
||||
{IT_STRING | IT_CVAR, "Difficulty", "Select the game speed",
|
||||
NULL, {.cvar = &cv_dummykartspeed}, 0, 0},
|
||||
|
||||
// DISABLE THAT OPTION OUTSIDE OF MATCH RACE
|
||||
{IT_STRING2 | IT_CVAR, "CPU", "Set the difficulty of CPU players.",
|
||||
NULL, {.cvar = &cv_dummymatchbots}, 0, 0},
|
||||
{IT_STRING2 | IT_CVAR, "Racers", "Sets the number of racers, including players and CPU.",
|
||||
NULL, {.cvar = &cv_maxplayers}, 0, 0},
|
||||
|
||||
{IT_STRING2 | IT_CVAR, "Encore", "Enable or disable Encore mode",
|
||||
NULL, {.cvar = &cv_dummygpencore}, 0, 0},
|
||||
|
||||
// For GP
|
||||
{IT_STRING | IT_CALL, "Cup Select", "Go on and select a cup!", NULL, {.routine = M_LevelSelectInit}, 2, GT_RACE},
|
||||
|
||||
// Match Race
|
||||
{IT_STRING | IT_CALL, "Map Select", "Go on and select a race track!", NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE},
|
||||
|
||||
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_RaceDifficultyDef = {
|
||||
sizeof(PLAY_RaceDifficulty) / sizeof(menuitem_t),
|
||||
&PLAY_RaceGamemodesDef,
|
||||
0,
|
||||
PLAY_RaceDifficulty,
|
||||
0, 0,
|
||||
0, 0,
|
||||
1, 5,
|
||||
M_DrawRaceDifficulty,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
consvar_t cv_dummygpdifficulty = CVAR_INIT ("dummygpdifficulty", "Normal", CV_HIDDEN, gpdifficulty_cons_t, NULL);
|
||||
consvar_t cv_dummykartspeed = CVAR_INIT ("dummykartspeed", "Normal", CV_HIDDEN, dummykartspeed_cons_t, NULL);
|
||||
consvar_t cv_dummygpencore = CVAR_INIT ("dummygpencore", "Off", CV_HIDDEN, CV_OnOff, NULL);
|
||||
|
||||
static CV_PossibleValue_t dummymatchbots_cons_t[] = {
|
||||
{0, "Off"},
|
||||
{1, "Lv.1"},
|
||||
{2, "Lv.2"},
|
||||
{3, "Lv.3"},
|
||||
{4, "Lv.4"},
|
||||
{5, "Lv.5"},
|
||||
{6, "Lv.6"},
|
||||
{7, "Lv.7"},
|
||||
{8, "Lv.8"},
|
||||
{9, "Lv.9"},
|
||||
{10, "Lv.10"},
|
||||
{11, "Lv.11"},
|
||||
{12, "Lv.12"},
|
||||
{13, "Lv.MAX"},
|
||||
{0, NULL}
|
||||
};
|
||||
consvar_t cv_dummymatchbots = CVAR_INIT ("dummymatchbots", "Off", CV_HIDDEN, dummymatchbots_cons_t, NULL);
|
||||
|
||||
void M_SetupDifficultySelect(INT32 choice)
|
||||
{
|
||||
// check what we picked.
|
||||
choice = currentMenu->menuitems[itemOn].mvar1;
|
||||
|
||||
// setup the difficulty menu and then remove choices depending on choice
|
||||
PLAY_RaceDifficultyDef.prevMenu = currentMenu;
|
||||
|
||||
PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_mrcpu].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_mrracers].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_encore].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_cupselect].status = IT_DISABLED;
|
||||
PLAY_RaceDifficulty[drace_mapselect].status = IT_DISABLED;
|
||||
|
||||
if (choice) // Match Race
|
||||
{
|
||||
PLAY_RaceDifficulty[drace_mrkartspeed].status = IT_STRING|IT_CVAR; // Kart Speed
|
||||
PLAY_RaceDifficulty[drace_mrcpu].status = IT_STRING2|IT_CVAR; // CPUs on/off
|
||||
PLAY_RaceDifficulty[drace_mrracers].status = IT_STRING2|IT_CVAR; // CPU amount
|
||||
PLAY_RaceDifficulty[drace_mapselect].status = IT_STRING|IT_CALL; // Level Select (Match Race)
|
||||
PLAY_RaceDifficultyDef.lastOn = drace_mapselect; // Select map select by default.
|
||||
}
|
||||
else // GP
|
||||
{
|
||||
PLAY_RaceDifficulty[drace_gpdifficulty].status = IT_STRING|IT_CVAR; // Difficulty
|
||||
PLAY_RaceDifficulty[drace_cupselect].status = IT_STRING|IT_CALL; // Level Select (GP)
|
||||
PLAY_RaceDifficultyDef.lastOn = drace_cupselect; // Select cup select by default.
|
||||
}
|
||||
|
||||
if (M_SecretUnlocked(SECRET_ENCORE, false))
|
||||
{
|
||||
PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off
|
||||
}
|
||||
|
||||
M_SetupNextMenu(&PLAY_RaceDifficultyDef, false);
|
||||
}
|
||||
210
src/menus/play-local-race-time-attack.c
Normal file
210
src/menus/play-local-race-time-attack.c
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/// \file menus/play-local-race-time-attack.c
|
||||
/// \brief Race Time Attack Menu
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../r_local.h" // SplitScreen_OnChange
|
||||
#include "../s_sound.h"
|
||||
#include "../f_finale.h" // F_WipeStartScreen
|
||||
#include "../v_video.h"
|
||||
#include "../d_main.h" // srb2home
|
||||
#include "../m_misc.h" // M_MkdirEach
|
||||
|
||||
// see ta_e
|
||||
menuitem_t PLAY_TimeAttack[] =
|
||||
{
|
||||
{IT_STRING | IT_SUBMENU, "Replay...", NULL, NULL, {.submenu = &PLAY_TAReplayDef}, 0, 0},
|
||||
{IT_STRING | IT_SUBMENU, "Guest...", NULL, NULL, {.submenu = &PLAY_TAReplayGuestDef}, 0, 0},
|
||||
{IT_STRING | IT_SUBMENU, "Ghosts...", NULL, NULL, {.submenu = &PLAY_TAGhostsDef}, 0, 0},
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Start", NULL, NULL, {.routine = M_StartTimeAttack}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_TimeAttackDef = {
|
||||
sizeof(PLAY_TimeAttack) / sizeof(menuitem_t),
|
||||
&PLAY_LevelSelectDef,
|
||||
0,
|
||||
PLAY_TimeAttack,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawTimeAttack,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
menuitem_t PLAY_TAReplay[] =
|
||||
{
|
||||
{IT_STRING | IT_CALL, "Replay Best Time", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Replay Best Lap", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0},
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Replay Last", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Replay Guest", NULL, NULL, {.routine = M_ReplayTimeAttack}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Replay Staff", NULL, NULL, {.routine = M_HandleStaffReplay}, 0, 0},
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_TAReplayDef = {
|
||||
sizeof(PLAY_TAReplay) / sizeof(menuitem_t),
|
||||
&PLAY_TimeAttackDef,
|
||||
0,
|
||||
PLAY_TAReplay,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawTimeAttack,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
menuitem_t PLAY_TAReplayGuest[] =
|
||||
{
|
||||
{IT_HEADERTEXT|IT_HEADER, "Save as guest...", NULL, NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Best Time", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Best Lap", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Last Run", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0},
|
||||
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Delete Guest", NULL, NULL, {.routine = M_SetGuestReplay}, 0, 0},
|
||||
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
{IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t PLAY_TAReplayGuestDef = {
|
||||
sizeof(PLAY_TAReplayGuest) / sizeof(menuitem_t),
|
||||
&PLAY_TimeAttackDef,
|
||||
0,
|
||||
PLAY_TAReplayGuest,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawTimeAttack,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
menuitem_t PLAY_TAGhosts[] =
|
||||
{
|
||||
{IT_STRING | IT_CVAR, "Best Time", NULL, NULL, {.cvar = &cv_ghost_besttime}, 0, 0},
|
||||
{IT_STRING | IT_CVAR, "Best Lap", NULL, NULL, {.cvar = &cv_ghost_bestlap}, 0, 0},
|
||||
{IT_STRING | IT_CVAR, "Last", NULL, NULL, {.cvar = &cv_ghost_last}, 0, 0},
|
||||
{IT_DISABLED, "Guest", NULL, NULL, {.cvar = &cv_ghost_guest}, 0, 0},
|
||||
{IT_DISABLED, "Staff", NULL, NULL, {.cvar = &cv_ghost_staff}, 0, 0},
|
||||
|
||||
{IT_HEADERTEXT|IT_HEADER, "", NULL, NULL, {NULL}, 0, 0},
|
||||
{IT_STRING | IT_SUBMENU, "Back", NULL, NULL, {.submenu = &PLAY_TimeAttackDef}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_TAGhostsDef = {
|
||||
sizeof(PLAY_TAGhosts) / sizeof(menuitem_t),
|
||||
&PLAY_TimeAttackDef,
|
||||
0,
|
||||
PLAY_TAGhosts,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawTimeAttack,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
// autorecord demos for time attack
|
||||
consvar_t cv_autorecord = CVAR_INIT ("autorecord", "Yes", 0, CV_YesNo, NULL);
|
||||
|
||||
CV_PossibleValue_t ghost_cons_t[] = {{0, "Hide"}, {1, "Show Character"}, {2, "Show All"}, {0, NULL}};
|
||||
CV_PossibleValue_t ghost2_cons_t[] = {{0, "Hide"}, {1, "Show"}, {0, NULL}};
|
||||
|
||||
consvar_t cv_ghost_besttime = CVAR_INIT ("ghost_besttime", "Show All", CV_SAVE, ghost_cons_t, NULL);
|
||||
consvar_t cv_ghost_bestlap = CVAR_INIT ("ghost_bestlap", "Show All", CV_SAVE, ghost_cons_t, NULL);
|
||||
consvar_t cv_ghost_last = CVAR_INIT ("ghost_last", "Show All", CV_SAVE, ghost_cons_t, NULL);
|
||||
consvar_t cv_ghost_guest = CVAR_INIT ("ghost_guest", "Show", CV_SAVE, ghost2_cons_t, NULL);
|
||||
consvar_t cv_ghost_staff = CVAR_INIT ("ghost_staff", "Show", CV_SAVE, ghost2_cons_t, NULL);
|
||||
|
||||
// time attack stuff...
|
||||
void M_HandleStaffReplay(INT32 choice)
|
||||
{
|
||||
// @TODO:
|
||||
(void) choice;
|
||||
}
|
||||
|
||||
void M_ReplayTimeAttack(INT32 choice)
|
||||
{
|
||||
// @TODO:
|
||||
(void) choice;
|
||||
}
|
||||
|
||||
void M_SetGuestReplay(INT32 choice)
|
||||
{
|
||||
// @TODO:
|
||||
(void) choice;
|
||||
}
|
||||
|
||||
void M_StartTimeAttack(INT32 choice)
|
||||
{
|
||||
char *gpath;
|
||||
char nameofdemo[256];
|
||||
|
||||
(void)choice;
|
||||
|
||||
modeattacking = ATTACKING_TIME;
|
||||
|
||||
if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT)
|
||||
&& (mapheaderinfo[levellist.choosemap]->numlaps != 1))
|
||||
{
|
||||
modeattacking |= ATTACKING_LAP;
|
||||
}
|
||||
|
||||
// Still need to reset devmode
|
||||
cht_debug = 0;
|
||||
emeralds = 0;
|
||||
|
||||
if (demo.playback)
|
||||
G_StopDemo();
|
||||
if (metalrecording)
|
||||
G_StopMetalDemo();
|
||||
|
||||
splitscreen = 0;
|
||||
SplitScreen_OnChange();
|
||||
|
||||
S_StartSound(NULL, sfx_s3k63);
|
||||
|
||||
paused = false;
|
||||
|
||||
// Early fadeout to let the sound finish playing
|
||||
F_WipeStartScreen();
|
||||
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
|
||||
F_WipeEndScreen();
|
||||
F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
|
||||
|
||||
SV_StartSinglePlayerServer(levellist.newgametype, false);
|
||||
|
||||
gpath = va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s",
|
||||
srb2home, timeattackfolder);
|
||||
M_MkdirEach(gpath, M_PathParts(gpath) - 3, 0755);
|
||||
|
||||
strcat(gpath, PATHSEP);
|
||||
strcat(gpath, G_BuildMapName(levellist.choosemap+1));
|
||||
|
||||
snprintf(nameofdemo, sizeof nameofdemo, "%s-%s-last", gpath, cv_skin[0].string);
|
||||
|
||||
if (!cv_autorecord.value)
|
||||
remove(va("%s"PATHSEP"%s.lmp", srb2home, nameofdemo));
|
||||
else
|
||||
G_RecordDemo(nameofdemo);
|
||||
|
||||
M_ClearMenus(true);
|
||||
D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_dummygpencore.value == 1), 1, 1, false, false);
|
||||
}
|
||||
84
src/menus/play-online-1.c
Normal file
84
src/menus/play-online-1.c
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/// \file menus/play-online-1.c
|
||||
/// \brief MULTIPLAYER OPTION SELECT
|
||||
|
||||
#include "../k_menu.h"
|
||||
|
||||
menuitem_t PLAY_MP_OptSelect[] =
|
||||
{
|
||||
//{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0},
|
||||
{IT_STRING | IT_CALL, "Host Game", "Start your own online game!",
|
||||
NULL, {.routine = M_MPHostInit}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Server Browser", "Search for game servers to play in.",
|
||||
NULL, {.routine = M_MPRoomSelectInit}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "Join by IP", "Join an online game by its IP address.",
|
||||
NULL, {.routine = M_MPJoinIPInit}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_MP_OptSelectDef = {
|
||||
sizeof (PLAY_MP_OptSelect) / sizeof (menuitem_t),
|
||||
&PLAY_MainDef,
|
||||
0,
|
||||
PLAY_MP_OptSelect,
|
||||
0, 0,
|
||||
0, 0,
|
||||
-1, 1,
|
||||
M_DrawMPOptSelect,
|
||||
M_MPOptSelectTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
struct mpmenu_s mpmenu;
|
||||
|
||||
// Use this as a quit routine within the HOST GAME and JOIN BY IP "sub" menus
|
||||
boolean M_MPResetOpts(void)
|
||||
{
|
||||
UINT8 i = 0;
|
||||
|
||||
for (; i < 3; i++)
|
||||
mpmenu.modewinextend[i][0] = 0; // Undo this
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void M_MPOptSelectInit(INT32 choice)
|
||||
{
|
||||
INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}};
|
||||
UINT8 i = 0, j = 0; // To copy the array into the struct
|
||||
const UINT32 forbidden = GTR_FORBIDMP;
|
||||
|
||||
(void)choice;
|
||||
|
||||
mpmenu.modechoice = 0;
|
||||
mpmenu.ticker = 0;
|
||||
|
||||
for (; i < 3; i++)
|
||||
for (j = 0; j < 3; j++)
|
||||
mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already
|
||||
|
||||
// Guarantee menugametype is good
|
||||
M_NextMenuGametype(forbidden);
|
||||
M_PrevMenuGametype(forbidden);
|
||||
|
||||
M_SetupNextMenu(&PLAY_MP_OptSelectDef, false);
|
||||
}
|
||||
|
||||
void M_MPOptSelectTick(void)
|
||||
{
|
||||
UINT8 i = 0;
|
||||
|
||||
// 3 Because we have 3 options in the menu
|
||||
for (; i < 3; i++)
|
||||
{
|
||||
if (mpmenu.modewinextend[i][0])
|
||||
mpmenu.modewinextend[i][2] += 8;
|
||||
else
|
||||
mpmenu.modewinextend[i][2] -= 8;
|
||||
|
||||
mpmenu.modewinextend[i][2] = min(mpmenu.modewinextend[i][1], max(0, mpmenu.modewinextend[i][2]));
|
||||
//CONS_Printf("%d - %d,%d,%d\n", i, mpmenu.modewinextend[i][0], mpmenu.modewinextend[i][1], mpmenu.modewinextend[i][2]);
|
||||
}
|
||||
}
|
||||
112
src/menus/play-online-host.c
Normal file
112
src/menus/play-online-host.c
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/// \file menus/play-online-host.c
|
||||
/// \brief MULTIPLAYER HOST SCREEN -- see mhost_e
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
// MULTIPLAYER HOST SCREEN -- see mhost_e
|
||||
menuitem_t PLAY_MP_Host[] =
|
||||
{
|
||||
//{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "Server Name", "Display name for your game online. Other players will see this.",
|
||||
NULL, {.cvar = &cv_servername}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Public Server", "Display or not your game in the Server Browser for other players.",
|
||||
NULL, {.cvar = &cv_advertise}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR, "Max. Players", "Set how many players can play at once. Others will spectate.",
|
||||
NULL, {.cvar = &cv_maxplayers}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_KEYHANDLER, "Gamemode", "Choose the type of play on your server.",
|
||||
NULL, {.routine = M_HandleHostMenuGametype}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode",
|
||||
NULL, {.routine = M_MPSetupNetgameMapSelect}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t PLAY_MP_HostDef = {
|
||||
sizeof (PLAY_MP_Host) / sizeof (menuitem_t),
|
||||
&PLAY_MP_OptSelectDef,
|
||||
0,
|
||||
PLAY_MP_Host,
|
||||
0, 0,
|
||||
0, 0,
|
||||
-1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe
|
||||
M_DrawMPHost,
|
||||
M_MPOptSelectTick, // This handles the unfolding options
|
||||
NULL,
|
||||
M_MPResetOpts,
|
||||
NULL
|
||||
};
|
||||
|
||||
void M_MPHostInit(INT32 choice)
|
||||
{
|
||||
|
||||
(void)choice;
|
||||
mpmenu.modewinextend[0][0] = 1;
|
||||
M_SetupNextMenu(&PLAY_MP_HostDef, true);
|
||||
itemOn = mhost_go;
|
||||
}
|
||||
|
||||
void M_HandleHostMenuGametype(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
const UINT32 forbidden = GTR_FORBIDMP;
|
||||
|
||||
(void)choice;
|
||||
|
||||
if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
return;
|
||||
}
|
||||
else if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_NextMenuGametype(forbidden);
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
M_PrevMenuGametype(forbidden);
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
M_NextOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
M_PrevOpt();
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void M_MPSetupNetgameMapSelect(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
// Yep, we'll be starting a netgame.
|
||||
levellist.netgame = true;
|
||||
// Make sure we reset those
|
||||
levellist.levelsearch.timeattack = false;
|
||||
levellist.levelsearch.checklocked = true;
|
||||
cupgrid.grandprix = false;
|
||||
|
||||
// okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back.
|
||||
mpmenu.modewinextend[0][0] = 1;
|
||||
|
||||
if (!M_LevelListFromGametype(menugametype))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3kb2);
|
||||
M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[menugametype]->name), NULL, MM_NOTHING);
|
||||
}
|
||||
}
|
||||
110
src/menus/play-online-join-ip.c
Normal file
110
src/menus/play-online-join-ip.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/// \file menus/play-online-join-ip.c
|
||||
/// \brief MULTIPLAYER JOIN BY IP
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../v_video.h"
|
||||
#include "../i_system.h" // I_OsPolling
|
||||
#include "../i_video.h" // I_UpdateNoBlit
|
||||
#include "../m_misc.h" // NUMLOGIP
|
||||
|
||||
menuitem_t PLAY_MP_JoinIP[] =
|
||||
{
|
||||
//{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0},
|
||||
|
||||
{IT_STRING | IT_CVAR | IT_CV_STRING, "IP: ", "Type the IPv4 address of the server.",
|
||||
NULL, {.cvar = &cv_dummyip}, 0, 0},
|
||||
|
||||
{IT_STRING, "CONNECT ", "Attempt to connect to the server you entered the IP for.",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING | IT_SPACE, "LAST IPs JOINED:", "Kanade best waifu :)",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING, "servip1", "The last 3 IPs you've succesfully joined are displayed here.",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING, "servip2", "The last 3 IPs you've succesfully joined are displayed here.",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_STRING, "servip3", "The last 3 IPs you've succesfully joined are displayed here.",
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
};
|
||||
|
||||
menu_t PLAY_MP_JoinIPDef = {
|
||||
sizeof (PLAY_MP_JoinIP) / sizeof (menuitem_t),
|
||||
&PLAY_MP_OptSelectDef,
|
||||
0,
|
||||
PLAY_MP_JoinIP,
|
||||
0, 0,
|
||||
0, 0,
|
||||
-1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe
|
||||
M_DrawMPJoinIP,
|
||||
M_MPOptSelectTick, // This handles the unfolding options
|
||||
NULL,
|
||||
M_MPResetOpts,
|
||||
M_JoinIPInputs
|
||||
};
|
||||
|
||||
consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL);
|
||||
|
||||
void M_MPJoinIPInit(INT32 choice)
|
||||
{
|
||||
|
||||
(void)choice;
|
||||
mpmenu.modewinextend[2][0] = 1;
|
||||
M_SetupNextMenu(&PLAY_MP_JoinIPDef, true);
|
||||
}
|
||||
|
||||
// Attempts to join a given IP from the menu.
|
||||
void M_JoinIP(const char *ipa)
|
||||
{
|
||||
if (*(ipa) == '\0') // Jack shit
|
||||
{
|
||||
M_StartMessage("Please specify an address.\n", NULL, MM_NOTHING);
|
||||
return;
|
||||
}
|
||||
|
||||
COM_BufAddText(va("connect \"%s\"\n", ipa));
|
||||
|
||||
// A little "please wait" message.
|
||||
M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2);
|
||||
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server...");
|
||||
I_OsPolling();
|
||||
I_UpdateNoBlit();
|
||||
if (rendermode == render_soft)
|
||||
I_FinishUpdate(); // page flip or blit buffer
|
||||
}
|
||||
|
||||
boolean M_JoinIPInputs(INT32 ch)
|
||||
{
|
||||
|
||||
const UINT8 pid = 0;
|
||||
(void) ch;
|
||||
|
||||
if (itemOn == 1) // connect field
|
||||
{
|
||||
// enter: connect
|
||||
if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_JoinIP(cv_dummyip.string);
|
||||
M_SetMenuDelay(pid);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (currentMenu->numitems - itemOn <= NUMLOGIP && M_MenuConfirmPressed(pid)) // On one of the last 3 options for IP rejoining
|
||||
{
|
||||
UINT8 index = NUMLOGIP - (currentMenu->numitems - itemOn);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
// Is there an address at this part of the table?
|
||||
if (*joinedIPlist[index][0])
|
||||
M_JoinIP(joinedIPlist[index][0]);
|
||||
else
|
||||
S_StartSound(NULL, sfx_lose);
|
||||
|
||||
return true; // eat input.
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
66
src/menus/play-online-room-select.c
Normal file
66
src/menus/play-online-room-select.c
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/// \file menus/play-online-room-select.c
|
||||
/// \brief MULTIPLAYER ROOM SELECT MENU
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../s_sound.h"
|
||||
|
||||
menuitem_t PLAY_MP_RoomSelect[] =
|
||||
{
|
||||
{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_MPRoomSelect}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_MP_RoomSelectDef = {
|
||||
sizeof (PLAY_MP_RoomSelect) / sizeof (menuitem_t),
|
||||
&PLAY_MP_OptSelectDef,
|
||||
0,
|
||||
PLAY_MP_RoomSelect,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0, 0,
|
||||
M_DrawMPRoomSelect,
|
||||
M_MPRoomSelectTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
void M_MPRoomSelect(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
(void) choice;
|
||||
|
||||
if (menucmd[pid].dpad_lr)
|
||||
{
|
||||
mpmenu.room = (!mpmenu.room) ? 1 : 0;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (M_MenuConfirmPressed(pid))
|
||||
{
|
||||
M_ServersMenu(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
}
|
||||
|
||||
void M_MPRoomSelectTick(void)
|
||||
{
|
||||
mpmenu.ticker++;
|
||||
}
|
||||
|
||||
void M_MPRoomSelectInit(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
mpmenu.room = 0;
|
||||
mpmenu.ticker = 0;
|
||||
mpmenu.servernum = 0;
|
||||
mpmenu.scrolln = 0;
|
||||
mpmenu.slide = 0;
|
||||
|
||||
M_SetupNextMenu(&PLAY_MP_RoomSelectDef, false);
|
||||
}
|
||||
494
src/menus/play-online-server-browser.c
Normal file
494
src/menus/play-online-server-browser.c
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
/// \file menus/play-online-server-browser.c
|
||||
/// \brief MULTIPLAYER ROOM FETCH / REFRESH THREADS
|
||||
|
||||
#include "../k_menu.h"
|
||||
#include "../v_video.h"
|
||||
#include "../i_system.h" // I_OsPolling
|
||||
#include "../i_video.h" // I_UpdateNoBlit
|
||||
|
||||
#ifdef SERVERLISTDEBUG
|
||||
#include "../m_random.h"
|
||||
#endif
|
||||
|
||||
menuitem_t PLAY_MP_ServerBrowser[] =
|
||||
{
|
||||
|
||||
{IT_STRING | IT_CVAR, "SORT BY", NULL, // tooltip MUST be null.
|
||||
NULL, {.cvar = &cv_serversort}, 0, 0},
|
||||
|
||||
{IT_STRING, "REFRESH", NULL,
|
||||
NULL, {NULL}, 0, 0},
|
||||
|
||||
{IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_MP_ServerBrowserDef = {
|
||||
sizeof (PLAY_MP_ServerBrowser) / sizeof (menuitem_t),
|
||||
&PLAY_MP_RoomSelectDef,
|
||||
0,
|
||||
PLAY_MP_ServerBrowser,
|
||||
32, 36,
|
||||
0, 0,
|
||||
0, 0,
|
||||
M_DrawMPServerBrowser,
|
||||
M_MPServerBrowserTick,
|
||||
NULL,
|
||||
NULL,
|
||||
M_ServerBrowserInputs
|
||||
};
|
||||
|
||||
static CV_PossibleValue_t serversort_cons_t[] = {
|
||||
{0,"Ping"},
|
||||
{1,"AVG. Power Level"},
|
||||
{2,"Most Players"},
|
||||
{3,"Least Players"},
|
||||
{4,"Max Player Slots"},
|
||||
{5,"Gametype"},
|
||||
{0,NULL}
|
||||
};
|
||||
consvar_t cv_serversort = CVAR_INIT ("serversort", "Ping", CV_CALL, serversort_cons_t, M_SortServerList);
|
||||
|
||||
// for server fetch threads...
|
||||
M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
|
||||
|
||||
// depending on mpmenu.room, either allows only unmodded servers or modded ones. Remove others from the list.
|
||||
// we do this by iterating backwards.
|
||||
static void M_CleanServerList(void)
|
||||
{
|
||||
UINT8 i = serverlistcount;
|
||||
|
||||
while (i)
|
||||
{
|
||||
|
||||
if (serverlist[i].info.modifiedgame != mpmenu.room)
|
||||
{
|
||||
// move everything after this index 1 slot down...
|
||||
if (i != serverlistcount)
|
||||
memcpy(&serverlist[i], &serverlist[i+1], sizeof(serverelem_t)*(serverlistcount-i));
|
||||
|
||||
serverlistcount--;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
M_SetWaitingMode (int mode)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&k_menu_mutex);
|
||||
#endif
|
||||
{
|
||||
m_waiting_mode = mode;
|
||||
}
|
||||
#ifdef HAVE_THREADS
|
||||
I_unlock_mutex(k_menu_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
M_GetWaitingMode (void)
|
||||
{
|
||||
int mode;
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&k_menu_mutex);
|
||||
#endif
|
||||
{
|
||||
mode = m_waiting_mode;
|
||||
}
|
||||
#ifdef HAVE_THREADS
|
||||
I_unlock_mutex(k_menu_mutex);
|
||||
#endif
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
#ifdef MASTERSERVER
|
||||
#ifdef HAVE_THREADS
|
||||
void
|
||||
Spawn_masterserver_thread (const char *name, void (*thread)(int*))
|
||||
{
|
||||
int *id = malloc(sizeof *id);
|
||||
|
||||
I_lock_mutex(&ms_QueryId_mutex);
|
||||
{
|
||||
*id = ms_QueryId;
|
||||
}
|
||||
I_unlock_mutex(ms_QueryId_mutex);
|
||||
|
||||
I_spawn_thread(name, (I_thread_fn)thread, id);
|
||||
}
|
||||
|
||||
int
|
||||
Same_instance (int id)
|
||||
{
|
||||
int okay;
|
||||
|
||||
I_lock_mutex(&ms_QueryId_mutex);
|
||||
{
|
||||
okay = ( id == ms_QueryId );
|
||||
}
|
||||
I_unlock_mutex(ms_QueryId_mutex);
|
||||
|
||||
return okay;
|
||||
}
|
||||
#endif/*HAVE_THREADS*/
|
||||
|
||||
void
|
||||
Fetch_servers_thread (int *id)
|
||||
{
|
||||
msg_server_t * server_list;
|
||||
|
||||
(void)id;
|
||||
|
||||
M_SetWaitingMode(M_WAITING_SERVERS);
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
server_list = GetShortServersList(*id);
|
||||
#else
|
||||
server_list = GetShortServersList(0);
|
||||
#endif
|
||||
|
||||
if (server_list)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
if (Same_instance(*id))
|
||||
#endif
|
||||
{
|
||||
M_SetWaitingMode(M_NOT_WAITING);
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&ms_ServerList_mutex);
|
||||
{
|
||||
ms_ServerList = server_list;
|
||||
}
|
||||
I_unlock_mutex(ms_ServerList_mutex);
|
||||
#else
|
||||
CL_QueryServerList(server_list);
|
||||
free(server_list);
|
||||
#endif
|
||||
}
|
||||
#ifdef HAVE_THREADS
|
||||
else
|
||||
{
|
||||
free(server_list);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
free(id);
|
||||
#endif
|
||||
}
|
||||
#endif/*MASTERSERVER*/
|
||||
|
||||
// updates serverlist
|
||||
void M_RefreshServers(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
|
||||
// Display a little "please wait" message.
|
||||
M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
|
||||
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers...");
|
||||
V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
|
||||
I_OsPolling();
|
||||
I_UpdateNoBlit();
|
||||
if (rendermode == render_soft)
|
||||
I_FinishUpdate(); // page flip or blit buffer
|
||||
|
||||
#ifdef MASTERSERVER
|
||||
#ifdef HAVE_THREADS
|
||||
Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread);
|
||||
#else/*HAVE_THREADS*/
|
||||
Fetch_servers_thread(NULL);
|
||||
#endif/*HAVE_THREADS*/
|
||||
#else/*MASTERSERVER*/
|
||||
CL_UpdateServerList();
|
||||
#endif/*MASTERSERVER*/
|
||||
|
||||
#ifdef SERVERLISTDEBUG
|
||||
M_ServerListFillDebug();
|
||||
#endif
|
||||
M_CleanServerList();
|
||||
M_SortServerList();
|
||||
|
||||
}
|
||||
|
||||
#ifdef UPDATE_ALERT
|
||||
static void M_CheckMODVersion(int id)
|
||||
{
|
||||
char updatestring[500];
|
||||
const char *updatecheck = GetMODVersion(id);
|
||||
if(updatecheck)
|
||||
{
|
||||
sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck);
|
||||
#ifdef HAVE_THREADS
|
||||
I_lock_mutex(&k_menu_mutex);
|
||||
#endif
|
||||
M_StartMessage(updatestring, NULL, MM_NOTHING);
|
||||
#ifdef HAVE_THREADS
|
||||
I_unlock_mutex(k_menu_mutex);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif/*UPDATE_ALERT*/
|
||||
|
||||
#if defined (UPDATE_ALERT) && defined (HAVE_THREADS)
|
||||
static void
|
||||
Check_new_version_thread (int *id)
|
||||
{
|
||||
M_SetWaitingMode(M_WAITING_VERSION);
|
||||
|
||||
M_CheckMODVersion(*id);
|
||||
|
||||
if (Same_instance(*id))
|
||||
{
|
||||
Fetch_servers_thread(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(id);
|
||||
}
|
||||
}
|
||||
#endif/*defined (UPDATE_ALERT) && defined (HAVE_THREADS)*/
|
||||
|
||||
|
||||
// Initializes serverlist when entering the menu...
|
||||
void M_ServersMenu(INT32 choice)
|
||||
{
|
||||
(void)choice;
|
||||
// modified game check: no longer handled
|
||||
// we don't request a restart unless the filelist differs
|
||||
|
||||
mpmenu.servernum = 0;
|
||||
mpmenu.scrolln = 0;
|
||||
mpmenu.slide = 0;
|
||||
|
||||
M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false);
|
||||
itemOn = 0;
|
||||
|
||||
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
|
||||
I_lock_mutex(&ms_QueryId_mutex);
|
||||
{
|
||||
ms_QueryId++;
|
||||
}
|
||||
I_unlock_mutex(ms_QueryId_mutex);
|
||||
|
||||
I_lock_mutex(&ms_ServerList_mutex);
|
||||
{
|
||||
if (ms_ServerList)
|
||||
{
|
||||
free(ms_ServerList);
|
||||
ms_ServerList = NULL;
|
||||
}
|
||||
}
|
||||
I_unlock_mutex(ms_ServerList_mutex);
|
||||
|
||||
#ifdef UPDATE_ALERT
|
||||
Spawn_masterserver_thread("check-new-version", Check_new_version_thread);
|
||||
#else/*UPDATE_ALERT*/
|
||||
Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread);
|
||||
#endif/*UPDATE_ALERT*/
|
||||
#else/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
|
||||
#ifdef UPDATE_ALERT
|
||||
M_CheckMODVersion(0);
|
||||
#endif/*UPDATE_ALERT*/
|
||||
M_RefreshServers(0);
|
||||
#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
|
||||
|
||||
#ifdef SERVERLISTDEBUG
|
||||
M_ServerListFillDebug();
|
||||
#endif
|
||||
|
||||
M_CleanServerList();
|
||||
M_SortServerList();
|
||||
}
|
||||
|
||||
#ifdef SERVERLISTDEBUG
|
||||
|
||||
// Fill serverlist with a bunch of garbage to make our life easier in debugging
|
||||
void M_ServerListFillDebug(void)
|
||||
{
|
||||
UINT8 i = 0;
|
||||
|
||||
serverlistcount = 10;
|
||||
memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience...
|
||||
|
||||
for (i = 0; i < serverlistcount; i++)
|
||||
{
|
||||
// We don't really care about the server node for this, let's just fill in the info so that we have a visual...
|
||||
serverlist[i].info.numberofplayer = min(i, 8);
|
||||
serverlist[i].info.maxplayer = 8;
|
||||
|
||||
serverlist[i].info.avgpwrlv = P_RandomRange(PR_UNDEFINED, 500, 1500);
|
||||
serverlist[i].info.time = P_RandomRange(PR_UNDEFINED, 1, 8); // ping
|
||||
|
||||
strcpy(serverlist[i].info.servername, va("Serv %d", i+1));
|
||||
|
||||
strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle");
|
||||
|
||||
P_RandomRange(PR_UNDEFINED, 0, 5); // change results...
|
||||
serverlist[i].info.kartvars = P_RandomRange(PR_UNDEFINED, 0, 3) & SV_SPEEDMASK;
|
||||
|
||||
serverlist[i].info.modifiedgame = P_RandomRange(PR_UNDEFINED, 0, 1);
|
||||
|
||||
CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SERVERLISTDEBUG
|
||||
|
||||
// Ascending order, not descending.
|
||||
// The casts are safe as long as the caller doesn't do anything stupid.
|
||||
#define SERVER_LIST_ENTRY_COMPARATOR(key) \
|
||||
static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \
|
||||
{ \
|
||||
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
|
||||
if (sa->info.key != sb->info.key) \
|
||||
return sa->info.key - sb->info.key; \
|
||||
return strcmp(sa->info.servername, sb->info.servername); \
|
||||
}
|
||||
|
||||
// This does descending instead of ascending.
|
||||
#define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \
|
||||
static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \
|
||||
{ \
|
||||
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
|
||||
if (sb->info.key != sa->info.key) \
|
||||
return sb->info.key - sa->info.key; \
|
||||
return strcmp(sb->info.servername, sa->info.servername); \
|
||||
}
|
||||
|
||||
SERVER_LIST_ENTRY_COMPARATOR(time)
|
||||
SERVER_LIST_ENTRY_COMPARATOR(numberofplayer)
|
||||
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer)
|
||||
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer)
|
||||
SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv)
|
||||
|
||||
|
||||
static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2)
|
||||
{
|
||||
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
|
||||
int c;
|
||||
if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) ))
|
||||
return c;
|
||||
return strcmp(sa->info.servername, sb->info.servername); \
|
||||
}
|
||||
|
||||
void M_SortServerList(void)
|
||||
{
|
||||
switch(cv_serversort.value)
|
||||
{
|
||||
case 0: // Ping.
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time);
|
||||
break;
|
||||
case 1: // AVG. Power Level
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_avgpwrlv);
|
||||
break;
|
||||
case 2: // Most players.
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse);
|
||||
break;
|
||||
case 3: // Least players.
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer);
|
||||
break;
|
||||
case 4: // Max players.
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse);
|
||||
break;
|
||||
case 5: // Gametype.
|
||||
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Server browser inputs & ticker
|
||||
void M_MPServerBrowserTick(void)
|
||||
{
|
||||
mpmenu.slide /= 2;
|
||||
}
|
||||
|
||||
// Input handler for server browser.
|
||||
boolean M_ServerBrowserInputs(INT32 ch)
|
||||
{
|
||||
UINT8 pid = 0;
|
||||
UINT8 maxscroll = serverlistcount-(SERVERSPERPAGE/2);
|
||||
(void) ch;
|
||||
|
||||
if (!itemOn && menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
M_PrevOpt(); // go to itemOn 2
|
||||
if (serverlistcount)
|
||||
{
|
||||
UINT8 prevscroll = mpmenu.scrolln;
|
||||
|
||||
mpmenu.servernum = serverlistcount;
|
||||
mpmenu.scrolln = maxscroll;
|
||||
mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln);
|
||||
}
|
||||
else
|
||||
{
|
||||
itemOn = 1; // Sike! If there are no servers, go to refresh instead.
|
||||
}
|
||||
|
||||
return true; // overwrite behaviour.
|
||||
}
|
||||
else if (itemOn == 2) // server browser itself...
|
||||
{
|
||||
// we have to manually do that here.
|
||||
if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_GoBack(0);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
else if (menucmd[pid].dpad_ud > 0) // down
|
||||
{
|
||||
if (mpmenu.servernum >= serverlistcount-1)
|
||||
{
|
||||
UINT8 prevscroll = mpmenu.scrolln;
|
||||
mpmenu.servernum = 0;
|
||||
mpmenu.scrolln = 0;
|
||||
mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln);
|
||||
|
||||
M_NextOpt(); // Go back to the top of the real menu.
|
||||
}
|
||||
else
|
||||
{
|
||||
mpmenu.servernum++;
|
||||
if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2)
|
||||
{
|
||||
mpmenu.scrolln++;
|
||||
mpmenu.slide += SERVERSPACE;
|
||||
}
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
|
||||
if (!mpmenu.servernum)
|
||||
{
|
||||
M_PrevOpt();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln)
|
||||
{
|
||||
mpmenu.scrolln--;
|
||||
mpmenu.slide -= SERVERSPACE;
|
||||
}
|
||||
|
||||
mpmenu.servernum--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
}
|
||||
return true; // Overwrite behaviour.
|
||||
}
|
||||
return false; // use normal behaviour.
|
||||
}
|
||||
11
src/menus/transient/CMakeLists.txt
Normal file
11
src/menus/transient/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
cup-select.c
|
||||
explosions.c
|
||||
level-select.c
|
||||
gametype.c
|
||||
manual.c
|
||||
message-box.c
|
||||
pause-game.c
|
||||
pause-replay.c
|
||||
virtual-keyboard.c
|
||||
)
|
||||
195
src/menus/transient/cup-select.c
Normal file
195
src/menus/transient/cup-select.c
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/// \file menus/transient/cup-select.c
|
||||
/// \brief Cup Select
|
||||
|
||||
#include "../../k_menu.h"
|
||||
#include "../../s_sound.h"
|
||||
#include "../../f_finale.h" // F_WipeStartScreen
|
||||
#include "../../v_video.h"
|
||||
#include "../../k_grandprix.h"
|
||||
#include "../../r_local.h" // SplitScreen_OnChange
|
||||
|
||||
menuitem_t PLAY_CupSelect[] =
|
||||
{
|
||||
{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_CupSelectHandler}, 0, 0},
|
||||
};
|
||||
|
||||
menu_t PLAY_CupSelectDef = {
|
||||
sizeof(PLAY_CupSelect) / sizeof(menuitem_t),
|
||||
&PLAY_RaceGamemodesDef,
|
||||
0,
|
||||
PLAY_CupSelect,
|
||||
0, 0,
|
||||
0, 0,
|
||||
2, 5,
|
||||
M_DrawCupSelect,
|
||||
M_CupSelectTick,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
struct cupgrid_s cupgrid;
|
||||
|
||||
void M_CupSelectHandler(INT32 choice)
|
||||
{
|
||||
const UINT8 pid = 0;
|
||||
|
||||
(void)choice;
|
||||
|
||||
if (menucmd[pid].dpad_lr > 0)
|
||||
{
|
||||
cupgrid.x++;
|
||||
if (cupgrid.x >= CUPMENU_COLUMNS)
|
||||
{
|
||||
cupgrid.x = 0;
|
||||
cupgrid.pageno++;
|
||||
if (cupgrid.pageno >= cupgrid.numpages)
|
||||
cupgrid.pageno = 0;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_lr < 0)
|
||||
{
|
||||
cupgrid.x--;
|
||||
if (cupgrid.x < 0)
|
||||
{
|
||||
cupgrid.x = CUPMENU_COLUMNS-1;
|
||||
if (cupgrid.pageno == 0)
|
||||
cupgrid.pageno = cupgrid.numpages-1;
|
||||
else
|
||||
cupgrid.pageno--;
|
||||
}
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
if (menucmd[pid].dpad_ud > 0)
|
||||
{
|
||||
cupgrid.y++;
|
||||
if (cupgrid.y >= CUPMENU_ROWS)
|
||||
cupgrid.y = 0;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
else if (menucmd[pid].dpad_ud < 0)
|
||||
{
|
||||
cupgrid.y--;
|
||||
if (cupgrid.y < 0)
|
||||
cupgrid.y = CUPMENU_ROWS-1;
|
||||
S_StartSound(NULL, sfx_s3k5b);
|
||||
M_SetMenuDelay(pid);
|
||||
}
|
||||
|
||||
if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/)
|
||||
{
|
||||
INT16 count;
|
||||
cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID];
|
||||
cupheader_t *oldcup = levellist.levelsearch.cup;
|
||||
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
levellist.levelsearch.cup = newcup;
|
||||
count = M_CountLevelsToShowInList(&levellist.levelsearch);
|
||||
|
||||
if ((!newcup)
|
||||
|| (count <= 0)
|
||||
|| (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID))
|
||||
{
|
||||
S_StartSound(NULL, sfx_s3kb2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cupgrid.grandprix == true)
|
||||
{
|
||||
INT32 levelNum;
|
||||
UINT8 ssplayers = cv_splitplayers.value-1;
|
||||
|
||||
S_StartSound(NULL, sfx_s3k63);
|
||||
|
||||
// Early fadeout to let the sound finish playing
|
||||
F_WipeStartScreen();
|
||||
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
|
||||
F_WipeEndScreen();
|
||||
F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false);
|
||||
|
||||
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
|
||||
|
||||
if (cv_maxconnections.value < ssplayers+1)
|
||||
CV_SetValue(&cv_maxconnections, ssplayers+1);
|
||||
|
||||
if (splitscreen != ssplayers)
|
||||
{
|
||||
splitscreen = ssplayers;
|
||||
SplitScreen_OnChange();
|
||||
}
|
||||
|
||||
// read our dummy cvars
|
||||
|
||||
grandprixinfo.gamespeed = min(KARTSPEED_HARD, cv_dummygpdifficulty.value);
|
||||
grandprixinfo.masterbots = (cv_dummygpdifficulty.value == 3);
|
||||
grandprixinfo.encore = (boolean)cv_dummygpencore.value;
|
||||
|
||||
grandprixinfo.cup = newcup;
|
||||
|
||||
grandprixinfo.gp = true;
|
||||
grandprixinfo.roundnum = 1;
|
||||
grandprixinfo.initalize = true;
|
||||
|
||||
paused = false;
|
||||
|
||||
// Don't restart the server if we're already in a game lol
|
||||
if (gamestate == GS_MENU)
|
||||
{
|
||||
SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame);
|
||||
}
|
||||
|
||||
levelNum = grandprixinfo.cup->cachedlevels[0];
|
||||
|
||||
D_MapChange(
|
||||
levelNum + 1,
|
||||
GT_RACE,
|
||||
grandprixinfo.encore,
|
||||
true,
|
||||
1,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
else if (count == 1)
|
||||
{
|
||||
PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1;
|
||||
M_LevelSelected(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep cursor position if you select the same cup again, reset if it's a different cup
|
||||
if (oldcup != newcup || levellist.cursor >= count)
|
||||
{
|
||||
levellist.cursor = 0;
|
||||
}
|
||||
|
||||
M_LevelSelectScrollDest();
|
||||
levellist.y = levellist.dest;
|
||||
|
||||
M_SetupNextMenu(&PLAY_LevelSelectDef, false);
|
||||
S_StartSound(NULL, sfx_s3k63);
|
||||
}
|
||||
}
|
||||
else if (M_MenuBackPressed(pid))
|
||||
{
|
||||
M_SetMenuDelay(pid);
|
||||
|
||||
if (currentMenu->prevMenu)
|
||||
M_SetupNextMenu(currentMenu->prevMenu, false);
|
||||
else
|
||||
M_ClearMenus(true);
|
||||
}
|
||||
}
|
||||
|
||||
void M_CupSelectTick(void)
|
||||
{
|
||||
cupgrid.previewanim++;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue