diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dfd8e6f9..151a7a389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,9 +130,7 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}") find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(SDL2 REQUIRED) - find_package(SDL2_mixer REQUIRED) find_package(CURL REQUIRED) - find_package(OPENMPT REQUIRED) find_package(GME REQUIRED) endif() diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake index 3859c2aad..f50faf933 100644 --- a/cmake/Comptime.cmake +++ b/cmake/Comptime.cmake @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + set(CMAKE_BINARY_DIR "${BINARY_DIR}") set(CMAKE_CURRENT_BINARY_DIR "${BINARY_DIR}") @@ -7,7 +9,23 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo include(GitUtilities) git_current_branch(SRB2_COMP_BRANCH) -git_summary(SRB2_COMP_REVISION) git_working_tree_dirty(SRB2_COMP_UNCOMMITTED) +git_summary(revision) +string(REGEX REPLACE "([\"\\])" "\\\\\\1" SRB2_COMP_REVISION "${revision}") + +if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE None) +endif() + +# These build types enable optimizations of some kind by default. +set(optimized_build_types "MINSIZEREL;RELEASE;RELWITHDEBINFO") + +string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) +if("${build_type}" IN_LIST optimized_build_types) + set(SRB2_COMP_OPTIMIZED TRUE) +else() + set(SRB2_COMP_OPTIMIZED FALSE) +endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") diff --git a/cmake/Modules/FindOPENMPT.cmake b/cmake/Modules/FindOPENMPT.cmake deleted file mode 100644 index 7e5b2d5a3..000000000 --- a/cmake/Modules/FindOPENMPT.cmake +++ /dev/null @@ -1,33 +0,0 @@ -include(LibFindMacros) - -libfind_pkg_check_modules(OPENMPT_PKGCONF OPENMPT) - -find_path(OPENMPT_INCLUDE_DIR - NAMES libopenmpt.h - PATHS - ${OPENMPT_PKGCONF_INCLUDE_DIRS} - "/usr/include/libopenmpt" - "/usr/local/include/libopenmpt" -) - -find_library(OPENMPT_LIBRARY - NAMES openmpt - PATHS - ${OPENMPT_PKGCONF_LIBRARY_DIRS} - "/usr/lib" - "/usr/local/lib" -) - -set(OPENMPT_PROCESS_INCLUDES OPENMPT_INCLUDE_DIR) -set(OPENMPT_PROCESS_LIBS OPENMPT_LIBRARY) -libfind_process(OPENMPT) - -if(OPENMPT_FOUND AND NOT TARGET openmpt) - add_library(openmpt UNKNOWN IMPORTED) - set_target_properties( - openmpt - PROPERTIES - IMPORTED_LOCATION "${OPENMPT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${OPENMPT_INCLUDE_DIR}" - ) -endif() diff --git a/cmake/Modules/FindSDL2_mixer.cmake b/cmake/Modules/FindSDL2_mixer.cmake deleted file mode 100644 index 637498e53..000000000 --- a/cmake/Modules/FindSDL2_mixer.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# Find SDL2 -# Once done, this will define -# -# SDL2_MIXER_FOUND - system has SDL2 -# SDL2_MIXER_INCLUDE_DIRS - SDL2 include directories -# SDL2_MIXER_LIBRARIES - link libraries - -include(LibFindMacros) - -libfind_pkg_check_modules(SDL2_MIXER_PKGCONF SDL2_mixer) - -# includes -find_path(SDL2_MIXER_INCLUDE_DIR - NAMES SDL_mixer.h - PATHS - ${SDL2_MIXER_PKGCONF_INCLUDE_DIRS} - "/usr/include/SDL2" - "/usr/local/include/SDL2" -) - -# library -find_library(SDL2_MIXER_LIBRARY - NAMES SDL2_mixer - PATHS - ${SDL2_MIXER_PKGCONF_LIBRARY_DIRS} - "/usr/lib" - "/usr/local/lib" -) - - -# set include dir variables -set(SDL2_MIXER_PROCESS_INCLUDES SDL2_MIXER_INCLUDE_DIR) -set(SDL2_MIXER_PROCESS_LIBS SDL2_MIXER_LIBRARY) -libfind_process(SDL2_MIXER) - -if(SDL2_MIXER_FOUND AND NOT TARGET SDL2_mixer::SDL2_mixer) - add_library(SDL2_mixer::SDL2_mixer UNKNOWN IMPORTED) - set_target_properties( - SDL2_mixer::SDL2_mixer - PROPERTIES - IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" - ) -endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 283749c1b..1477b6f6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,7 +154,7 @@ add_custom_command( # build time for accurate git information and before anything # that needs it, obviously. add_custom_target(_SRB2_reconf ALL - COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake + COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." ) add_dependencies(SRB2SDL2 _SRB2_reconf) @@ -214,9 +214,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") target_include_directories(SRB2SDL2 PRIVATE "${libgme_SOURCE_DIR}") endif() -target_link_libraries(SRB2SDL2 PRIVATE openmpt) -target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_OPENMPT) - target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB PNG::PNG CURL::libcurl) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB -DHAVE_PNG -DHAVE_CURL -D_LARGEFILE64_SOURCE) target_sources(SRB2SDL2 PRIVATE apng.c) @@ -226,6 +223,9 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN) 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 acsvm::acsvm) set(SRB2_HAVE_THREADS ON) @@ -312,15 +312,6 @@ if("${SRB2_CONFIG_HWRENDER}") endif() endif() -# TODO: build this with the game -if(${CMAKE_SYSTEM} MATCHES Windows AND ${CMAKE_C_COMPILER_ID} MATCHES "GNU" AND ${SRB2_SYSTEM_BITS} EQUAL 32) - target_link_libraries(SRB2SDL2 PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libexchndl.a" - "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libmgwhelp.a" - ) - target_include_directories(SRB2SDL2 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/include") -endif() - if(${SRB2_CONFIG_USEASM}) #SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm. if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") @@ -403,7 +394,7 @@ target_compile_options(SRB2SDL2 PRIVATE -Winline -Wformat-y2k -Wformat-security - + $<$,2.9.5>: -Wno-div-by-zero -Wendif-labels @@ -539,6 +530,7 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") target_link_options(SRB2SDL2 PRIVATE -pg) endif() +add_subdirectory(audio) add_subdirectory(io) add_subdirectory(sdl) add_subdirectory(objects) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt new file mode 100644 index 000000000..0f35daf91 --- /dev/null +++ b/src/audio/CMakeLists.txt @@ -0,0 +1,37 @@ +target_sources(SRB2SDL2 PRIVATE + chunk_load.cpp + chunk_load.hpp + expand_mono.cpp + expand_mono.hpp + filter.cpp + filter.hpp + gain.cpp + gain.hpp + gme_player.cpp + gme_player.hpp + gme.cpp + gme.hpp + mixer.cpp + mixer.hpp + music_player.cpp + music_player.hpp + ogg_player.cpp + ogg_player.hpp + ogg.cpp + ogg.hpp + resample.cpp + resample.hpp + sample.hpp + sound_chunk.hpp + sound_effect_player.cpp + sound_effect_player.hpp + source.hpp + wav_player.cpp + wav_player.hpp + wav.cpp + wav.hpp + xmp_player.cpp + xmp_player.hpp + xmp.cpp + xmp.hpp +) diff --git a/src/audio/chunk_load.cpp b/src/audio/chunk_load.cpp new file mode 100644 index 000000000..79900ec17 --- /dev/null +++ b/src/audio/chunk_load.cpp @@ -0,0 +1,206 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "chunk_load.hpp" + +#include + +#include "../cxxutil.hpp" +#include "../io/streams.hpp" +#include "gme.hpp" +#include "gme_player.hpp" +#include "ogg.hpp" +#include "ogg_player.hpp" +#include "resample.hpp" +#include "sound_chunk.hpp" +#include "sound_effect_player.hpp" +#include "wav.hpp" +#include "wav_player.hpp" + +using std::nullopt; +using std::optional; +using std::size_t; + +using namespace srb2::audio; +using namespace srb2; + +namespace { + +// Utility for leveraging Resampler... +class SoundChunkSource : public Source<1> { +public: + explicit SoundChunkSource(std::unique_ptr&& chunk) + : chunk_(std::forward>(chunk)) {} + + virtual size_t generate(tcb::span> buffer) override final { + if (!chunk_) + return 0; + + size_t written = 0; + for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) { + buffer[written] = chunk_->samples[pos_]; + written++; + } + return written; + } + +private: + std::unique_ptr chunk_; + size_t pos_ {0}; +}; + +template +std::vector> generate_to_vec(I& source, std::size_t estimate = 0) { + std::vector> generated; + + size_t total = 0; + size_t read = 0; + generated.reserve(estimate); + do { + generated.resize(total + 4096); + read = source.generate(tcb::span {generated.data() + total, 4096}); + total += read; + } while (read != 0); + generated.resize(total); + return generated; +} + +optional try_load_dmx(tcb::span data) { + io::SpanStream stream {data}; + + if (io::remaining(stream) < 8) + return nullopt; + + uint16_t version = io::read_uint16(stream); + if (version != 3) + return nullopt; + + uint16_t rate = io::read_uint16(stream); + uint32_t length = io::read_uint32(stream) - 32u; + + if (io::remaining(stream) < (length + 32u)) + return nullopt; + + stream.seek(io::SeekFrom::kCurrent, 16); + + std::vector> samples; + 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) { + return SoundChunk {samples}; + } + + std::unique_ptr chunk_source = + std::make_unique(std::make_unique(SoundChunk {std::move(samples)})); + Resampler<1> resampler(std::move(chunk_source), rate / static_cast(kSampleRate)); + + std::vector> resampled; + + size_t total = 0; + size_t read = 0; + resampled.reserve(samples_len * (static_cast(kSampleRate) / rate)); + do { + resampled.resize(total + 4096); + read = resampler.generate(tcb::span {resampled.data() + total, 4096}); + total += read; + } while (read != 0); + resampled.resize(total); + + return SoundChunk {std::move(resampled)}; +} + +optional try_load_wav(tcb::span data) { + io::SpanStream stream {data}; + + audio::Wav wav; + std::size_t sample_rate; + + try { + wav = audio::load_wav(stream); + } catch (const std::exception& ex) { + return nullopt; + } + + sample_rate = wav.sample_rate(); + + audio::Resampler<1> resampler(std::make_unique(std::move(wav)), + sample_rate / static_cast(kSampleRate)); + + SoundChunk chunk {generate_to_vec(resampler)}; + return chunk; +} + +optional try_load_ogg(tcb::span data) { + std::shared_ptr> player; + try { + io::SpanStream data_stream {data}; + audio::Ogg ogg = audio::load_ogg(data_stream); + player = std::make_shared>(std::move(ogg)); + } catch (...) { + return nullopt; + } + player->looping(false); + player->playing(true); + player->reset(); + std::size_t sample_rate = player->sample_rate(); + audio::Resampler<1> resampler(player, sample_rate / 44100.); + std::vector> resampled {generate_to_vec(resampler)}; + + SoundChunk chunk {std::move(resampled)}; + return chunk; +} + +optional try_load_gme(tcb::span data) { + std::shared_ptr> player; + 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>(std::move(gme)); + } else { + io::ZlibInputStream stream {io::SpanStream(data)}; + audio::Gme gme = audio::load_gme(stream); + player = std::make_shared>(std::move(gme)); + } + } catch (...) { + return nullopt; + } + std::vector> samples {generate_to_vec(*player)}; + SoundChunk chunk {std::move(samples)}; + return chunk; +} + +} // namespace + +optional srb2::audio::try_load_chunk(tcb::span data) { + optional ret; + + ret = try_load_dmx(data); + if (ret) + return ret; + + ret = try_load_wav(data); + if (ret) + return ret; + + ret = try_load_ogg(data); + if (ret) + return ret; + + ret = try_load_gme(data); + if (ret) + return ret; + + return nullopt; +} diff --git a/src/audio/chunk_load.hpp b/src/audio/chunk_load.hpp new file mode 100644 index 000000000..c97d559d2 --- /dev/null +++ b/src/audio/chunk_load.hpp @@ -0,0 +1,27 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_CHUNK_LOAD_HPP__ +#define __SRB2_AUDIO_CHUNK_LOAD_HPP__ + +#include +#include + +#include + +#include "sound_chunk.hpp" + +namespace srb2::audio { + +/// @brief Try to load a chunk from the given byte span. +std::optional try_load_chunk(tcb::span data); + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_CHUNK_LOAD_HPP__ diff --git a/src/audio/expand_mono.cpp b/src/audio/expand_mono.cpp new file mode 100644 index 000000000..ce7cf0dc3 --- /dev/null +++ b/src/audio/expand_mono.cpp @@ -0,0 +1,26 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "expand_mono.hpp" + +#include + +using std::size_t; + +using namespace srb2::audio; + +ExpandMono::~ExpandMono() = default; + +size_t ExpandMono::filter(tcb::span> input_buffer, tcb::span> 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]; + } + return std::min(input_buffer.size(), buffer.size()); +} diff --git a/src/audio/expand_mono.hpp b/src/audio/expand_mono.hpp new file mode 100644 index 000000000..f3686704e --- /dev/null +++ b/src/audio/expand_mono.hpp @@ -0,0 +1,27 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_EXPAND_MONO_HPP__ +#define __SRB2_AUDIO_EXPAND_MONO_HPP__ + +#include + +#include "filter.hpp" + +namespace srb2::audio { + +class ExpandMono : public Filter<1, 2> { +public: + virtual ~ExpandMono(); + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) override final; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_EXPAND_MONO_HPP__ diff --git a/src/audio/filter.cpp b/src/audio/filter.cpp new file mode 100644 index 000000000..8bb09bdfb --- /dev/null +++ b/src/audio/filter.cpp @@ -0,0 +1,40 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "filter.hpp" + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Filter; +using srb2::audio::Sample; +using srb2::audio::Source; + +template +size_t Filter::generate(tcb::span> buffer) { + input_buffer_.clear(); + input_buffer_.resize(buffer.size()); + + input_->generate(input_buffer_); + + return filter(input_buffer_, buffer); +} + +template +void Filter::bind(const shared_ptr>& input) { + input_ = input; +} + +template +Filter::~Filter() = default; + +template class srb2::audio::Filter<1, 1>; +template class srb2::audio::Filter<1, 2>; +template class srb2::audio::Filter<2, 1>; +template class srb2::audio::Filter<2, 2>; diff --git a/src/audio/filter.hpp b/src/audio/filter.hpp new file mode 100644 index 000000000..59dce6e1e --- /dev/null +++ b/src/audio/filter.hpp @@ -0,0 +1,46 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_FILTER_HPP__ +#define __SRB2_AUDIO_FILTER_HPP__ + +#include +#include +#include + +#include + +#include "source.hpp" + +namespace srb2::audio { + +template +class Filter : public Source { +public: + virtual std::size_t generate(tcb::span> buffer) override; + + void bind(const std::shared_ptr>& input); + + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) = 0; + + virtual ~Filter(); + +private: + std::shared_ptr> input_; + std::vector> input_buffer_; +}; + +extern template class Filter<1, 1>; +extern template class Filter<1, 2>; +extern template class Filter<2, 1>; +extern template class Filter<2, 2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_FILTER_HPP__ diff --git a/src/audio/gain.cpp b/src/audio/gain.cpp new file mode 100644 index 000000000..59839bb69 --- /dev/null +++ b/src/audio/gain.cpp @@ -0,0 +1,43 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gain.hpp" + +#include + +using std::size_t; + +using srb2::audio::Filter; +using srb2::audio::Gain; +using srb2::audio::Sample; + +constexpr const float kGainInterpolationAlpha = 0.8f; + +template +size_t Gain::filter(tcb::span> input_buffer, tcb::span> buffer) { + size_t written = std::min(buffer.size(), input_buffer.size()); + for (size_t i = 0; i < written; i++) { + buffer[i] = input_buffer[i]; + buffer[i] *= gain_; + gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha; + } + + return written; +} + +template +void Gain::gain(float new_gain) { + new_gain_ = std::clamp(new_gain, 0.0f, 1.0f); +} + +template +Gain::~Gain() = default; + +template class srb2::audio::Gain<1>; +template class srb2::audio::Gain<2>; diff --git a/src/audio/gain.hpp b/src/audio/gain.hpp new file mode 100644 index 000000000..ef4dd0d53 --- /dev/null +++ b/src/audio/gain.hpp @@ -0,0 +1,33 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GAIN_HPP__ +#define __SRB2_AUDIO_GAIN_HPP__ + +#include + +#include "filter.hpp" + +namespace srb2::audio { + +template +class Gain : public Filter { +public: + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) override final; + void gain(float new_gain); + + virtual ~Gain(); + +private: + float new_gain_ {1.f}; + float gain_ {1.f}; +}; +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GAIN_HPP__ diff --git a/src/audio/gme.cpp b/src/audio/gme.cpp new file mode 100644 index 000000000..38279dd98 --- /dev/null +++ b/src/audio/gme.cpp @@ -0,0 +1,141 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gme.hpp" + +#include +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +Gme::Gme() : 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&& data) : memory_data_(std::move(data)), instance_(nullptr) { + _init_with_data(); +} + +Gme::Gme(tcb::span data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { + _init_with_data(); +} + +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_delete(instance_); + instance_ = nullptr; + } +} + +std::size_t Gme::get_samples(tcb::span buffer) { + SRB2_ASSERT(instance_ != nullptr); + + gme_err_t err = gme_play(instance_, buffer.size(), buffer.data()); + if (err) + throw GmeException(err); + + return buffer.size(); +} + +void Gme::seek(int sample) { + SRB2_ASSERT(instance_ != nullptr); + + gme_seek_samples(instance_, sample); +} + +std::optional Gme::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + if (info->length == -1) + return std::nullopt; + + // info lengths are in ms + return static_cast(info->length) / 1000.f; +} + +std::optional Gme::loop_point_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + int loop_point_ms = info->intro_length; + if (loop_point_ms == -1) + return std::nullopt; + + return loop_point_ms / 44100.f; +} + +float Gme::position_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + int position = gme_tell(instance_); + + // adjust position, since GME's counter keeps going past loop + if (info->length > 0) + position %= info->length; + else if (info->intro_length + info->loop_length > 0) + position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position; + else + position %= 150 * 1000; // 2.5 minutes + + return position / 1000.f; +} + +void Gme::_init_with_data() { + if (instance_) { + return; + } + + if (memory_data_.size() >= std::numeric_limits::max()) + throw std::invalid_argument("Buffer is too large for gme"); + if (memory_data_.size() == 0) + throw std::invalid_argument("Insufficient data from stream"); + + gme_err_t result = + gme_open_data(reinterpret_cast(memory_data_.data()), memory_data_.size(), &instance_, 44100); + if (result) + throw GmeException(result); + + // we no longer need the data, so there's no reason to keep the allocation + memory_data_ = std::vector(); + + result = gme_start_track(instance_, 0); + if (result) + throw GmeException(result); +} diff --git a/src/audio/gme.hpp b/src/audio/gme.hpp new file mode 100644 index 000000000..34f2c2769 --- /dev/null +++ b/src/audio/gme.hpp @@ -0,0 +1,74 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GME_HPP__ +#define __SRB2_AUDIO_GME_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#undef byte // BLARGG!! NO!! +#undef check // STOP IT!!!! + +#include "../io/streams.hpp" + +namespace srb2::audio { + +class GmeException : public std::exception { + std::string msg_; + +public: + explicit GmeException(gme_err_t msg) : msg_(msg == nullptr ? "" : msg) {} + + virtual const char* what() const noexcept override { return msg_.c_str(); } +}; + +class Gme { + std::vector memory_data_; + Music_Emu* instance_; + +public: + Gme(); + Gme(const Gme&) = delete; + Gme(Gme&& rhs) noexcept; + + Gme& operator=(const Gme&) = delete; + Gme& operator=(Gme&& rhs) noexcept; + + explicit Gme(std::vector&& data); + explicit Gme(tcb::span data); + + std::size_t get_samples(tcb::span buffer); + void seek(int sample); + + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + ~Gme(); + +private: + void _init_with_data(); +}; + +template , int> = 0> +inline Gme load_gme(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Gme {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GME_HPP__ diff --git a/src/audio/gme_player.cpp b/src/audio/gme_player.cpp new file mode 100644 index 000000000..229c99676 --- /dev/null +++ b/src/audio/gme_player.cpp @@ -0,0 +1,73 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gme_player.hpp" + +using namespace srb2; +using namespace srb2::audio; + +template +GmePlayer::GmePlayer(Gme&& gme) : gme_(std::forward(gme)), buf_() { +} + +template +GmePlayer::GmePlayer(GmePlayer&& rhs) noexcept = default; + +template +GmePlayer& GmePlayer::operator=(GmePlayer&& rhs) noexcept = default; + +template +GmePlayer::~GmePlayer() = default; + +template +std::size_t GmePlayer::generate(tcb::span> 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) { + buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f; + } else if constexpr (C == 2) { + buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f; + buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f; + } + } + return new_samples; +} + +template +void GmePlayer::seek(float position_seconds) { + gme_.seek(static_cast(position_seconds * 44100.f)); +} + +template +void GmePlayer::reset() { + gme_.seek(0); +} + +template +std::optional GmePlayer::duration_seconds() const { + return gme_.duration_seconds(); +} + +template +std::optional GmePlayer::loop_point_seconds() const { + return gme_.loop_point_seconds(); +} + +template +float GmePlayer::position_seconds() const { + return gme_.position_seconds(); +} + +template class srb2::audio::GmePlayer<1>; +template class srb2::audio::GmePlayer<2>; diff --git a/src/audio/gme_player.hpp b/src/audio/gme_player.hpp new file mode 100644 index 000000000..a2f555c08 --- /dev/null +++ b/src/audio/gme_player.hpp @@ -0,0 +1,51 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GME_PLAYER_HPP__ +#define __SRB2_AUDIO_GME_PLAYER_HPP__ + +#include + +#include "gme.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class GmePlayer : public Source { + Gme gme_; + std::vector buf_; + +public: + GmePlayer(Gme&& gme); + GmePlayer(const GmePlayer&) = delete; + GmePlayer(GmePlayer&& gme) noexcept; + + ~GmePlayer(); + + GmePlayer& operator=(const GmePlayer&) = delete; + GmePlayer& operator=(GmePlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override; + + void seek(float position_seconds); + + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + void reset(); +}; + +extern template class GmePlayer<1>; +extern template class GmePlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GME_PLAYER_HPP__ diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp new file mode 100644 index 000000000..d4b718ffa --- /dev/null +++ b/src/audio/mixer.cpp @@ -0,0 +1,62 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "mixer.hpp" + +#include + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Mixer; +using srb2::audio::Sample; +using srb2::audio::Source; + +namespace { + +template +void default_init_sample_buffer(Sample* buffer, size_t size) { + std::for_each(buffer, buffer + size, [](auto& i) { i = Sample {}; }); +} + +template +void mix_sample_buffers(Sample* dst, size_t size, Sample* src, size_t src_size) { + for (size_t i = 0; i < size && i < src_size; i++) { + dst[i] += src[i]; + } +} + +} // namespace + +template +size_t Mixer::generate(tcb::span> buffer) { + buffer_.resize(buffer.size()); + + default_init_sample_buffer(buffer.data(), buffer.size()); + + for (auto& source : sources_) { + size_t read = source->generate(buffer_); + + mix_sample_buffers(buffer.data(), buffer.size(), buffer_.data(), read); + } + + // because we initialized the out-buffer, we always generate size samples + return buffer.size(); +} + +template +void Mixer::add_source(const shared_ptr>& source) { + sources_.push_back(source); +} + +template +Mixer::~Mixer() = default; + +template class srb2::audio::Mixer<1>; +template class srb2::audio::Mixer<2>; diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp new file mode 100644 index 000000000..75c8ba2f9 --- /dev/null +++ b/src/audio/mixer.hpp @@ -0,0 +1,41 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_MIXER_HPP__ +#define __SRB2_AUDIO_MIXER_HPP__ + +#include +#include + +#include + +#include "source.hpp" + +namespace srb2::audio { + +template +class Mixer : public Source { +public: + virtual std::size_t generate(tcb::span> buffer) override final; + + virtual ~Mixer(); + + void add_source(const std::shared_ptr>& source); + +private: + std::vector>> sources_; + std::vector> buffer_; +}; + +extern template class Mixer<1>; +extern template class Mixer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_MIXER_HPP__ diff --git a/src/audio/music_player.cpp b/src/audio/music_player.cpp new file mode 100644 index 000000000..bb85a01de --- /dev/null +++ b/src/audio/music_player.cpp @@ -0,0 +1,422 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "music_player.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#undef byte // BLARGG!! NO!! +#undef check // STOP IT!!!! + +#include "../cxxutil.hpp" +#include "../io/streams.hpp" +#include "gme_player.hpp" +#include "ogg_player.hpp" +#include "resample.hpp" +#include "xmp_player.hpp" + +using std::array; +using std::byte; +using std::make_unique; +using std::size_t; +using std::vector; + +using srb2::audio::MusicPlayer; +using srb2::audio::Resampler; +using srb2::audio::Sample; +using srb2::audio::Source; +using namespace srb2; + +class MusicPlayer::Impl { +public: + Impl() = default; + Impl(tcb::span data) : Impl() { _load(data); } + + size_t generate(tcb::span> buffer) { + if (!resampler_) + return 0; + + if (!playing_) + return 0; + + size_t total_written = 0; + + 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++) { + const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) / + static_cast(gain_samples_target_); + const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_; + buffer[total_written + i] *= fade_gain; + } + + gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_); + + if (gain_samples_ >= gain_samples_target_) { + fading_ = false; + gain_samples_ = gain_samples_target_; + gain_ = gain_target_; + } + + total_written += generated; + + if (generated == 0) { + playing_ = false; + break; + } + } + + return total_written; + } + + void _load(tcb::span data) { + ogg_inst_ = nullptr; + gme_inst_ = nullptr; + xmp_inst_ = nullptr; + resampler_ = std::nullopt; + + try { + io::SpanStream stream {data}; + audio::Ogg ogg = audio::load_ogg(stream); + ogg_inst_ = std::make_shared>(std::move(ogg)); + ogg_inst_->looping(looping_); + resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f); + } 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}) { + io::ZlibInputStream stream {io::SpanStream(data)}; + audio::Gme gme = audio::load_gme(stream); + gme_inst_ = std::make_shared>(std::move(gme)); + } else { + io::SpanStream stream {data}; + audio::Gme gme = audio::load_gme(stream); + gme_inst_ = std::make_shared>(std::move(gme)); + } + + resampler_ = Resampler<2>(gme_inst_, 1.f); + } catch (const std::exception& ex) { + // it's probably not gme + gme_inst_ = nullptr; + resampler_ = std::nullopt; + } + } + + if (!resampler_) { + try { + io::SpanStream stream {data}; + audio::Xmp<2> xmp = audio::load_xmp<2>(stream); + xmp_inst_ = std::make_shared>(std::move(xmp)); + xmp_inst_->looping(looping_); + + resampler_ = Resampler<2>(xmp_inst_, 1.f); + } catch (const std::exception& ex) { + // it's probably not xmp + xmp_inst_ = nullptr; + resampler_ = std::nullopt; + } + } + + playing_ = false; + + internal_gain(1.f); + } + + void play(bool looping) { + if (ogg_inst_) { + ogg_inst_->looping(looping); + ogg_inst_->playing(true); + playing_ = true; + ogg_inst_->reset(); + } else if (gme_inst_) { + playing_ = true; + gme_inst_->reset(); + } else if (xmp_inst_) { + xmp_inst_->looping(looping); + playing_ = true; + xmp_inst_->reset(); + } + } + + void unpause() { + if (ogg_inst_) { + ogg_inst_->playing(true); + playing_ = true; + } else if (gme_inst_) { + playing_ = true; + } else if (xmp_inst_) { + playing_ = true; + } + } + + void pause() { + if (ogg_inst_) { + ogg_inst_->playing(false); + playing_ = false; + } else if (gme_inst_) { + playing_ = false; + } else if (xmp_inst_) { + playing_ = false; + } + } + + void stop() { + if (ogg_inst_) { + ogg_inst_->reset(); + ogg_inst_->playing(false); + playing_ = false; + } else if (gme_inst_) { + gme_inst_->reset(); + playing_ = false; + } else if (xmp_inst_) { + xmp_inst_->reset(); + playing_ = false; + } + } + + void seek(float position_seconds) { + if (ogg_inst_) { + ogg_inst_->seek(position_seconds); + return; + } + if (gme_inst_) { + gme_inst_->seek(position_seconds); + return; + } + if (xmp_inst_) { + xmp_inst_->seek(position_seconds); + return; + } + } + + bool playing() const { + if (ogg_inst_) + return ogg_inst_->playing(); + else if (gme_inst_) + return playing_; + else if (xmp_inst_) + return playing_; + + return false; + } + + std::optional music_type() const { + if (ogg_inst_) + return audio::MusicType::kOgg; + else if (gme_inst_) + return audio::MusicType::kGme; + else if (xmp_inst_) + return audio::MusicType::kMod; + + return std::nullopt; + } + + std::optional duration_seconds() const { + if (ogg_inst_) + return ogg_inst_->duration_seconds(); + if (gme_inst_) + return gme_inst_->duration_seconds(); + if (xmp_inst_) + return xmp_inst_->duration_seconds(); + + return std::nullopt; + } + + std::optional loop_point_seconds() const { + if (ogg_inst_) + return ogg_inst_->loop_point_seconds(); + if (gme_inst_) + return gme_inst_->loop_point_seconds(); + + return std::nullopt; + } + + std::optional position_seconds() const { + if (ogg_inst_) + return ogg_inst_->position_seconds(); + if (gme_inst_) + return gme_inst_->position_seconds(); + + return std::nullopt; + } + + void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, 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(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal + gain_samples_ = 0; + } + + bool fading() const { return fading_; } + + void stop_fade() { internal_gain(gain_target_); } + + void loop_point_seconds(float loop_point) { + if (ogg_inst_) + ogg_inst_->loop_point_seconds(loop_point); + } + + void internal_gain(float gain) { + fading_ = false; + gain_ = gain; + gain_target_ = gain; + gain_samples_target_ = 1; + gain_samples_ = 0; + } + +private: + std::shared_ptr> ogg_inst_; + std::shared_ptr> gme_inst_; + std::shared_ptr> xmp_inst_; + std::optional> resampler_; + bool playing_ {false}; + bool looping_ {false}; + + // fade control + float gain_target_ {1.f}; + float gain_ {1.f}; + bool fading_ {false}; + uint64_t gain_samples_ {0}; + uint64_t gain_samples_target_ {1}; +}; + +// The special member functions MUST be declared in this unit, where Impl is complete. +MusicPlayer::MusicPlayer() : impl_(make_unique()) { +} +MusicPlayer::MusicPlayer(tcb::span data) : impl_(make_unique(data)) { +} +MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default; +MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default; + +MusicPlayer::~MusicPlayer() = default; + +void MusicPlayer::play(bool looping) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->play(looping); +} + +void MusicPlayer::unpause() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->unpause(); +} + +void MusicPlayer::pause() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->pause(); +} + +void MusicPlayer::stop() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->stop(); +} + +void MusicPlayer::seek(float position_seconds) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->seek(position_seconds); +} + +bool MusicPlayer::playing() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->playing(); +} + +size_t MusicPlayer::generate(tcb::span> buffer) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->generate(buffer); +} + +std::optional MusicPlayer::music_type() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->music_type(); +} + +std::optional MusicPlayer::duration_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->duration_seconds(); +} + +std::optional MusicPlayer::loop_point_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->loop_point_seconds(); +} + +std::optional MusicPlayer::position_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->position_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) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->fade_from_to(from, to, seconds); +} + +void MusicPlayer::internal_gain(float gain) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->internal_gain(gain); +} + +bool MusicPlayer::fading() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->fading(); +} + +void MusicPlayer::stop_fade() { + SRB2_ASSERT(impl_ != nullptr); + + impl_->stop_fade(); +} + +void MusicPlayer::loop_point_seconds(float loop_point) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->loop_point_seconds(loop_point); +} diff --git a/src/audio/music_player.hpp b/src/audio/music_player.hpp new file mode 100644 index 000000000..668724fa0 --- /dev/null +++ b/src/audio/music_player.hpp @@ -0,0 +1,69 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_MUSIC_PLAYER_HPP__ +#define __SRB2_AUDIO_MUSIC_PLAYER_HPP__ + +#include +#include + +#include + +#include "source.hpp" + +struct stb_vorbis; + +namespace srb2::audio { + +enum class MusicType { + kOgg, + kGme, + kMod +}; + +class MusicPlayer : public Source<2> { +public: + MusicPlayer(); + MusicPlayer(tcb::span data); + MusicPlayer(const MusicPlayer& rhs) = delete; + MusicPlayer(MusicPlayer&& rhs) noexcept; + + MusicPlayer& operator=(const MusicPlayer& rhs) = delete; + MusicPlayer& operator=(MusicPlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override final; + + void play(bool looping); + void unpause(); + void pause(); + void stop(); + void seek(float position_seconds); + void fade_to(float gain, float seconds); + void fade_from_to(float from, float to, float seconds); + void internal_gain(float gain); + void stop_fade(); + void loop_point_seconds(float loop_point); + bool playing() const; + std::optional music_type() const; + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + std::optional position_seconds() const; + bool fading() const; + + virtual ~MusicPlayer() final; + +private: + class Impl; + + std::unique_ptr impl_; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_MUSIC_PLAYER_HPP__ diff --git a/src/audio/ogg.cpp b/src/audio/ogg.cpp new file mode 100644 index 000000000..388fd37fe --- /dev/null +++ b/src/audio/ogg.cpp @@ -0,0 +1,194 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "ogg.hpp" + +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +StbVorbisException::StbVorbisException(int code) noexcept : code_(code) { +} + +const char* StbVorbisException::what() const noexcept { + switch (code_) { + case VORBIS__no_error: + return "No error"; + case VORBIS_need_more_data: + return "Need more data"; + case VORBIS_invalid_api_mixing: + return "Invalid API mixing"; + case VORBIS_outofmem: + return "Out of memory"; + case VORBIS_feature_not_supported: + return "Feature not supported"; + case VORBIS_too_many_channels: + return "Too many channels"; + case VORBIS_file_open_failure: + return "File open failure"; + case VORBIS_seek_without_length: + return "Seek without length"; + case VORBIS_unexpected_eof: + return "Unexpected EOF"; + case VORBIS_seek_invalid: + return "Seek invalid"; + case VORBIS_invalid_setup: + return "Invalid setup"; + case VORBIS_invalid_stream: + return "Invalid stream"; + case VORBIS_missing_capture_pattern: + return "Missing capture pattern"; + case VORBIS_invalid_stream_structure_version: + return "Invalid stream structure version"; + case VORBIS_continued_packet_flag_invalid: + return "Continued packet flag invalid"; + case VORBIS_incorrect_stream_serial_number: + return "Incorrect stream serial number"; + case VORBIS_invalid_first_page: + return "Invalid first page"; + case VORBIS_bad_packet_type: + return "Bad packet type"; + case VORBIS_cant_find_last_page: + return "Can't find last page"; + case VORBIS_seek_failed: + return "Seek failed"; + case VORBIS_ogg_skeleton_not_supported: + return "OGG skeleton not supported"; + default: + return "Unrecognized error code"; + } +} + +Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { +} + +Ogg::Ogg(std::vector data) : memory_data_(std::move(data)), instance_(nullptr) { + _init_with_data(); +} + +Ogg::Ogg(tcb::span data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { + _init_with_data(); +} + +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 { + std::swap(memory_data_, rhs.memory_data_); + std::swap(instance_, rhs.instance_); + + return *this; +} + +Ogg::~Ogg() { + if (instance_) { + stb_vorbis_close(instance_); + instance_ = nullptr; + } +} + +std::size_t Ogg::get_samples(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + + size_t read = stb_vorbis_get_samples_float_interleaved( + instance_, 1, reinterpret_cast(buffer.data()), buffer.size() * 1); + + return read; +} + +std::size_t Ogg::get_samples(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + + size_t read = stb_vorbis_get_samples_float_interleaved( + instance_, 2, reinterpret_cast(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)) { + sample.amplitudes[1] = sample.amplitudes[0]; + } + } + + return read; +} + +OggComment Ogg::comment() const { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); + + return OggComment { + std::string(c_comment.vendor), + std::vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; +} + +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) { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_seek(instance_, sample); +} + +std::size_t Ogg::position() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_get_sample_offset(instance_); +} + +float Ogg::position_seconds() const { + return position() / static_cast(sample_rate()); +} + +std::size_t Ogg::duration_samples() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_stream_length_in_samples(instance_); +} + +float Ogg::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_stream_length_in_seconds(instance_); +} + +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_) { + return; + } + + if (memory_data_.size() >= std::numeric_limits::max()) + throw std::logic_error("Buffer is too large for stb_vorbis"); + if (memory_data_.size() == 0) + throw std::logic_error("Insufficient data from stream"); + + int vorbis_result; + instance_ = stb_vorbis_open_memory( + reinterpret_cast(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL); + + if (vorbis_result != VORBIS__no_error) + throw StbVorbisException(vorbis_result); +} diff --git a/src/audio/ogg.hpp b/src/audio/ogg.hpp new file mode 100644 index 000000000..d4b8b9275 --- /dev/null +++ b/src/audio/ogg.hpp @@ -0,0 +1,81 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_OGG_HPP__ +#define __SRB2_AUDIO_OGG_HPP__ + +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" +#include "source.hpp" + +namespace srb2::audio { + +class StbVorbisException final : public std::exception { + int code_; + +public: + explicit StbVorbisException(int code) noexcept; + + virtual const char* what() const noexcept; +}; + +struct OggComment { + std::string vendor; + std::vector comments; +}; + +class Ogg final { + std::vector memory_data_; + stb_vorbis* instance_; + +public: + Ogg() noexcept; + + explicit Ogg(std::vector data); + explicit Ogg(tcb::span data); + + Ogg(const Ogg&) = delete; + Ogg(Ogg&& rhs) noexcept; + + Ogg& operator=(const Ogg&) = delete; + Ogg& operator=(Ogg&& rhs) noexcept; + + ~Ogg(); + + std::size_t get_samples(tcb::span> buffer); + std::size_t get_samples(tcb::span> buffer); + void seek(std::size_t sample); + std::size_t position() const; + float position_seconds() const; + + OggComment comment() const; + std::size_t sample_rate() const; + std::size_t channels() const; + std::size_t duration_samples() const; + float duration_seconds() const; + +private: + void _init_with_data(); +}; + +template , int> = 0> +inline Ogg load_ogg(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Ogg {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_OGG_HPP__ diff --git a/src/audio/ogg_player.cpp b/src/audio/ogg_player.cpp new file mode 100644 index 000000000..d9028dedb --- /dev/null +++ b/src/audio/ogg_player.cpp @@ -0,0 +1,141 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "ogg_player.hpp" + +#include +#include +#include +#include +#include + +using namespace srb2; +using namespace srb2::audio; + +namespace { + +std::optional 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) { + std::string_view comment_view(comment); + comment_view.remove_prefix(10); + std::string copied {comment_view}; + + try { + int loop_point = std::stoi(copied); + return loop_point; + } catch (...) { + } + } + + if (comment.find("LOOPMS=") == 0) { + std::string_view comment_view(comment); + comment_view.remove_prefix(7); + std::string copied {comment_view}; + + try { + int loop_ms = std::stoi(copied); + int loop_point = std::round(static_cast(loop_ms) / (rate / 1000.)); + + return loop_point; + } catch (...) { + } + } + } + + return std::nullopt; +} + +} // namespace + +template +OggPlayer::OggPlayer(Ogg&& ogg) noexcept + : playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward(ogg)) { + loop_point_ = find_loop_point(ogg_); +} + +template +OggPlayer::OggPlayer(OggPlayer&& rhs) noexcept = default; + +template +OggPlayer& OggPlayer::operator=(OggPlayer&& rhs) noexcept = default; + +template +OggPlayer::~OggPlayer() = default; + +template +std::size_t OggPlayer::generate(tcb::span> buffer) { + if (!playing_) + return 0; + + std::size_t total = 0; + do { + std::size_t read = ogg_.get_samples(buffer.subspan(total)); + total += read; + + if (read == 0 && !looping_) { + playing_ = false; + break; + } + + if (read == 0 && loop_point_) { + ogg_.seek(*loop_point_); + } + + if (read == 0 && !loop_point_) { + ogg_.seek(0); + } + } while (total < buffer.size()); + + return total; +} + +template +void OggPlayer::seek(float position_seconds) { + ogg_.seek(static_cast(position_seconds * sample_rate())); +} + +template +void OggPlayer::loop_point_seconds(float loop_point) { + std::size_t rate = sample_rate(); + loop_point = static_cast(std::round(loop_point * rate)); +} + +template +void OggPlayer::reset() { + ogg_.seek(0); +} + +template +std::size_t OggPlayer::sample_rate() const { + return ogg_.sample_rate(); +} + +template +float OggPlayer::duration_seconds() const { + return ogg_.duration_seconds(); +} + +template +std::optional OggPlayer::loop_point_seconds() const { + if (!loop_point_) + return std::nullopt; + + return *loop_point_ / static_cast(sample_rate()); +} + +template +float OggPlayer::position_seconds() const { + return ogg_.position_seconds(); +} + +template class srb2::audio::OggPlayer<1>; +template class srb2::audio::OggPlayer<2>; diff --git a/src/audio/ogg_player.hpp b/src/audio/ogg_player.hpp new file mode 100644 index 000000000..049d39862 --- /dev/null +++ b/src/audio/ogg_player.hpp @@ -0,0 +1,72 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_OGG_SOURCE_HPP__ +#define __SRB2_AUDIO_OGG_SOURCE_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" +#include "ogg.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class OggPlayer final : public Source { + bool playing_; + bool looping_; + std::optional loop_point_; + Ogg ogg_; + +public: + OggPlayer(Ogg&& ogg) noexcept; + + OggPlayer(const OggPlayer&) = delete; + OggPlayer(OggPlayer&& rhs) noexcept; + + OggPlayer& operator=(const OggPlayer&) = delete; + OggPlayer& operator=(OggPlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override final; + + bool looping() const { return looping_; } + + void looping(bool looping) { looping_ = looping; } + + bool playing() const { return playing_; } + void playing(bool playing) { playing_ = playing; } + void seek(float position_seconds); + void loop_point_seconds(float loop_point); + + void reset(); + std::size_t sample_rate() const; + + float duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + ~OggPlayer(); +}; + +extern template class OggPlayer<1>; +extern template class OggPlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_OGG_SOURCE_HPP__ diff --git a/src/audio/resample.cpp b/src/audio/resample.cpp new file mode 100644 index 000000000..a64d0af13 --- /dev/null +++ b/src/audio/resample.cpp @@ -0,0 +1,81 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "resample.hpp" + +#include +#include +#include +#include +#include + +using std::shared_ptr; +using std::size_t; +using std::vector; + +using namespace srb2::audio; + +template +Resampler::Resampler(std::shared_ptr>&& source, float ratio) + : source_(std::forward>>(source)), ratio_(ratio) { +} + +template +Resampler::Resampler(Resampler&& r) = default; + +template +Resampler::~Resampler() = default; + +template +Resampler& Resampler::operator=(Resampler&& r) = default; + +template +size_t Resampler::generate(tcb::span> buffer) { + if (!source_) + return 0; + + if (ratio_ == 1.f) { + // fast path - generate directly from source + size_t source_read = source_->generate(buffer); + return source_read; + } + + size_t written = 0; + + while (written < buffer.size()) { + // do we need a refill? + if (buf_.size() == 0 || pos_ >= static_cast(buf_.size() - 1)) { + pos_ -= buf_.size(); + last_ = buf_.size() == 0 ? Sample {} : buf_.back(); + buf_.clear(); + buf_.resize(512); + size_t source_read = source_->generate(buf_); + buf_.resize(source_read); + if (source_read == 0) { + break; + } + } + + if (pos_ < 0) { + buffer[written] = (buf_[0] - last_) * pos_frac_ + last_; + advance(ratio_); + written++; + continue; + } + + buffer[written] = (buf_[pos_ + 1] - buf_[pos_]) * pos_frac_ + buf_[pos_]; + advance(ratio_); + written++; + } + + return written; +} + +template class srb2::audio::Resampler<1>; +template class srb2::audio::Resampler<2>; diff --git a/src/audio/resample.hpp b/src/audio/resample.hpp new file mode 100644 index 000000000..7aab4f674 --- /dev/null +++ b/src/audio/resample.hpp @@ -0,0 +1,63 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_RESAMPLE_HPP__ +#define __SRB2_AUDIO_RESAMPLE_HPP__ + +#include +#include +#include +#include +#include + +#include + +#include "sound_chunk.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class Resampler : public Source { +public: + Resampler(std::shared_ptr>&& source_, float ratio); + Resampler(const Resampler& r) = delete; + Resampler(Resampler&& r); + virtual ~Resampler(); + + virtual std::size_t generate(tcb::span> buffer); + + Resampler& operator=(const Resampler& r) = delete; + Resampler& operator=(Resampler&& r); + +private: + std::shared_ptr> source_; + float ratio_ {1.f}; + std::vector> buf_; + Sample last_; + int pos_ {0}; + float pos_frac_ {0.f}; + + void advance(float samples) { + pos_frac_ += samples; + float integer; + std::modf(pos_frac_, &integer); + pos_ += integer; + pos_frac_ -= integer; + } + + void refill(); +}; + +extern template class Resampler<1>; +extern template class Resampler<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_RESAMPLE_HPP__ diff --git a/src/audio/sample.hpp b/src/audio/sample.hpp new file mode 100644 index 000000000..b1f0298b5 --- /dev/null +++ b/src/audio/sample.hpp @@ -0,0 +1,78 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SAMPLE_HPP__ +#define __SRB2_AUDIO_SAMPLE_HPP__ + +#include + +namespace srb2::audio { + +template +struct Sample { + std::array amplitudes; + + 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++) { + amplitudes[i] *= rhs; + } + return *this; + } +}; + +template +constexpr Sample operator+(const Sample& lhs, const Sample& rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i]; + } + return out; +} + +template +constexpr Sample operator-(const Sample& lhs, const Sample& rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i]; + } + return out; +} + +template +constexpr Sample operator*(const Sample& lhs, float rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] * rhs; + } + return out; +} + +template +static constexpr float sample_to_float(T sample) noexcept; + +template <> +constexpr float sample_to_float(uint8_t sample) noexcept { + return (sample / 128.f) - 1.f; +} + +template <> +constexpr float sample_to_float(int16_t sample) noexcept { + return sample / 32768.f; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SAMPLE_HPP__ diff --git a/src/audio/sound_chunk.hpp b/src/audio/sound_chunk.hpp new file mode 100644 index 000000000..7fc0a45eb --- /dev/null +++ b/src/audio/sound_chunk.hpp @@ -0,0 +1,25 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOUND_CHUNK_HPP__ +#define __SRB2_AUDIO_SOUND_CHUNK_HPP__ + +#include + +#include "source.hpp" + +namespace srb2::audio { + +struct SoundChunk { + std::vector> samples; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOUND_CHUNK_HPP__ diff --git a/src/audio/sound_effect_player.cpp b/src/audio/sound_effect_player.cpp new file mode 100644 index 000000000..a038ee3d8 --- /dev/null +++ b/src/audio/sound_effect_player.cpp @@ -0,0 +1,72 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "sound_effect_player.hpp" + +#include +#include +#include + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Sample; +using srb2::audio::SoundEffectPlayer; +using srb2::audio::Source; + +size_t SoundEffectPlayer::generate(tcb::span> buffer) { + if (!chunk_) + return 0; + if (position_ >= chunk_->samples.size()) { + return 0; + } + + size_t written = 0; + 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); + + float left_scale = std::cos(sep_pan); + float right_scale = std::sin(sep_pan); + buffer[written] = {mono_sample * volume_ * left_scale, mono_sample * volume_ * right_scale}; + written += 1; + } + return written; +} + +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) { + volume_ = volume; + sep_ = sep; +} + +void SoundEffectPlayer::reset() { + position_ = 0; + chunk_ = nullptr; +} + +bool SoundEffectPlayer::finished() const { + if (!chunk_) + return true; + if (position_ >= chunk_->samples.size()) + return true; + return false; +} + +bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const { + return chunk_ == chunk; +} + +SoundEffectPlayer::~SoundEffectPlayer() = default; diff --git a/src/audio/sound_effect_player.hpp b/src/audio/sound_effect_player.hpp new file mode 100644 index 000000000..99f5edb9e --- /dev/null +++ b/src/audio/sound_effect_player.hpp @@ -0,0 +1,46 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ +#define __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ + +#include + +#include + +#include "sound_chunk.hpp" +#include "source.hpp" + +namespace srb2::audio { + +class SoundEffectPlayer : public Source<2> { +public: + virtual std::size_t generate(tcb::span> buffer) override final; + + virtual ~SoundEffectPlayer() final; + + void start(const SoundChunk* chunk, float volume, float sep); + void update(float volume, float sep); + void reset(); + bool finished() const; + + bool is_playing_chunk(const SoundChunk* chunk) const; + +private: + float volume_; + float sep_; + + std::size_t position_; + + const SoundChunk* chunk_; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ diff --git a/src/audio/source.hpp b/src/audio/source.hpp new file mode 100644 index 000000000..ea4be8761 --- /dev/null +++ b/src/audio/source.hpp @@ -0,0 +1,36 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOURCE_HPP__ +#define __SRB2_AUDIO_SOURCE_HPP__ + +#include + +#include + +#include "sample.hpp" + +namespace srb2::audio { + +template +class Source { +public: + virtual std::size_t generate(tcb::span> buffer) = 0; + + virtual ~Source() = default; +}; + +// This audio DSP is Stereo, FP32 system-endian, 44100 Hz internally. +// Conversions to other formats should be handled elsewhere. + +constexpr const std::size_t kSampleRate = 44100; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOURCE_HPP__ diff --git a/src/audio/wav.cpp b/src/audio/wav.cpp new file mode 100644 index 000000000..31f3a0468 --- /dev/null +++ b/src/audio/wav.cpp @@ -0,0 +1,264 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "wav.hpp" + +#include +#include +#include + +using namespace srb2; +using srb2::audio::Wav; + +namespace { + +constexpr const uint32_t kMagicRIFF = 0x46464952; +constexpr const uint32_t kMagicWAVE = 0x45564157; +constexpr const uint32_t kMagicFmt = 0x20746d66; +constexpr const uint32_t kMagicData = 0x61746164; + +constexpr const uint16_t kFormatPcm = 1; + +constexpr const std::size_t kRiffHeaderLength = 8; + +struct RiffHeader { + uint32_t magic; + std::size_t filesize; +}; + +struct TagHeader { + uint32_t type; + std::size_t length; +}; + +struct FmtTag { + uint16_t format; + uint16_t channels; + uint32_t rate; + uint32_t bytes_per_second; + uint32_t bytes_per_sample; + uint16_t bit_width; +}; + +struct DataTag {}; + +RiffHeader parse_riff_header(io::SpanStream& stream) { + if (io::remaining(stream) < kRiffHeaderLength) + throw std::runtime_error("insufficient bytes remaining in stream"); + + RiffHeader ret; + ret.magic = io::read_uint32(stream); + ret.filesize = io::read_uint32(stream); + return ret; +} + +TagHeader parse_tag_header(io::SpanStream& stream) { + if (io::remaining(stream) < 8) + throw std::runtime_error("insufficient bytes remaining in stream"); + + TagHeader header; + header.type = io::read_uint32(stream); + header.length = io::read_uint32(stream); + return header; +} + +FmtTag parse_fmt_tag(io::SpanStream& stream) { + if (io::remaining(stream) < 16) + throw std::runtime_error("insufficient bytes in stream"); + + FmtTag tag; + tag.format = io::read_uint16(stream); + tag.channels = io::read_uint16(stream); + tag.rate = io::read_uint32(stream); + tag.bytes_per_second = io::read_uint32(stream); + tag.bytes_per_sample = io::read_uint16(stream); + tag.bit_width = io::read_uint16(stream); + + return tag; +} + +template +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) { + case kMagicFmt: + { + FmtTag fmt_tag {parse_fmt_tag(stream)}; + visitor(fmt_tag); + break; + } + case kMagicData: + { + DataTag data_tag; + visitor(data_tag); + break; + } + default: + // Unrecognized tags are ignored. + break; + } + + stream.seek(io::SeekFrom::kStart, dest); +} + +std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { + std::vector samples; + samples.reserve(count); + for (std::size_t i = 0; i < count; i++) { + samples.push_back(io::read_uint8(stream)); + } + return samples; +} + +std::vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { + std::vector samples; + samples.reserve(count); + for (std::size_t i = 0; i < count; i++) { + samples.push_back(io::read_int16(stream)); + } + return samples; +} + +template +struct OverloadVisitor : Ts... { + using Ts::operator()...; +}; + +template +OverloadVisitor(Ts...) -> OverloadVisitor; + +} // namespace + +Wav::Wav() = default; + +Wav::Wav(tcb::span data) { + io::SpanStream stream {data}; + + auto [magic, filesize] = parse_riff_header(stream); + + if (magic != kMagicRIFF) { + throw std::runtime_error("invalid RIFF magic"); + } + + 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) { + throw std::runtime_error("RIFF in stream is not a WAVE"); + } + + std::optional read_fmt; + std::variant, std::vector> interleaved_samples; + + while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { + TagHeader tag_header {parse_tag_header(stream)}; + 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) { + throw std::runtime_error("WAVE has multiple 'fmt' tags"); + } + if (fmt.format != kFormatPcm) { + throw std::runtime_error("Unsupported WAVE format (only PCM is supported)"); + } + read_fmt = 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) { + 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) { + case 8: + interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count)); + break; + case 16: + interleaved_samples = std::move(read_int16_samples_from_stream(stream, sample_count)); + break; + default: + throw std::runtime_error("unsupported sample amplitude bit width"); + } + }}; + + visit_tag(tag_visitor, stream, tag_header); + } + + if (!read_fmt) { + throw std::runtime_error("WAVE did not have a fmt tag"); + } + + interleaved_samples_ = std::move(interleaved_samples); + channels_ = read_fmt->channels; + sample_rate_ = read_fmt->rate; +} + +namespace { + +template +std::size_t read_samples(std::size_t channels, + std::size_t offset, + const std::vector& samples, + tcb::span> 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) { + 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++) { + buffer[i].amplitudes[0] = 0.f; + 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(channels); + } + + return samples_to_read; +} + +} // namespace + +std::size_t Wav::get_samples(std::size_t offset, tcb::span> buffer) const noexcept { + auto samples_visitor = OverloadVisitor { + [&](const std::vector& samples) { return read_samples(channels(), offset, samples, buffer); }, + [&](const std::vector& samples) { + return read_samples(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& samples) { return samples.size(); }, + [](const std::vector& samples) { return samples.size(); }}; + return std::visit(samples_visitor, interleaved_samples_); +} diff --git a/src/audio/wav.hpp b/src/audio/wav.hpp new file mode 100644 index 000000000..e571969e7 --- /dev/null +++ b/src/audio/wav.hpp @@ -0,0 +1,51 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_WAV_HPP__ +#define __SRB2_AUDIO_WAV_HPP__ + +#include +#include +#include +#include +#include + +#include + +#include "../io/streams.hpp" +#include "sample.hpp" + +namespace srb2::audio { + +class Wav final { + std::variant, std::vector> interleaved_samples_; + std::size_t channels_ = 1; + std::size_t sample_rate_ = 44100; + +public: + Wav(); + + explicit Wav(tcb::span data); + + std::size_t get_samples(std::size_t offset, tcb::span> buffer) const noexcept; + std::size_t interleaved_length() const noexcept; + std::size_t length() const noexcept { return interleaved_length() / channels(); }; + std::size_t channels() const noexcept { return channels_; }; + std::size_t sample_rate() const noexcept { return sample_rate_; }; +}; + +template , int> = 0> +inline Wav load_wav(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Wav {data}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_WAV_HPP__ diff --git a/src/audio/wav_player.cpp b/src/audio/wav_player.cpp new file mode 100644 index 000000000..64f9831f0 --- /dev/null +++ b/src/audio/wav_player.cpp @@ -0,0 +1,45 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "wav_player.hpp" + +using namespace srb2; + +using srb2::audio::WavPlayer; + +WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) { +} + +WavPlayer::WavPlayer(const WavPlayer& rhs) = default; + +WavPlayer::WavPlayer(WavPlayer&& rhs) noexcept = default; + +WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default; + +WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default; + +WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward(wav)), position_(0), looping_(false) { +} + +std::size_t WavPlayer::generate(tcb::span> buffer) { + std::size_t samples_read = 0; + 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_) { + position_ = 0; + } + if (read_this_time == 0 && !looping_) { + break; + } + } + return samples_read; +} diff --git a/src/audio/wav_player.hpp b/src/audio/wav_player.hpp new file mode 100644 index 000000000..dc6a98864 --- /dev/null +++ b/src/audio/wav_player.hpp @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_WAV_PLAYER_HPP__ +#define __SRB2_AUDIO_WAV_PLAYER_HPP__ + +#include + +#include + +#include "source.hpp" +#include "wav.hpp" + +namespace srb2::audio { + +class WavPlayer final : public Source<1> { + Wav wav_; + std::size_t position_; + bool looping_; + +public: + WavPlayer(); + WavPlayer(const WavPlayer& rhs); + WavPlayer(WavPlayer&& rhs) noexcept; + + WavPlayer& operator=(const WavPlayer& rhs); + WavPlayer& operator=(WavPlayer&& rhs) noexcept; + + WavPlayer(Wav&& wav) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override; + + bool looping() const { return looping_; } + void looping(bool looping) { looping_ = looping; } + + std::size_t sample_rate() const { return wav_.sample_rate(); } + float duration_seconds() const { return wav_.length() / static_cast(wav_.sample_rate()); } + void seek(float seconds) { position_ = seconds * wav_.sample_rate(); } +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_WAV_PLAYER_HPP__ diff --git a/src/audio/xmp.cpp b/src/audio/xmp.cpp new file mode 100644 index 000000000..9e88f2a7f --- /dev/null +++ b/src/audio/xmp.cpp @@ -0,0 +1,167 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "xmp.hpp" + +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +XmpException::XmpException(int code) : code_(code) { +} + +const char* XmpException::what() const noexcept { + switch (code_) { + case -XMP_ERROR_INTERNAL: + return "XMP_ERROR_INTERNAL"; + case -XMP_ERROR_FORMAT: + return "XMP_ERROR_FORMAT"; + case -XMP_ERROR_LOAD: + return "XMP_ERROR_LOAD"; + case -XMP_ERROR_DEPACK: + return "XMP_ERROR_DEPACK"; + case -XMP_ERROR_SYSTEM: + return "XMP_ERROR_SYSTEM"; + case -XMP_ERROR_INVALID: + return "XMP_ERROR_INVALID"; + case -XMP_ERROR_STATE: + return "XMP_ERROR_STATE"; + default: + return "unknown"; + } +} + +template +Xmp::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) { +} + +template +Xmp::Xmp(std::vector data) + : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { + _init(); +} + +template +Xmp::Xmp(tcb::span data) + : data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) { + _init(); +} + +template +Xmp::Xmp(Xmp&& rhs) noexcept : Xmp() { + std::swap(data_, rhs.data_); + std::swap(instance_, rhs.instance_); + std::swap(module_loaded_, rhs.module_loaded_); + std::swap(looping_, rhs.looping_); +} + +template +Xmp& Xmp::operator=(Xmp&& rhs) noexcept { + std::swap(data_, rhs.data_); + std::swap(instance_, rhs.instance_); + std::swap(module_loaded_, rhs.module_loaded_); + std::swap(looping_, rhs.looping_); + + return *this; +}; + +template +Xmp::~Xmp() { + if (instance_) { + xmp_free_context(instance_); + instance_ = nullptr; + } +} + +template +std::size_t Xmp::play_buffer(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + int result = xmp_play_buffer(instance_, buffer.data(), buffer.size_bytes(), !looping_); + + if (result == -XMP_END) + return 0; + + if (result != 0) + throw XmpException(result); + + return buffer.size(); +} + +template +void Xmp::reset() { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + xmp_restart_module(instance_); +} + +template +float Xmp::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + xmp_frame_info info; + xmp_get_frame_info(instance_, &info); + return static_cast(info.total_time) / 1000.f; +} + +template +void Xmp::seek(int position_ms) { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + int err = xmp_seek_time(instance_, position_ms); + if (err != 0) + throw XmpException(err); +} + +template +void Xmp::_init() { + if (instance_) + return; + + if (data_.size() >= std::numeric_limits::max()) + throw std::logic_error("Buffer is too large for xmp"); + if (data_.size() == 0) + throw std::logic_error("Insufficient data from stream"); + + instance_ = xmp_create_context(); + if (instance_ == nullptr) { + throw std::bad_alloc(); + } + + int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size()); + if (result != 0) { + xmp_free_context(instance_); + instance_ = nullptr; + throw XmpException(result); + } + module_loaded_ = true; + + int flags = 0; + if constexpr (C == 1) { + flags |= XMP_FORMAT_MONO; + } + result = xmp_start_player(instance_, 44100, flags); + if (result != 0) { + xmp_release_module(instance_); + module_loaded_ = false; + xmp_free_context(instance_); + instance_ = nullptr; + throw XmpException(result); + } +} + +template class srb2::audio::Xmp<1>; +template class srb2::audio::Xmp<2>; diff --git a/src/audio/xmp.hpp b/src/audio/xmp.hpp new file mode 100644 index 000000000..a5b443bfa --- /dev/null +++ b/src/audio/xmp.hpp @@ -0,0 +1,78 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + + #ifndef __SRB2_AUDIO_XMP_HPP__ +#define __SRB2_AUDIO_XMP_HPP__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" + +namespace srb2::audio { + +class XmpException : public std::exception { + int code_; + +public: + XmpException(int code); + virtual const char* what() const noexcept override final; +}; + +template +class Xmp final { + std::vector data_; + xmp_context instance_; + bool module_loaded_; + bool looping_; + +public: + Xmp(); + + explicit Xmp(std::vector data); + explicit Xmp(tcb::span data); + + Xmp(const Xmp&) = delete; + Xmp(Xmp&& rhs) noexcept; + + Xmp& operator=(const Xmp&) = delete; + Xmp& operator=(Xmp&& rhs) noexcept; + + std::size_t play_buffer(tcb::span> buffer); + bool looping() const { return looping_; }; + void looping(bool looping) { looping_ = looping; }; + void reset(); + float duration_seconds() const; + void seek(int position_ms); + + ~Xmp(); + +private: + void _init(); +}; + +extern template class Xmp<1>; +extern template class Xmp<2>; + +template , int> = 0> +inline Xmp load_xmp(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Xmp {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_XMP_HPP__ diff --git a/src/audio/xmp_player.cpp b/src/audio/xmp_player.cpp new file mode 100644 index 000000000..a08ee8bb5 --- /dev/null +++ b/src/audio/xmp_player.cpp @@ -0,0 +1,57 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "xmp_player.hpp" + +#include + +using namespace srb2; +using namespace srb2::audio; + +template +XmpPlayer::XmpPlayer(Xmp&& xmp) : xmp_(std::move(xmp)), buf_() { +} + +template +XmpPlayer::XmpPlayer(XmpPlayer&& rhs) noexcept = default; + +template +XmpPlayer& XmpPlayer::operator=(XmpPlayer&& rhs) noexcept = default; + +template +XmpPlayer::~XmpPlayer() = default; + +template +std::size_t XmpPlayer::generate(tcb::span> 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++) { + buffer[i].amplitudes[j] = buf_[i][j] / 32768.f; + } + } + + return ret; +} + +template +float XmpPlayer::duration_seconds() const { + return xmp_.duration_seconds(); +} + +template +void XmpPlayer::seek(float position_seconds) { + xmp_.seek(static_cast(std::round(position_seconds * 1000.f))); +} + +template class srb2::audio::XmpPlayer<1>; +template class srb2::audio::XmpPlayer<2>; diff --git a/src/audio/xmp_player.hpp b/src/audio/xmp_player.hpp new file mode 100644 index 000000000..5829dbd8a --- /dev/null +++ b/src/audio/xmp_player.hpp @@ -0,0 +1,48 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_XMP_PLAYER_HPP__ +#define __SRB2_AUDIO_XMP_PLAYER_HPP__ + +#include "source.hpp" +#include "xmp.hpp" + +namespace srb2::audio { + +template +class XmpPlayer final : public Source { + Xmp xmp_; + std::vector> buf_; + +public: + XmpPlayer(Xmp&& xmp); + + XmpPlayer(const XmpPlayer&) = delete; + XmpPlayer(XmpPlayer&& rhs) noexcept; + + XmpPlayer& operator=(const XmpPlayer&) = delete; + XmpPlayer& operator=(XmpPlayer&& rhs) noexcept; + + ~XmpPlayer(); + + virtual std::size_t generate(tcb::span> buffer) override final; + + bool looping() { return xmp_.looping(); }; + void looping(bool looping) { xmp_.looping(looping); } + void reset() { xmp_.reset(); } + float duration_seconds() const; + void seek(float position_seconds); +}; + +extern template class XmpPlayer<1>; +extern template class XmpPlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_XMP_PLAYER_HPP__ diff --git a/src/comptime.c b/src/comptime.c index 2baef79d6..74b810062 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -11,6 +11,8 @@ #include "config.h" const char *compbranch = SRB2_COMP_BRANCH; const char *comprevision = SRB2_COMP_REVISION; +const char *comptype = CMAKE_BUILD_TYPE; +const int compoptimized = SRB2_COMP_OPTIMIZED; #elif (defined(COMPVERSION)) #include "comptime.h" diff --git a/src/config.h.in b/src/config.h.in index d7b67cdce..77a205a74 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -20,6 +20,9 @@ #define COMPVERSION_UNCOMMITTED #endif +#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" +#cmakedefine01 SRB2_COMP_OPTIMIZED + #endif /* Manually defined asset hashes for non-CMake builds diff --git a/src/console.c b/src/console.c index 1d906af71..04a9fdce4 100644 --- a/src/console.c +++ b/src/console.c @@ -358,7 +358,22 @@ static void CON_SetupColormaps(void) *memorysrc = (UINT8)(i & 0xFF); // remap each color to itself... purplemap[0] = (UINT8)163; + yellowmap[0] = (UINT8)73; + yellowmap[1] = (UINT8)73; + yellowmap[3] = (UINT8)74; + yellowmap[6] = (UINT8)74; + yellowmap[7] = (UINT8)190; + yellowmap[8] = (UINT8)190; + yellowmap[10] = (UINT8)190; + yellowmap[12] = (UINT8)190; + yellowmap[14] = (UINT8)149; + yellowmap[15] = (UINT8)149; + yellowmap[16] = (UINT8)149; + yellowmap[21] = (UINT8)152; + yellowmap[23] = (UINT8)173; + yellowmap[24] = (UINT8)167; + greenmap[0] = (UINT8)98; bluemap[0] = (UINT8)148; redmap[0] = (UINT8)34; // battle diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 07c756d53..1553f7f2c 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -54,7 +54,6 @@ #include "k_pwrlv.h" #include "k_bot.h" #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "s_sound.h" // sfx_syfail #include "m_cond.h" // netUnlocked @@ -899,9 +898,6 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) UINT8 *p; size_t mirror_length; const char *httpurl = cv_httpsource.string; - UINT8 prefgametype = (cv_kartgametypepreference.value == -1) - ? gametype - : cv_kartgametypepreference.value; netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; @@ -933,7 +929,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[prefgametype], + strncpy(netbuffer->u.serverinfo.gametypename, gametypes[gametype]->name, sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); @@ -1141,35 +1137,32 @@ static boolean SV_ResendingSavegameToAnyone(void) static void SV_SendSaveGame(INT32 node, boolean resending) { size_t length, compressedlen; - savebuffer_t save; + savebuffer_t save = {0}; UINT8 *compressedsave; UINT8 *buffertosend; // first save it in a malloced buffer - save.size = NETSAVEGAMESIZE; - save.buffer = (UINT8 *)malloc(save.size); - if (!save.buffer) + if (P_SaveBufferAlloc(&save, NETSAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } // Leave room for the uncompressed length. - save.p = save.buffer + sizeof(UINT32); - save.end = save.buffer + save.size; + save.p += sizeof(UINT32); P_SaveNetGame(&save, resending); length = save.p - save.buffer; if (length > NETSAVEGAMESIZE) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Savegame buffer overrun"); } // Allocate space for compressed save: one byte fewer than for the // uncompressed data to ensure that the compression is worthwhile. - compressedsave = malloc(length - 1); + compressedsave = Z_Malloc(length - 1, PU_STATIC, NULL); if (!compressedsave) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); @@ -1180,7 +1173,7 @@ static void SV_SendSaveGame(INT32 node, boolean resending) if ((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) { // Compressing succeeded; send compressed data - free(save.buffer); + P_SaveBufferFree(&save); // State that we're compressed. buffertosend = compressedsave; @@ -1190,14 +1183,14 @@ static void SV_SendSaveGame(INT32 node, boolean resending) else { // Compression failed to make it smaller; send original - free(compressedsave); + Z_Free(compressedsave); // State that we're not compressed buffertosend = save.buffer; WRITEUINT32(save.buffer, 0); } - AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); + AddRamToSendQueue(node, buffertosend, length, SF_Z_RAM, 0); // Remember when we started sending the savegame so we can handle timeouts sendingsavegame[node] = true; @@ -1211,7 +1204,7 @@ static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SA static void SV_SavedGame(void) { size_t length; - savebuffer_t save; + savebuffer_t save = {0}; char tmpsave[256]; if (!cv_dumpconsistency.value) @@ -1220,22 +1213,18 @@ static void SV_SavedGame(void) sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); // first save it in a malloced buffer - save.size = NETSAVEGAMESIZE; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, NETSAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } - save.end = save.buffer + save.size; - P_SaveNetGame(&save, false); length = save.p - save.buffer; if (length > NETSAVEGAMESIZE) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Savegame buffer overrun"); } @@ -1243,7 +1232,7 @@ static void SV_SavedGame(void) if (!FIL_WriteFile(tmpsave, save.buffer, length)) CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); - free(save.buffer); + P_SaveBufferFree(&save); } #undef TMPSAVENAME @@ -1253,37 +1242,31 @@ static void SV_SavedGame(void) static void CL_LoadReceivedSavegame(boolean reloading) { - savebuffer_t save; + savebuffer_t save = {0}; size_t length, decompressedlen; char tmpsave[256]; sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - length = FIL_ReadFile(tmpsave, &save.buffer); - - CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); - if (!length) + if (P_SaveBufferFromFile(&save, tmpsave) == false) { I_Error("Can't read savegame sent"); return; } - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; + length = save.size; + CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); // Decompress saved game if necessary. decompressedlen = READUINT32(save.p); - if(decompressedlen > 0) + if (decompressedlen > 0) { UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); lzf_decompress(save.p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); - Z_Free(save.buffer); - save.p = save.buffer = decompressedbuffer; - save.size = decompressedlen; - save.end = save.buffer + decompressedlen; + P_SaveBufferFree(&save); + P_SaveBufferFromExisting(&save, decompressedbuffer, decompressedlen); } paused = false; @@ -1315,10 +1298,13 @@ static void CL_LoadReceivedSavegame(boolean reloading) } // done - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); + if (unlink(tmpsave) == -1) + { CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); + } + consistancy[gametic%BACKUPTICS] = Consistancy(); CON_ToggleOff(); @@ -2087,8 +2073,9 @@ static void CL_ConnectToServer(void) Y_EndVote(); DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); - M_ClearMenus(true); G_SetGamestate(GS_WAITINGPLAYERS); + if (wipegamestate == GS_MENU) + M_ClearMenus(true); wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); @@ -3754,6 +3741,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); SetPlayerSkinByNum(newplayernum, skinnum); + players[newplayernum].spectator = !(gametyperules & GTR_BOTS) + || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE); + if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); @@ -6067,7 +6057,7 @@ void CL_ClearRewinds(void) rewind_t *CL_SaveRewindPoint(size_t demopos) { - savebuffer_t save; + savebuffer_t save = {0}; rewind_t *rewind; if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime) @@ -6077,10 +6067,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) if (!rewind) return NULL; - save.buffer = save.p = rewind->savebuffer; - save.size = NETSAVEGAMESIZE; - save.end = save.buffer + save.size; - + P_SaveBufferFromExisting(&save, rewind->savebuffer, NETSAVEGAMESIZE); P_SaveNetGame(&save, false); rewind->leveltime = leveltime; @@ -6093,7 +6080,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) rewind_t *CL_RewindToTime(tic_t time) { - savebuffer_t save; + savebuffer_t save = {0}; rewind_t *rewind; while (rewindhead && rewindhead->leveltime > time) @@ -6106,10 +6093,7 @@ rewind_t *CL_RewindToTime(tic_t time) if (!rewindhead) return NULL; - save.buffer = save.p = rewindhead->savebuffer; - save.size = NETSAVEGAMESIZE; - save.end = save.buffer + save.size; - + P_SaveBufferFromExisting(&save, rewindhead->savebuffer, NETSAVEGAMESIZE); P_LoadNetGame(&save, false); wipegamestate = gamestate; // No fading back in! diff --git a/src/d_main.c b/src/d_main.c index b9020eb04..e6be31828 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -72,7 +72,6 @@ // SRB2Kart #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "m_random.h" // P_ClearRandom #include "k_specialstage.h" @@ -140,7 +139,8 @@ char srb2home[256] = "."; char srb2path[256] = "."; boolean usehome = true; const char *pandf = "%s" PATHSEP "%s"; -static char addonsdir[MAX_WADPATH]; +char addonsdir[MAX_WADPATH]; +char downloaddir[sizeof addonsdir + sizeof DOWNLOADDIR_PART] = "DOWNLOAD"; // // EVENT HANDLING @@ -964,12 +964,6 @@ void D_StartTitle(void) // Reset GP memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - // Reset boss info - K_ResetBossInfo(); - - // Reset Special Stage - K_ResetSpecialStage(); - // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1201,13 +1195,14 @@ D_ConvertVersionNumbers (void) // void D_SRB2Main(void) { - INT32 i, p; + INT32 i, j, p; #ifdef DEVELOP INT32 pstartmap = 1; // default to first loaded map (Test Run) #else INT32 pstartmap = 0; // default to random map (0 is not a valid map number) #endif boolean autostart = false; + INT32 newgametype = -1; /* break the version string into version numbers, for netplay */ D_ConvertVersionNumbers(); @@ -1368,7 +1363,7 @@ void D_SRB2Main(void) /* and downloads in a subdirectory */ snprintf(downloaddir, sizeof downloaddir, "%s%s%s", - addonsdir, PATHSEP, "downloads"); + addonsdir, PATHSEP, DOWNLOADDIR_PART); // rand() needs seeded regardless of password srand((unsigned int)time(NULL)); @@ -1520,11 +1515,6 @@ void D_SRB2Main(void) CON_SetLoadingProgress(LOADED_HUINIT); - memset(timelimits, 0, sizeof(timelimits)); - memset(pointlimits, 0, sizeof(pointlimits)); - - timelimits[GT_BATTLE] = 2; - D_RegisterServerCommands(); D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame R_RegisterEngineStuff(); @@ -1532,14 +1522,14 @@ void D_SRB2Main(void) I_RegisterSysCommands(); + M_Init(); + //--------------------------------------------------------- CONFIG.CFG M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()" // Load Profiles now that default controls have been defined PR_LoadProfiles(); // load control profiles - M_Init(); - #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen #endif @@ -1801,8 +1791,6 @@ void D_SRB2Main(void) if (M_CheckParm("-gametype") && M_IsNextParm()) { // from Command_Map_f - INT32 j; - INT16 newgametype = -1; const char *sgametype = M_GetNextParm(); newgametype = G_GetGametypeByName(sgametype); @@ -1810,7 +1798,7 @@ void D_SRB2Main(void) if (newgametype == -1) // reached end of the list with no match { j = atoi(sgametype); // assume they gave us a gametype number, which is okay too - if (j >= 0 && j < gametypecount) + if (j >= 0 && j < numgametypes) newgametype = (INT16)j; } @@ -1824,7 +1812,6 @@ void D_SRB2Main(void) if (M_CheckParm("-skill") && M_IsNextParm()) { - INT32 j; INT16 newskill = -1; const char *sskill = M_GetNextParm(); @@ -1877,11 +1864,20 @@ void D_SRB2Main(void) if (grandprixinfo.gp == true && mapheaderinfo[pstartmap-1]) { - if (mapheaderinfo[pstartmap-1]->typeoflevel & TOL_SPECIAL) + if (newgametype == -1) { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - grandprixinfo.eventmode = GPEVENT_SPECIAL; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[pstartmap-1]->typeoflevel); + if (newgametype != -1) + { + j = gametype; + G_SetGametype(newgametype); + D_GameTypeChanged(j); + } + + if (gametyperules & (GTR_BOSS|GTR_CATCHER)) + grandprixinfo.eventmode = GPEVENT_SPECIAL; + else if (gametype != GT_RACE) + grandprixinfo.eventmode = GPEVENT_BONUS; } G_SetUsedCheats(); diff --git a/src/d_main.h b/src/d_main.h index 75a8e4b25..0b4f6f19d 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -29,6 +29,7 @@ extern char srb2home[256]; //Alam: My Home extern boolean usehome; //Alam: which path? extern const char *pandf; //Alam: how to path? extern char srb2path[256]; //Alam: SRB2's Home +extern char addonsdir[MAX_WADPATH]; // Where addons are stored // the infinite loop of D_SRB2Loop() called from win_main for windows version void D_SRB2Loop(void) FUNCNORETURN; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 975c165ad..97b71478e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -57,7 +57,6 @@ #include "k_color.h" #include "k_respawn.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_follower.h" #include "doomstat.h" #include "deh_tables.h" @@ -399,10 +398,6 @@ consvar_t cv_kartbumpers = CVAR_INIT ("battlebumpers", "3", CV_NETVAR, kartbumpe consvar_t cv_kartfrantic = CVAR_INIT ("franticitems", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, KartFrantic_OnChange); static CV_PossibleValue_t kartencore_cons_t[] = {{-1, "Auto"}, {0, "Off"}, {1, "On"}, {0, NULL}}; consvar_t cv_kartencore = CVAR_INIT ("encore", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartencore_cons_t, KartEncore_OnChange); -static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}}; -consvar_t cv_kartvoterulechanges = CVAR_INIT ("voterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL); -static CV_PossibleValue_t kartgametypepreference_cons_t[] = {{-1, "None"}, {GT_RACE, "Race"}, {GT_BATTLE, "Battle"}, {0, NULL}}; -consvar_t cv_kartgametypepreference = CVAR_INIT ("gametypepreference", "None", CV_NETVAR, kartgametypepreference_cons_t, NULL); static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Percentage"}, {2, "Kilometers"}, {3, "Miles"}, {4, "Fracunits"}, {0, NULL}}; consvar_t cv_kartspeedometer = CVAR_INIT ("speedometer", "Percentage", CV_SAVE, kartspeedometer_cons_t, NULL); // use tics in display static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; @@ -485,10 +480,6 @@ consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_NETVAR|CV_CALL|CV_NO static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {MAX_LAPS, "MAX"}, {0, "Map default"}, {0, NULL}}; consvar_t cv_numlaps = CVAR_INIT ("numlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, numlaps_cons_t, NumLaps_OnChange); -// Point and time limits for every gametype -INT32 pointlimits[NUMGAMETYPES]; -INT32 timelimits[NUMGAMETYPES]; - consvar_t cv_forceskin = CVAR_INIT ("forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange); consvar_t cv_downloading = CVAR_INIT ("downloading", "On", 0, CV_OnOff, NULL); @@ -557,13 +548,11 @@ char timedemo_csv_id[256]; boolean timedemo_quit; INT16 gametype = GT_RACE; -UINT32 gametyperules = 0; -INT16 gametypecount = GT_FIRSTFREESLOT; +INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; UINT8 splitscreen = 0; -boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS]; // Scheduled commands. @@ -2530,19 +2519,11 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN))) + if ((netgame || multiplayer) && (grandprixinfo.gp != false)) FLS = false; // Too lazy to change the input value for every instance of this function....... - if (bossinfo.boss == true) - { - pencoremode = bossinfo.encore; - } - else if (specialStage.active == true) - { - pencoremode = specialStage.encore; - } - else if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true) { pencoremode = grandprixinfo.encore; } @@ -2596,14 +2577,13 @@ void D_SetupVote(void) UINT8 buf[5*2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes UINT8 *p = buf; INT32 i; - UINT8 gt = (cv_kartgametypepreference.value == -1) ? gametype : cv_kartgametypepreference.value; - UINT8 secondgt = G_SometimesGetDifferentGametype(gt); + UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametypedefaultrules[gt] & GTR_CIRCUIT)) - WRITEUINT8(p, (gt|VOTEMODIFIER_ENCORE)); + if ((cv_kartencore.value == 1) && (gametyperules & GTR_ENCORE)) + WRITEUINT8(p, (gametype|VOTEMODIFIER_ENCORE)); else - WRITEUINT8(p, gt); + WRITEUINT8(p, gametype); WRITEUINT8(p, secondgt); secondgt &= ~VOTEMODIFIER_ENCORE; @@ -2613,9 +2593,9 @@ void D_SetupVote(void) if (i == 2) // sometimes a different gametype m = G_RandMap(G_TOLFlag(secondgt), prevmap, ((secondgt != gametype) ? 2 : 0), 0, true, votebuffer); else if (i >= 3) // unknown-random and formerly force-unknown MAP HELL - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, (i-2), (i < 4), votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, (i-2), (i < 4), votebuffer); else - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, 0, true, votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, true, votebuffer); if (i < 3) votebuffer[i] = m; WRITEUINT16(p, m); @@ -2758,13 +2738,7 @@ static void Command_Map_f(void) if (option_gametype) { - if (!multiplayer) - { - CONS_Printf(M_GetText( - "You can't switch gametypes in single player!\n")); - return; - } - else if (COM_Argc() < option_gametype + 2)/* no argument after? */ + if (COM_Argc() < option_gametype + 2)/* no argument after? */ { CONS_Alert(CONS_ERROR, "No gametype name follows parameter '%s'.\n", @@ -2822,7 +2796,7 @@ static void Command_Map_f(void) if (isdigit(gametypename[0])) { d = atoi(gametypename); - if (d >= 0 && d < gametypecount) + if (d >= 0 && d < numgametypes) newgametype = d; else { @@ -2830,7 +2804,7 @@ static void Command_Map_f(void) "Gametype number %d is out of range. Use a number between" " 0 and %d inclusive. ...Or just use the name. :v\n", d, - gametypecount-1); + numgametypes-1); Z_Free(realmapname); Z_Free(mapname); return; @@ -2839,13 +2813,23 @@ static void Command_Map_f(void) else { CONS_Alert(CONS_ERROR, - "'%s' is not a gametype.\n", + "'%s' is not a valid gametype.\n", gametypename); Z_Free(realmapname); Z_Free(mapname); return; } } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } else if (!Playing()) { @@ -2853,7 +2837,15 @@ static void Command_Map_f(void) if (mapheaderinfo[newmapnum-1]) { // Let's just guess so we don't have to specify the gametype EVERY time... - newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & (TOL_BATTLE|TOL_BOSS)) ? GT_BATTLE : GT_RACE; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[newmapnum-1]->typeoflevel); + + if (newgametype == -1) + { + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support any known gametype!\n"), realmapname, G_BuildMapName(newmapnum)); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } } @@ -2865,6 +2857,8 @@ static void Command_Map_f(void) if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) { CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + Z_Free(realmapname); + Z_Free(mapname); return; } } @@ -2885,8 +2879,7 @@ static void Command_Map_f(void) mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) )) { - CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), - (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), gametypes[newgametype]->name); Z_Free(realmapname); Z_Free(mapname); return; @@ -2895,8 +2888,7 @@ static void Command_Map_f(void) { fromlevelselect = ( netgame || multiplayer ) && - newgametype == gametype && - gametypedefaultrules[newgametype] & GTR_CAMPAIGN; + grandprixinfo.gp != false; } } @@ -2952,35 +2944,18 @@ static void Command_Map_f(void) grandprixinfo.eventmode = GPEVENT_NONE; - if (newgametype == GT_BATTLE) + if (gametypes[newgametype]->rules & (GTR_BOSS|GTR_CATCHER)) + { + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + else if (newgametype != GT_RACE) { grandprixinfo.eventmode = GPEVENT_BONUS; - - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_BOSS) - { - bossinfo.boss = true; - bossinfo.encore = newencoremode; - } - else - { - bossinfo.boss = false; - K_ResetBossInfo(); - } } - else + + if (!Playing()) { - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage - { - specialStage.active = true; - specialStage.encore = newencoremode; - grandprixinfo.eventmode = GPEVENT_SPECIAL; - } - else - { - specialStage.active = false; - } + multiplayer = true; } } @@ -3029,12 +3004,12 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) gametype = READUINT8(*cp); G_SetGametype(gametype); // I fear putting that macro as an argument - if (gametype < 0 || gametype >= gametypecount) + if (gametype < 0 || gametype >= numgametypes) gametype = lastgametype; else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype - if (!(gametyperules & GTR_CIRCUIT) && !bossinfo.boss) + if (!(gametyperules & GTR_ENCORE)) pencoremode = false; skipprecutscene = ((flags & (1<<2)) != 0); @@ -3086,9 +3061,10 @@ static void Command_RandomMap(void) { INT32 oldmapnum; INT32 newmapnum; - INT32 newgametype; - boolean newencoremode; + INT32 newgametype = (Playing() ? gametype : menugametype); + boolean newencore = false; boolean newresetplayers; + size_t option_gametype; if (client && !IsPlayerAdmin(consoleplayer)) { @@ -3096,13 +3072,69 @@ static void Command_RandomMap(void) return; } + if ((option_gametype = COM_CheckPartialParm("-g"))) + { + const char *gametypename; + + if (COM_Argc() < option_gametype + 2)/* no argument after? */ + { + CONS_Alert(CONS_ERROR, + "No gametype name follows parameter '%s'.\n", + COM_Argv(option_gametype)); + return; + } + + // new gametype value + // use current one by default + gametypename = COM_Argv(option_gametype + 1); + + newgametype = G_GetGametypeByName(gametypename); + + if (newgametype == -1) // reached end of the list with no match + { + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) + { + INT16 d = atoi(gametypename); + if (d >= 0 && d < numgametypes) + newgametype = d; + else + { + CONS_Alert(CONS_ERROR, + "Gametype number %d is out of range. Use a number between" + " 0 and %d inclusive. ...Or just use the name. :v\n", + d, + numgametypes-1); + return; + } + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a valid gametype.\n", + gametypename); + return; + } + } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + return; + } + } + // TODO: Handle singleplayer conditions. // The existing ones are way too annoyingly complicated and "anti-cheat" for my tastes. if (Playing()) { - newgametype = gametype; - newencoremode = encoremode; + if (cv_kartencore.value == 1 && (gametypes[newgametype]->rules & GTR_ENCORE)) + { + newencore = true; + } newresetplayers = false; if (gamestate == GS_LEVEL) @@ -3116,14 +3148,12 @@ static void Command_RandomMap(void) } else { - newgametype = cv_dummygametype.value; // Changed from cv_newgametype to match newmenus - newencoremode = false; newresetplayers = true; oldmapnum = -1; } newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, 0, 0, false, NULL) + 1; - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); + D_MapChange(newmapnum, newgametype, newencore, newresetplayers, 0, false, false); } static void Command_RestartLevel(void) @@ -3742,7 +3772,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) // Clear player score and rings if a spectator. if (players[playernum].spectator) { - if (gametyperules & GTR_BUMPERS) // SRB2kart + if (gametyperules & GTR_POINTLIMIT) // SRB2kart { players[playernum].roundscore = 0; K_CalculateBattleWanted(); @@ -4774,16 +4804,13 @@ static void Command_Version_f(void) else // 16-bit? 128-bit? CONS_Printf("Bits Unknown "); + CONS_Printf("%s ", comptype); + // No ASM? #ifdef NOASM CONS_Printf("\x85" "NOASM " "\x80"); #endif - // Debug build -#ifdef _DEBUG - CONS_Printf("\x85" "DEBUG " "\x80"); -#endif - // DEVELOP build #if defined(TESTERS) CONS_Printf("\x88" "TESTERS " "\x80"); @@ -4812,15 +4839,9 @@ static void Command_ShowGametype_f(void) { const char *gametypestr = NULL; - if (!(netgame || multiplayer)) // print "Single player" instead of "Race" - { - CONS_Printf(M_GetText("Current gametype is %s\n"), "Single Player"); - return; - } - // get name string for current gametype - if (gametype >= 0 && gametype < gametypecount) - gametypestr = Gametype_Names[gametype]; + if (gametype >= 0 && gametype < numgametypes) + gametypestr = gametypes[gametype]->name; if (gametypestr) CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr); @@ -4999,10 +5020,10 @@ void D_GameTypeChanged(INT32 lastgametype) { const char *oldgt = NULL, *newgt = NULL; - if (lastgametype >= 0 && lastgametype < gametypecount) - oldgt = Gametype_Names[lastgametype]; - if (gametype >= 0 && lastgametype < gametypecount) - newgt = Gametype_Names[gametype]; + if (lastgametype >= 0 && lastgametype < numgametypes) + oldgt = gametypes[lastgametype]->name; + if (gametype >= 0 && gametype < numgametypes) + newgt = gametypes[gametype]->name; if (oldgt && newgt) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); @@ -5014,11 +5035,11 @@ void D_GameTypeChanged(INT32 lastgametype) { if (!cv_timelimit.changed) // user hasn't changed limits { - CV_SetValue(&cv_timelimit, timelimits[gametype]); + CV_SetValue(&cv_timelimit, gametypes[gametype]->timelimit); } if (!cv_pointlimit.changed) { - CV_SetValue(&cv_pointlimit, pointlimits[gametype]); + CV_SetValue(&cv_pointlimit, gametypes[gametype]->pointlimit); } } @@ -5313,11 +5334,29 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // Strip illegal Encore flag. if ((gt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypes[(gt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { gt &= ~VOTEMODIFIER_ENCORE; } + if ((gt & ~VOTEMODIFIER_ENCORE) >= numgametypes) + { + gt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal gametype ID %d not found (numgametypes = %d)", gt, numgametypes); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad gametype ID %d received from %s\n"), gt, player_names[playernum]); + return; + } + + if ((secondgt & ~VOTEMODIFIER_ENCORE) >= numgametypes) + { + secondgt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal second gametype ID %d not found (numgametypes = %d)", secondgt, numgametypes); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad second gametype ID %d received from %s\n"), secondgt, player_names[playernum]); + return; + } + for (i = 0; i < 4; i++) { tempvotelevels[i][0] = (UINT16)READUINT16(*cp); @@ -5333,11 +5372,11 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // If third entry has an illelegal Encore flag... (illelegal!?) if ((secondgt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypes[(secondgt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { secondgt &= ~VOTEMODIFIER_ENCORE; // Apply it to the second entry instead, gametype permitting! - if (gametypedefaultrules[gt] & GTR_CIRCUIT) + if (gametypes[gt]->rules & GTR_ENCORE) { tempvotelevels[1][1] |= VOTEMODIFIER_ENCORE; } @@ -5737,11 +5776,11 @@ void Command_Retry_f(void) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); } - else if (grandprixinfo.gp == false && bossinfo.boss == false) + else if (grandprixinfo.gp == false) { CONS_Printf(M_GetText("This only works in singleplayer games.\n")); } - else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + else if (grandprixinfo.eventmode == GPEVENT_BONUS) { CONS_Printf(M_GetText("You can't retry right now!\n")); } @@ -5778,7 +5817,7 @@ static void Command_Togglemodified_f(void) static void Command_Archivetest_f(void) { - savebuffer_t save; + savebuffer_t save = {0}; UINT32 i, wrote; thinker_t *th; if (gamestate != GS_LEVEL) @@ -5794,9 +5833,11 @@ static void Command_Archivetest_f(void) ((mobj_t *)th)->mobjnum = i++; // allocate buffer - save.size = 1024; - save.buffer = save.p = ZZ_Alloc(save.size); - save.end = save.buffer + save.size; + if (P_SaveBufferAlloc(&save, 1024) == false) + { + CONS_Printf("Unable to allocate buffer.\n"); + return; + } // test archive CONS_Printf("LUA_Archive...\n"); @@ -5814,10 +5855,12 @@ static void Command_Archivetest_f(void) LUA_UnArchive(&save, true); i = READUINT8(save.p); if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer)) + { CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer)); + } // free buffer - Z_Free(save.buffer); + P_SaveBufferFree(&save); CONS_Printf("Done. No crash.\n"); } #endif diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 8cf262df0..f0fbf59d8 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -83,8 +83,6 @@ extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; extern consvar_t cv_kartfrantic; extern consvar_t cv_kartencore; -extern consvar_t cv_kartvoterulechanges; -extern consvar_t cv_kartgametypepreference; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; extern consvar_t cv_kartbot; diff --git a/src/d_netfil.c b/src/d_netfil.c index 64b605d12..5238f7028 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -99,7 +99,6 @@ static filetran_t transfer[MAXNETNODES]; INT32 fileneedednum; // Number of files needed to join the server fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files static tic_t lasttimeackpacketsent = 0; -char downloaddir[512] = "DOWNLOAD"; // For resuming failed downloads typedef struct diff --git a/src/d_netfil.h b/src/d_netfil.h index 904c1862a..26bffbc18 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -62,7 +62,8 @@ struct fileneeded_t extern INT32 fileneedednum; extern fileneeded_t fileneeded[MAX_WADFILES]; -extern char downloaddir[512]; +#define DOWNLOADDIR_PART "downloads" +extern char downloaddir[]; extern INT32 lastfilenum; extern INT32 downloadcompletednum; diff --git a/src/deh_lua.c b/src/deh_lua.c index 56bfdf4ea..f131617c6 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -309,11 +309,16 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("GT_", word, 3)) { p = word; - for (i = 0; Gametype_ConstantNames[i]; i++) - if (fastcmp(p, Gametype_ConstantNames[i])) { + i = 0; + while (gametypes[i] != NULL) + { + if (fastcmp(p, gametypes[i]->constant)) + { lua_pushinteger(L, i); return 1; } + i++; + } if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word); return 0; } diff --git a/src/deh_soc.c b/src/deh_soc.c index 1d7b94214..4fb40b087 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -764,13 +764,13 @@ void readgametype(MYFILE *f, char *gtname) char *tmp; INT32 i, j; - INT16 newgtidx = 0; + gametype_t *newgametype = NULL; + UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; char gtconst[MAXLINELEN]; // Empty strings. @@ -821,12 +821,6 @@ void readgametype(MYFILE *f, char *gtname) newgtpointlimit = (INT32)i; else if (fastcmp(word, "DEFAULTTIMELIMIT")) newgttimelimit = (INT32)i; - // Rankings type - else if (fastcmp(word, "RANKINGTYPE")) - { - // Case insensitive - newgtrankingstype = (int)get_number(word2); - } // Intermission type else if (fastcmp(word, "INTERMISSIONTYPE")) { @@ -879,36 +873,54 @@ void readgametype(MYFILE *f, char *gtname) Z_Free(word2lwr); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) + if (numgametypes == GT_LASTFREESLOT) { - CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n"); + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); + } + + if (gtname[0] == '\0') + { + deh_warning("Custom gametype must have a name"); + return; + } + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + { + deh_warning("Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + return; + } + + for (i = 0; i < numgametypes; i++) + if (fastcmp(gtname, gametypes[i]->name)) + break; + + if (i < numgametypes) + { + deh_warning("Custom gametype \"%s\"'s name is already in use", gtname); return; } // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname); - - // Write the constant name. if (gtconst[0] == '\0') strncpy(gtconst, gtname, MAXLINELEN); - G_AddGametypeConstant(newgtidx, (const char *)gtconst); - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); + newgametype->name = Z_StrDup((const char *)gtname); + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant((const char *)gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + gametypes[numgametypes++] = newgametype; + + CONS_Printf("Added gametype %s\n", gtname); } void readlevelheader(MYFILE *f, char * name) @@ -1128,7 +1140,7 @@ void readlevelheader(MYFILE *f, char * name) } else if (fastcmp(word, "TYPEOFLEVEL")) { - if (i) // it's just a number + if (i || isdigit(word2[0])) // it's just a number mapheaderinfo[num]->typeoflevel = (UINT32)i; else { @@ -1267,12 +1279,12 @@ void readlevelheader(MYFILE *f, char * name) else mapheaderinfo[num]->menuflags &= ~LF2_NOTIMEATTACK; } - else if (fastcmp(word, "VISITNEEDED")) + else if (fastcmp(word, "FINISHNEEDED")) { if (i || word2[0] == 'T' || word2[0] == 'Y') - mapheaderinfo[num]->menuflags |= LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags |= LF2_FINISHNEEDED; else - mapheaderinfo[num]->menuflags &= ~LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags &= ~LF2_FINISHNEEDED; } else if (fastcmp(word, "GRAVITY")) mapheaderinfo[num]->gravity = FLOAT_TO_FIXED(atof(word2)); @@ -2267,6 +2279,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) unlockables[num].type = SECRET_BREAKTHECAPSULES; + else if (fastcmp(word2, "SPECIALATTACK")) + unlockables[num].type = SECRET_SPECIALATTACK; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; else if (fastcmp(word2, "ALTTITLE")) @@ -3670,7 +3684,7 @@ sfxenum_t get_sfx(const char *word) return atoi(word); if (fastncmp("GT_",word,3)) word += 3; // take off the GT_ - for (i = 0; i < NUMGAMETYPES; i++) + for (i = 0; i < MAXGAMETYPES; i++) if (fastcmp(word, Gametype_ConstantNames[i]+3)) return i; deh_warning("Couldn't find gametype named 'GT_%s'",word); diff --git a/src/deh_tables.c b/src/deh_tables.c index 8e7bee9c1..474db3b43 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3283,6 +3283,24 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi //"S_ITEMCAPSULE_BOTTOM", //"S_ITEMCAPSULE_INSIDE", + "S_MONITOR_DAMAGE", + "S_MONITOR_DEATH", + "S_MONITOR_SCREEN1A", + "S_MONITOR_SCREEN1B", + "S_MONITOR_SCREEN2A", + "S_MONITOR_SCREEN2B", + "S_MONITOR_SCREEN3A", + "S_MONITOR_SCREEN3B", + "S_MONITOR_SCREEN4A", + "S_MONITOR_SCREEN4B", + "S_MONITOR_STAND", + "S_MONITOR_CRACKA", + "S_MONITOR_CRACKB", + + "S_MONITOR_BIG_SHARD", + "S_MONITOR_SMALL_SHARD", + "S_MONITOR_TWINKLE", + "S_MAGICIANBOX", "S_MAGICIANBOXTOP", "S_MAGICIANBOXBOTTOM", @@ -5298,6 +5316,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLOATINGITEM", "MT_ITEMCAPSULE", "MT_ITEMCAPSULE_PART", + "MT_MONITOR", + "MT_MONITOR_PART", + "MT_MONITOR_SHARD", "MT_MAGICIANBOX", "MT_SIGNSPARKLE", @@ -5791,38 +5812,36 @@ const char *const PLAYERFLAG_LIST[] = { }; const char *const GAMETYPERULE_LIST[] = { - "CAMPAIGN", - "RINGSLINGER", - "SPECTATORS", - "LIVES", - "TEAMS", - "FIRSTPERSON", + "CIRCUIT", + "BOTS", + + "BUMPERS", + "SPHERES", + "CLOSERPLAYERS", + + "BATTLESTARTS", + "PAPERITEMS", "POWERSTONES", - "TEAMFLAGS", - "FRIENDLY", - "SPECIALSTAGES", - "EMERALDTOKENS", - "EMERALDHUNT", - "RACE", - "TAG", + "KARMA", + "ITEMARROWS", + + "CAPSULES", + "CATCHER", + "ROLLINGSTART", + "SPECIALSTART", + "BOSS", + "POINTLIMIT", "TIMELIMIT", "OVERTIME", - "HURTMESSAGES", - "FRIENDLYFIRE", - "STARTCOUNTDOWN", - "HIDEFROZEN", - "BLINDFOLDED", - "RESPAWNDELAY", - "PITYSHIELD", - "DEATHPENALTY", - "NOSPECTATORSPAWN", - "DEATHMATCHSTARTS", - "SPAWNINVUL", - "SPAWNENEMIES", - "ALLOWEXIT", - "NOTITLECARD", - "CUTSCENES", + "ENCORE", + + "TEAMS", + "NOTEAMS", + "TEAMSTARTS", + + "NOMP", + "NOCUPSELECT", NULL }; @@ -6305,7 +6324,7 @@ struct int_const_s const INT_CONST[] = { {"LF2_HIDEINMENU",LF2_HIDEINMENU}, {"LF2_HIDEINSTATS",LF2_HIDEINSTATS}, {"LF2_NOTIMEATTACK",LF2_NOTIMEATTACK}, - {"LF2_VISITNEEDED",LF2_VISITNEEDED}, + {"LF2_FINISHNEEDED",LF2_FINISHNEEDED}, // Emeralds {"EMERALD_CHAOS1",EMERALD_CHAOS1}, @@ -6403,9 +6422,9 @@ struct int_const_s const INT_CONST[] = { // Intermission types {"int_none",int_none}, - {"int_race",int_race}, - {"int_battle",int_battle}, - {"int_battletime", int_battletime}, + {"int_time",int_time}, + {"int_score",int_score}, + {"int_scoreortimeattack", int_scoreortimeattack}, // Jingles (jingletype_t) {"JT_NONE",JT_NONE}, diff --git a/src/discord.c b/src/discord.c index 28381d250..9d6011c11 100644 --- a/src/discord.c +++ b/src/discord.c @@ -499,8 +499,8 @@ void DRPC_UpdatePresence(void) else { snprintf(detailstr, 48, "%s%s%s", - gametype_cons_t[gametype].strvalue, - (gametype == GT_RACE) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", + gametypes[gametype]->name, + (gametyperules & GTR_CIRCUIT) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", (encoremode == true) ? " | Encore" : "" ); discordPresence.details = detailstr; diff --git a/src/doomdef.h b/src/doomdef.h index 09a5efa4a..72ba17a84 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -664,7 +664,8 @@ UINT32 quickncasehash (const char *p, size_t n) // Compile date and time and revision. extern const char *compdate, *comptime, *comprevision, *compbranch; -extern int compuncommitted; +extern int compuncommitted, compoptimized; +extern const char *comptype; // Disabled code and code under testing // None of these that are disabled in the normal build are guaranteed to work perfectly diff --git a/src/doomstat.h b/src/doomstat.h index 3457dcd2c..39b5a73da 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -135,9 +135,9 @@ extern boolean usedCheats; extern boolean imcontinuing; // Temporary flag while continuing extern boolean metalrecording; -#define ATTACKING_NONE 0 -#define ATTACKING_TIME 1 -#define ATTACKING_CAPSULES 2 +#define ATTACKING_NONE 0 +#define ATTACKING_TIME 1 +#define ATTACKING_LAP (1<<1) extern UINT8 modeattacking; // menu demo things @@ -151,15 +151,9 @@ extern boolean addedtogame; // true after the server has added you // Only true if >1 player. netgame => multiplayer but not (multiplayer=>netgame) extern boolean multiplayer; -extern INT16 gametype; - -extern UINT32 gametyperules; -extern INT16 gametypecount; - extern UINT8 splitscreen; extern int r_splitscreen; -extern boolean circuitmap; // Does this level have 'circuit mode'? extern boolean fromlevelselect; extern boolean forceresetplayers, deferencoremode; @@ -449,71 +443,97 @@ struct mapheader_t #define LF_SECTIONRACE (1<<2) ///< Section race level #define LF_SUBTRACTNUM (1<<3) ///< Use subtractive position number (for bright levels) -#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu -#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen -#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes -#define LF2_VISITNEEDED (1<<3) ///< Not available in Time Attack modes until you visit the level +#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu +#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen +#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes +#define LF2_FINISHNEEDED (1<<3) ///< Not available in Time Attack modes until you beat the level extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; // Gametypes -#define NUMGAMETYPEFREESLOTS 128 +#define NUMGAMETYPEFREESLOTS (MAXGAMETYPES-GT_FIRSTFREESLOT) +#define MAXGAMETYPELENGTH 32 enum GameType { GT_RACE = 0, GT_BATTLE, + GT_SPECIAL, + GT_VERSUS, GT_FIRSTFREESLOT, - GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1, - NUMGAMETYPES + GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. + MAXGAMETYPES }; -// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c +// If you alter this list, update defaultgametypes and *gametypes in g_game.c + +#define MAXTOL (1<<31) +#define NUMBASETOLNAMES (5) +#define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) + +struct gametype_t +{ + const char *name; + const char *constant; + UINT32 rules; + UINT32 tol; + UINT8 intermission; + INT32 pointlimit; + INT32 timelimit; +}; + +extern gametype_t *gametypes[MAXGAMETYPES+1]; +extern INT16 numgametypes; + +extern INT16 gametype; // Gametype rules enum GameTypeRules { // Race rules - GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. - GTR_BOTS = 1<<2, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. + GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. + GTR_BOTS = 1<<1, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. // Battle gametype rules - GTR_BUMPERS = 1<<3, // Enables the bumper health system - GTR_SPHERES = 1<<4, // Replaces rings with blue spheres - GTR_PAPERITEMS = 1<<5, // Replaces item boxes with paper item spawners - GTR_WANTED = 1<<6, // unused - GTR_KARMA = 1<<7, // Enables the Karma system if you're out of bumpers - GTR_ITEMARROWS = 1<<8, // Show item box arrows above players - GTR_CAPSULES = 1<<9, // Enables the wanted anti-camping system - GTR_BATTLESTARTS = 1<<10, // Use Battle Mode start positions. + GTR_BUMPERS = 1<<2, // Enables the bumper health system + GTR_SPHERES = 1<<3, // Replaces rings with blue spheres + GTR_CLOSERPLAYERS = 1<<4, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos - GTR_POINTLIMIT = 1<<11, // Reaching point limit ends the round - GTR_TIMELIMIT = 1<<12, // Reaching time limit ends the round - GTR_OVERTIME = 1<<13, // Allow overtime behavior + GTR_BATTLESTARTS = 1<<5, // Use Battle Mode start positions. + GTR_PAPERITEMS = 1<<6, // Replaces item boxes with paper item spawners + GTR_POWERSTONES = 1<<7, // Battle Emerald collectables. + GTR_KARMA = 1<<8, // Enables the Karma system if you're out of bumpers + GTR_ITEMARROWS = 1<<9, // Show item box arrows above players - // Custom gametype rules - GTR_TEAMS = 1<<14, // Teams are forced on - GTR_NOTEAMS = 1<<15, // Teams are forced off - GTR_TEAMSTARTS = 1<<16, // Use team-based start positions + // Bonus gametype rules + GTR_CAPSULES = 1<<10, // Can enter Break The Capsules mode + GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) + GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) + GTR_SPECIALSTART = 1<<13, // White fade instant start + GTR_BOSS = 1<<14, // Boss intro and spawning - // Grand Prix rules - GTR_CAMPAIGN = 1<<17, // Handles cup-based progression - GTR_LIVES = 1<<18, // Lives system, players are forced to spectate during Game Over. - GTR_SPECIALBOTS = 1<<19, // Bot difficulty gets stronger between rounds, and the rival system is enabled. + // General purpose rules + GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round + GTR_TIMELIMIT = 1<<16, // Reaching time limit ends the round + GTR_OVERTIME = 1<<17, // Allow overtime behavior + GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping - GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. + GTR_TEAMS = 1<<19, // Teams are forced on + GTR_NOTEAMS = 1<<20, // Teams are forced off + GTR_TEAMSTARTS = 1<<21, // Use team-based start positions + + GTR_NOMP = 1<<22, // No multiplayer + GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. // free: to and including 1<<31 }; +// Remember to update GAMETYPERULE_LIST in deh_soc.c -// String names for gametypes -extern const char *Gametype_Names[NUMGAMETYPES]; -extern const char *Gametype_ConstantNames[NUMGAMETYPES]; +#define GTR_FORBIDMP (GTR_NOMP|GTR_CATCHER|GTR_BOSS) -// Point and time limits for every gametype -extern INT32 pointlimits[NUMGAMETYPES]; -extern INT32 timelimits[NUMGAMETYPES]; +// TODO: replace every instance +#define gametyperules (gametypes[gametype]->rules) // TypeOfLevel things enum TypeOfLevel @@ -527,9 +547,10 @@ enum TypeOfLevel // Modifiers TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD }; +// Make sure to update TYPEOFLEVEL too #define MAXTOL (1<<31) -#define NUMBASETOLNAMES (4) +#define NUMBASETOLNAMES (5) #define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) struct tolinfo_t diff --git a/src/f_finale.c b/src/f_finale.c index e20deae1f..36ca452f9 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1889,6 +1889,52 @@ void F_StartTitleScreen(void) F_CacheTitleScreen(); } +void F_VersionDrawer(void) +{ + // An adapted thing from old menus - most games have version info on the title screen now... + INT32 texty = vid.height - 10*vid.dupy; + +#define addtext(f, str) {\ + V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ + texty -= 10*vid.dupy;\ +} + if (customversionstring[0] != '\0') + { + addtext(V_ALLOWLOWERCASE, customversionstring); + addtext(0, "Mod version:"); + } + else + { +// Development -- show revision / branch info +#if defined(TESTERS) + addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(HOSTTESTERS) + addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(DEVELOP) + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); +#else // Regular build + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); +#endif + if (compoptimized) + { + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s build", comptype)); + } + else + { + addtext(V_ALLOWLOWERCASE|V_ORANGEMAP, va("%s build (no optimizations)", comptype)); + } + + if (compuncommitted) + { + addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); + } + } +#undef addtext +} + // (no longer) De-Demo'd Title Screen void F_TitleScreenDrawer(void) { @@ -1959,39 +2005,7 @@ void F_TitleScreenDrawer(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_copyright, NULL); - // An adapted thing from old menus - most games have version info on the title screen now... - { - INT32 texty = vid.height - 10*vid.dupy; -#define addtext(f, str) {\ - V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ - texty -= 10*vid.dupy;\ -} - if (customversionstring[0] != '\0') - { - addtext(V_ALLOWLOWERCASE, customversionstring); - addtext(0, "Mod version:"); - } - else - { -// Development -- show revision / branch info -#if defined(TESTERS) - addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(HOSTTESTERS) - addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(DEVELOP) - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); -#else // Regular build - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); -#endif - if (compuncommitted) - addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); - } -#undef addtext - } - + F_VersionDrawer(); break; } diff --git a/src/f_finale.h b/src/f_finale.h index a42228625..ca1108214 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -60,6 +60,8 @@ void F_EndingDrawer(void); void F_CreditTicker(void); void F_CreditDrawer(void); +void F_VersionDrawer(void); + void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer); void F_CutsceneDrawer(void); void F_EndCutScene(void); diff --git a/src/filesrch.c b/src/filesrch.c index 9db81ad37..6f18cdd83 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -310,13 +310,13 @@ closedir (DIR * dirp) } #endif -static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"}, +static CV_PossibleValue_t addons_cons_t[] = {{0, "Addons"}, #if 1 - {1, "HOME"}, {2, "RINGRACERS"}, + {1, "HOME"}, {2, "IWAD"}, #endif {3, "CUSTOM"}, {0, NULL}}; -consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange); +consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Addons", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange); consvar_t cv_addons_folder = CVAR_INIT ("addons_folder", "", CV_SAVE, NULL, NULL); static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}}; diff --git a/src/g_demo.c b/src/g_demo.c index 579d786f5..a90771e48 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -70,7 +70,7 @@ boolean noblit; // for comparative timing purposes tic_t demostarttime; // for comparative timing purposes static char demoname[MAX_WADPATH]; -static savebuffer_t demobuf; +static savebuffer_t demobuf = {0}; static UINT8 *demotime_p, *demoinfo_p; static UINT8 demoflags; boolean demosynced = true; // console warning message @@ -119,18 +119,14 @@ demoghost *ghosts = NULL; #define DEMOVERSION 0x0007 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" -#define DF_GHOST 0x01 // This demo contains ghost data too! -#define DF_TIMEATTACK 0x02 // This demo is from Time Attack and contains its final completion time & best lap! -#define DF_BREAKTHECAPSULES 0x04 // This demo is from Break the Capsules and contains its final completion time! -#define DF_ATTACKMASK 0x06 // This demo is from ??? attack and contains ??? +#define DF_ATTACKMASK (ATTACKING_TIME|ATTACKING_LAP) // This demo contains time/lap data -// 0x08 free +#define DF_GHOST 0x08 // This demo contains ghost data too! #define DF_NONETMP 0x10 // multiplayer but not netgame #define DF_LUAVARS 0x20 // this demo contains extra lua vars -#define DF_ATTACKSHIFT 1 #define DF_ENCORE 0x40 #define DF_MULTIPLAYER 0x80 // This demo was recorded in multiplayer mode! @@ -2017,12 +2013,10 @@ void G_RecordDemo(const char *name) maxsize = atoi(M_GetNextParm()) * 1024; // if (demobuf.buffer) -// P_SaveBufferFree(&demobuf); +// Z_Free(demobuf.buffer); - demobuf.size = maxsize; - demobuf.buffer = (UINT8 *)malloc(maxsize); + P_SaveBufferAlloc(&demobuf, maxsize); demobuf.p = NULL; - demobuf.end = demobuf.buffer + demobuf.size; demo.recording = true; } @@ -2034,10 +2028,8 @@ void G_RecordMetal(void) if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; - demobuf.size = maxsize; - demobuf.buffer = (UINT8 *)malloc(maxsize); + P_SaveBufferAlloc(&demobuf, maxsize); demobuf.p = NULL; - demobuf.end = demobuf.buffer + demobuf.size; metalrecording = true; } @@ -2345,10 +2337,19 @@ void G_BeginRecording(void) memset(name,0,sizeof(name)); demobuf.p = demobuf.buffer; - demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<name, MAXGAMETYPELENGTH); + WRITEUINT8(demobuf.p, numlaps); // file list @@ -2388,21 +2391,18 @@ void G_BeginRecording(void) // character list G_SaveDemoSkins(&demobuf.p); - switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) + if ((demoflags & DF_ATTACKMASK)) { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - demotime_p = demobuf.p; + demotime_p = demobuf.p; + + if (demoflags & ATTACKING_TIME) WRITEUINT32(demobuf.p,UINT32_MAX); // time + if (demoflags & ATTACKING_LAP) WRITEUINT32(demobuf.p,UINT32_MAX); // lap - break; - case ATTACKING_CAPSULES: // 2 - demotime_p = demobuf.p; - WRITEUINT32(demobuf.p,UINT32_MAX); // time - break; - default: // 3 - break; + } + else + { + demotime_p = NULL; } for (i = 0; i < PRNUMCLASS; i++) @@ -2606,18 +2606,15 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) { if (!demo.recording || !demotime_p) return; - if (demoflags & DF_TIMEATTACK) + if (demoflags & ATTACKING_TIME) { WRITEUINT32(demotime_p, ptime); + } + if (demoflags & ATTACKING_LAP) + { WRITEUINT32(demotime_p, plap); - demotime_p = NULL; - } - else if (demoflags & DF_BREAKTHECAPSULES) - { - WRITEUINT32(demotime_p, ptime); - (void)plap; - demotime_p = NULL; } + demotime_p = NULL; } // Returns bitfield: @@ -2628,7 +2625,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) { UINT8 *buffer,*p; UINT8 flags; - UINT32 oldtime, newtime, oldlap, newlap; + UINT32 oldtime = UINT32_MAX, newtime = UINT32_MAX; + UINT32 oldlap = UINT32_MAX, newlap = UINT32_MAX; UINT16 oldversion; size_t bufsize ATTRUNUSED; UINT8 c; @@ -2658,23 +2656,22 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // map md5 flags = READUINT8(p); // demoflags - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); G_SkipDemoSkins(&p); - aflags = flags & (DF_TIMEATTACK|DF_BREAKTHECAPSULES); + aflags = flags & DF_ATTACKMASK; I_Assert(aflags); - if (flags & DF_TIMEATTACK) - uselaps = true; // get around uninitalized error + if (aflags & ATTACKING_LAP) + uselaps = true; - newtime = READUINT32(p); + if (aflags & ATTACKING_TIME) + newtime = READUINT32(p); if (uselaps) newlap = READUINT32(p); - else - newlap = UINT32_MAX; Z_Free(buffer); @@ -2718,7 +2715,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // mapmd5 flags = READUINT8(p); - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); if (!(flags & aflags)) @@ -2730,11 +2727,10 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) G_SkipDemoSkins(&p); - oldtime = READUINT32(p); + if (flags & ATTACKING_TIME) + oldtime = READUINT32(p); if (uselaps) oldlap = READUINT32(p); - else - oldlap = 0; Z_Free(buffer); @@ -2764,7 +2760,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) UINT8 version, subversion, pdemoflags, worknumskins, skinid; democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; - char mapname[MAXMAPLUMPNAME]; + char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; INT32 i; if (!FIL_ReadFile(pdemo->filepath, &infobuffer)) @@ -2828,7 +2824,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo) return; } - pdemo->gametype = READUINT8(info_p); + READSTRINGN(info_p, gtname, sizeof(gtname)); // gametype + pdemo->gametype = G_GetGametypeByName(gtname); + pdemo->numlaps = READUINT8(info_p); pdemo->addonstatus = G_CheckDemoExtraFiles(&info_p, true); @@ -2940,9 +2938,11 @@ void G_DeferedPlayDemo(const char *name) void G_DoPlayDemo(char *defdemoname) { - UINT8 i, p, numslots = 0; + INT32 i; + UINT8 p, numslots = 0; lumpnum_t l; - char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; + char *n,*pdemoname; UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; @@ -2959,6 +2959,7 @@ void G_DoPlayDemo(char *defdemoname) follower[16] = '\0'; color[MAXCOLORNAME] = '\0'; + gtname[MAXGAMETYPELENGTH-1] = '\0'; // No demo name means we're restarting the current demo if (defdemoname == NULL) @@ -2982,7 +2983,7 @@ void G_DoPlayDemo(char *defdemoname) if (FIL_CheckExtension(defdemoname)) { //FIL_DefaultExtension(defdemoname, ".lmp"); - if (!FIL_ReadFile(defdemoname, &demobuf.buffer)) + if (P_SaveBufferFromFile(&demobuf, defdemoname) == false) { snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname); CONS_Alert(CONS_ERROR, "%s", msg); @@ -2990,20 +2991,20 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); return; } - demobuf.p = demobuf.buffer; } // load demo resource from WAD - else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) + else { - snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - gameaction = ga_nothing; - M_StartMessage(msg, NULL, MM_NOTHING); - return; - } - else // it's an internal demo - { - demobuf.buffer = demobuf.p = W_CacheLumpNum(l, PU_STATIC); + if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) + { + snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + gameaction = ga_nothing; + M_StartMessage(msg, NULL, MM_NOTHING); + return; + } + + P_SaveBufferFromLump(&demobuf, l); #if defined(SKIPERRORS) && !defined(DEVELOP) skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes... #endif @@ -3068,8 +3069,22 @@ void G_DoPlayDemo(char *defdemoname) demobuf.p += 16; // mapmd5 demoflags = READUINT8(demobuf.p); - gametype = READUINT8(demobuf.p); - G_SetGametype(gametype); + + READSTRINGN(demobuf.p, gtname, sizeof(gtname)); // gametype + i = G_GetGametypeByName(gtname); + if (i < 0) + { + snprintf(msg, 1024, M_GetText("%s is in a gametype that is not currently loaded and cannot be played.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(pdemoname); + Z_Free(demobuf.buffer); + demo.playback = false; + demo.title = false; + return; + } + G_SetGametype(i); + numlaps = READUINT8(demobuf.p); if (demo.title) // Titledemos should always play and ought to always be compatible with whatever wadlist is running. @@ -3142,7 +3157,7 @@ void G_DoPlayDemo(char *defdemoname) return; } - modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; + modeattacking = (demoflags & DF_ATTACKMASK); multiplayer = !!(demoflags & DF_MULTIPLAYER); demo.netgame = (multiplayer && !(demoflags & DF_NONETMP)); CON_ToggleOff(); @@ -3150,21 +3165,10 @@ void G_DoPlayDemo(char *defdemoname) hu_demotime = UINT32_MAX; hu_demolap = UINT32_MAX; - switch (modeattacking) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - hu_demotime = READUINT32(demobuf.p); - hu_demolap = READUINT32(demobuf.p); - break; - case ATTACKING_CAPSULES: // 2 - hu_demotime = READUINT32(demobuf.p); - break; - default: // 3 - modeattacking = ATTACKING_NONE; - break; - } + if (modeattacking & ATTACKING_TIME) + hu_demotime = READUINT32(demobuf.p); + if (modeattacking & ATTACKING_LAP) + hu_demolap = READUINT32(demobuf.p); // Random seed for (i = 0; i < PRNUMCLASS; i++) @@ -3538,7 +3542,7 @@ void G_AddGhost(char *defdemoname) return; } - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. @@ -3551,19 +3555,10 @@ void G_AddGhost(char *defdemoname) return; } - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { @@ -3593,9 +3588,10 @@ void G_AddGhost(char *defdemoname) p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once // any invalidating flags? - if ((READUINT8(p) & (DEMO_SPECTATOR|DEMO_BOT)) != 0) + i = READUINT8(p); + if ((i & (DEMO_SPECTATOR|DEMO_BOT)) != 0) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (spectator/bot)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3606,6 +3602,8 @@ void G_AddGhost(char *defdemoname) M_Memcpy(name, p, 16); p += 16; + p += MAXAVAILABILITY; + // Skin i = READUINT8(p); if (i < worknumskins) @@ -3626,7 +3624,7 @@ void G_AddGhost(char *defdemoname) if (READUINT8(p) != 0xFF) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (bad terminator)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3761,25 +3759,16 @@ void G_UpdateStaffGhostName(lumpnum_t l) goto fail; // we don't NEED to do it here, but whatever } - p++; // Gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); G_SkipDemoSkins(&p); - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { @@ -3945,7 +3934,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill) WriteDemoChecksum(); saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. } - free(demobuf.buffer); + Z_Free(demobuf.buffer); metalrecording = false; if (saved) I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap)); @@ -4165,7 +4154,7 @@ void G_SaveDemo(void) if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. demo.savemode = DSM_SAVED; - free(demobuf.buffer); + Z_Free(demobuf.buffer); demo.recording = false; if (!modeattacking) diff --git a/src/g_demo.h b/src/g_demo.h index d24ed3bbb..a8c3f573c 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -80,13 +80,13 @@ typedef enum { } menudemotype_e; struct menudemo_t { - char filepath[256]; + char filepath[1023 + 256]; // see M_PrepReplayList and sizeof menupath menudemotype_e type; char title[65]; // Null-terminated for string prints UINT16 map; UINT8 addonstatus; // What do we need to do addon-wise to play this demo? - UINT8 gametype; + INT16 gametype; SINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk UINT8 numlaps; diff --git a/src/g_game.c b/src/g_game.c index c13094bf5..6cea62b1a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -61,7 +61,6 @@ #include "k_specialstage.h" #include "k_bot.h" #include "doomstat.h" -#include "acs/interface.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -318,9 +317,8 @@ typedef struct { INT16 *mapbuffer; // Pointer to zone memory INT32 lastnummapheaders; // Reset if nummapheaders != this - UINT8 counttogametype; // Time to gametype change event } randmaps_t; -static randmaps_t randmaps = {NULL, 0, 0}; +static randmaps_t randmaps = {NULL, 0}; static void G_ResetRandMapBuffer(void) { @@ -330,7 +328,6 @@ static void G_ResetRandMapBuffer(void) randmaps.mapbuffer = Z_Malloc(randmaps.lastnummapheaders * sizeof(INT16), PU_STATIC, NULL); for (i = 0; i < randmaps.lastnummapheaders; i++) randmaps.mapbuffer[i] = -1; - //intentionally not resetting randmaps.counttogametype here } typedef struct joystickvector2_s @@ -518,13 +515,18 @@ static void G_UpdateRecordReplays(void) players[consoleplayer].realtime = UINT32_MAX; } - if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) - && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + if (modeattacking & ATTACKING_TIME) { - mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) + && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + } + else + { + mapheaderinfo[gamemap-1]->mainrecord->time = 0; } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { if ((mapheaderinfo[gamemap-1]->mainrecord->lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->mainrecord->lap)) mapheaderinfo[gamemap-1]->mainrecord->lap = bestlap; @@ -547,27 +549,32 @@ static void G_UpdateRecordReplays(void) strcat(gpath, PATHSEP); strcat(gpath, G_BuildMapName(gamemap)); - snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_chooseskin.string); + snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_skin[0].string); - gpath = Z_StrDup(gpath); - - if (FIL_FileExists(lastdemo)) + if (modeattacking != ATTACKING_NONE && FIL_FileExists(lastdemo)) { UINT8 *buf; - size_t len = FIL_ReadFile(lastdemo, &buf); + size_t len; - snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_chooseskin.string); - if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) - { // Better time, save this demo. - if (FIL_FileExists(bestdemo)) - remove(bestdemo); - FIL_WriteFile(bestdemo, buf, len); - CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + gpath = Z_StrDup(gpath); + + len = FIL_ReadFile(lastdemo, &buf); + + if (modeattacking & ATTACKING_TIME) + { + snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_skin[0].string); + if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) + { // Better time, save this demo. + if (FIL_FileExists(bestdemo)) + remove(bestdemo); + FIL_WriteFile(bestdemo, buf, len); + CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + } } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { - snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_chooseskin.string); + snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_skin[0].string); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)) { // Better lap time, save this demo. if (FIL_FileExists(bestdemo)) @@ -580,9 +587,9 @@ static void G_UpdateRecordReplays(void) //CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo); Z_Free(buf); - } - Z_Free(gpath); + Z_Free(gpath); + } // Check emblems when level data is updated if ((earnedEmblems = M_CheckLevelEmblems())) @@ -1410,7 +1417,7 @@ void G_StartTitleCard(void) { // The title card has been disabled for this map. // Oh well. - if (!G_IsTitleCardAvailable() || demo.rewinding) + if (demo.rewinding || !G_IsTitleCardAvailable()) { WipeStageTitle = false; return; @@ -1425,9 +1432,9 @@ void G_StartTitleCard(void) // play the sound { sfxenum_t kstart = sfx_kstart; - if (bossinfo.boss) + if (K_CheckBossIntro() == true) kstart = sfx_ssa021; - else if (encoremode) + else if (encoremode == true) kstart = sfx_ruby2; S_StartSound(NULL, kstart); } @@ -1474,11 +1481,17 @@ void G_PreLevelTitleCard(void) // boolean G_IsTitleCardAvailable(void) { -#if 0 + // Overwrites all other title card exceptions. + if (K_CheckBossIntro() == true) + return true; + // The current level has no name. if (!mapheaderinfo[gamemap-1]->lvlttl[0]) return false; -#endif + + // Instant white fade. + if (gametyperules & GTR_SPECIALSTART) + return false; // The title card is available. return true; @@ -2790,22 +2803,9 @@ mapthing_t *G_FindMapStart(INT32 playernum) if (!playeringame[playernum]) return NULL; - // -- Spectators -- - // Order in platform gametypes: Race->DM->CTF - // And, with deathmatch starts: DM->CTF->Race - if (players[playernum].spectator) - { - // In platform gametypes, spawn in Co-op starts first - // Overriden by GTR_BATTLESTARTS. - if (gametyperules & GTR_BATTLESTARTS && bossinfo.boss == false) - spawnpoint = G_FindBattleStartOrFallback(playernum); - else - spawnpoint = G_FindRaceStartOrFallback(playernum); - } - - // -- Grand Prix / Time Attack -- + // -- Time Attack -- // Order: Race->DM->CTF - else if (grandprixinfo.gp || modeattacking) + if (K_TimeAttackRules() == true) spawnpoint = G_FindRaceStartOrFallback(playernum); // -- CTF -- @@ -2896,8 +2896,6 @@ void G_DoReborn(INT32 playernum) if (oldmo) G_ChangePlayerReferences(oldmo, players[playernum].mo); } - - ACS_RunPlayerEnterScript(player); } void G_AddPlayer(INT32 playernum) @@ -2912,17 +2910,20 @@ void G_ExitLevel(void) if (gamestate == GS_LEVEL) { UINT8 i; - boolean youlost = false; - if (bossinfo.boss == true) + boolean doretry = false; + + if (!G_GametypeUsesLives()) + ; // never force a retry + else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) { - youlost = true; + doretry = true; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && !players[i].bot) { - if (players[i].bumpers > 0) + if (!K_IsPlayerLosing(&players[i])) { - youlost = false; + doretry = false; break; } } @@ -2930,10 +2931,10 @@ void G_ExitLevel(void) } else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) { - youlost = (grandprixinfo.wonround != true); + doretry = (grandprixinfo.wonround != true); } - if (youlost) + if (doretry) { // You didn't win... @@ -3000,28 +3001,98 @@ void G_ExitLevel(void) } } -// See also the enum GameType in doomstat.h -const char *Gametype_Names[NUMGAMETYPES] = +static gametype_t defaultgametypes[] = { - "Race", // GT_RACE - "Battle" // GT_BATTLE + // GT_RACE + { + "Race", + "GT_RACE", + GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, + TOL_RACE, + int_time, + 0, + 0, + }, + + // GT_BATTLE + { + "Battle", + "GT_BATTLE", + GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, + TOL_BATTLE, + int_scoreortimeattack, + 0, + 2, + }, + + // GT_SPECIAL + { + "Special", + "GT_SPECIAL", + GTR_CATCHER|GTR_SPECIALSTART|GTR_ROLLINGSTART|GTR_CIRCUIT, + TOL_SPECIAL, + int_time, + 0, + 0, + }, + + // GT_VERSUS + { + "Versus", + "GT_VERSUS", + GTR_BOSS|GTR_SPHERES|GTR_BUMPERS|GTR_POINTLIMIT|GTR_CLOSERPLAYERS|GTR_NOCUPSELECT|GTR_ENCORE, + TOL_BOSS, + int_scoreortimeattack, + 0, + 0, + }, }; -// For dehacked -const char *Gametype_ConstantNames[NUMGAMETYPES] = +gametype_t *gametypes[MAXGAMETYPES+1] = { - "GT_RACE", // GT_RACE - "GT_BATTLE" // GT_BATTLE + &defaultgametypes[GT_RACE], + &defaultgametypes[GT_BATTLE], + &defaultgametypes[GT_SPECIAL], + &defaultgametypes[GT_VERSUS], }; -// Gametype rules -UINT32 gametypedefaultrules[NUMGAMETYPES] = +// +// G_GetGametypeByName +// +// Returns the number for the given gametype name string, or -1 if not valid. +// +INT32 G_GetGametypeByName(const char *gametypestr) { - // Race - GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS, - // Battle - GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME -}; + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (!stricmp(gametypestr, gametypes[i]->name)) + return i; + i++; + } + + return -1; // unknown gametype +} + +// +// G_GuessGametypeByTOL +// +// Returns the first valid number for the given typeoflevel, or -1 if not valid. +// +INT32 G_GuessGametypeByTOL(UINT32 tol) +{ + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (tol & gametypes[i]->tol) + return i; + i++; + } + + return -1; // unknown gametype +} // // G_SetGametype @@ -3030,41 +3101,26 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] = // void G_SetGametype(INT16 gtype) { + if (gtype < 0 || gtype > numgametypes) + { + I_Error("G_SetGametype: Bad gametype change %d (was %d/\"%s\")", gtype, gametype, gametypes[gametype]->name); + } + gametype = gtype; - gametyperules = gametypedefaultrules[gametype]; } // -// G_AddGametype -// -// Add a gametype. Returns the new gametype number. -// -INT16 G_AddGametype(UINT32 rules) -{ - INT16 newgtype = gametypecount; - gametypecount++; - - // Set gametype rules. - gametypedefaultrules[newgtype] = rules; - Gametype_Names[newgtype] = "???"; - - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); - - return newgtype; -} - -// -// G_AddGametypeConstant +// G_PrepareGametypeConstant // // Self-explanatory. Filters out "bad" characters. // -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) +char *G_PrepareGametypeConstant(const char *newgtconst) { size_t r = 0; // read size_t w = 0; // write - char *gtconst = Z_Calloc(strlen(newgtconst) + 4, PU_STATIC, NULL); - char *tmpconst = Z_Calloc(strlen(newgtconst) + 1, PU_STATIC, NULL); + size_t len = strlen(newgtconst); + char *gtconst = Z_Calloc(len + 4, PU_STATIC, NULL); + char *tmpconst = Z_Calloc(len + 1, PU_STATIC, NULL); // Copy the gametype name. strcpy(tmpconst, newgtconst); @@ -3124,42 +3180,10 @@ void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) // Free the temporary string. Z_Free(tmpconst); - // Finally, set the constant string. - Gametype_ConstantNames[gtype] = gtconst; + // Finally, return the constant string. + return gtconst; } -// -// G_UpdateGametypeSelections -// -// Updates gametype_cons_t. -// -void G_UpdateGametypeSelections(void) -{ - INT32 i; - for (i = 0; i < gametypecount; i++) - { - gametype_cons_t[i].value = i; - gametype_cons_t[i].strvalue = Gametype_Names[i]; - } - gametype_cons_t[NUMGAMETYPES].value = 0; - gametype_cons_t[NUMGAMETYPES].strvalue = NULL; -} - -// Gametype rankings -INT16 gametyperankings[NUMGAMETYPES] = -{ - GT_RACE, - GT_BATTLE, -}; - -// Gametype to TOL (Type Of Level) -UINT32 gametypetol[NUMGAMETYPES] = -{ - TOL_RACE, // Race - TOL_BATTLE, // Battle - TOL_TV, // Midnight Channel effect -}; - tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, @@ -3186,51 +3210,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname) TYPEOFLEVEL[i].flag = newtol; } -// -// G_AddGametypeTOL -// -// Assigns a type of level to a gametype. -// -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol) -{ - gametypetol[gtype] = newtol; -} - -// -// G_GetGametypeByName -// -// Returns the number for the given gametype name string, or -1 if not valid. -// -INT32 G_GetGametypeByName(const char *gametypestr) -{ - INT32 i; - - for (i = 0; i < gametypecount; i++) - if (!stricmp(gametypestr, Gametype_Names[i])) - return i; - - return -1; // unknown gametype -} - -// -// G_IsSpecialStage -// -// Returns TRUE if -// the given map is a special stage. -// -boolean G_IsSpecialStage(INT32 mapnum) -{ - mapnum--; // gamemap-based to 0 indexed - - if (mapnum > nummapheaders || !mapheaderinfo[mapnum]) - return false; - - if (!mapheaderinfo[mapnum]->cup || mapheaderinfo[mapnum]->cup->cachedlevels[CUPCACHE_SPECIAL] != mapnum) - return false; - - return true; -} - // // G_GametypeUsesLives // @@ -3243,13 +3222,7 @@ boolean G_GametypeUsesLives(void) return false; if ((grandprixinfo.gp == true) // In Grand Prix - && (gametype == GT_RACE) // NOT in bonus round - && grandprixinfo.eventmode == GPEVENT_NONE) // NOT in bonus - { - return true; - } - - if (bossinfo.boss == true) // Fighting a boss? + && grandprixinfo.eventmode != GPEVENT_BONUS) // NOT in bonus round { return true; } @@ -3294,100 +3267,30 @@ boolean G_GametypeHasSpectators(void) // // G_SometimesGetDifferentGametype // -// Oh, yeah, and we sometimes flip encore mode on here too. +// Because gametypes are no longer on the vote screen, all this does is sometimes flip encore mode. +// However, it remains a seperate function for long-term possibility. // -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) +INT16 G_SometimesGetDifferentGametype(void) { - // Most of the gametype references in this condition are intentionally not prefgametype. - // This is so a server CAN continue playing a gametype if they like the taste of it. - // The encore check needs prefgametype so can't use G_RaceGametype... boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1) - && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); + && (gametyperules & GTR_ENCORE)); UINT8 encoremodifier = 0; // -- the below is only necessary if you want to use randmaps.mapbuffer here //if (randmaps.lastnummapheaders != nummapheaders) //G_ResetRandMapBuffer(); - if (encorepossible) + // FORCE to what was scrambled on intermission? + if (encorepossible && encorescramble != -1) { - if (encorescramble != -1) + // FORCE to what was scrambled on intermission + if ((encorescramble != 0) != (cv_kartencore.value == 1)) { - encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission - } - else - { - switch (cv_kartvoterulechanges.value) - { - case 3: // always - encorepossible = true; - break; - case 2: // frequent - encorepossible = M_RandomChance(FRACUNIT>>1); - break; - case 1: // sometimes - encorepossible = M_RandomChance(FRACUNIT>>2); - break; - default: - break; - } - } - if (encorepossible != (cv_kartencore.value == 1)) encoremodifier = VOTEMODIFIER_ENCORE; + } } - if (!cv_kartvoterulechanges.value) // never - return (gametype|encoremodifier); - - if (randmaps.counttogametype > 0 && (cv_kartvoterulechanges.value != 3)) - { - randmaps.counttogametype--; - return (gametype|encoremodifier); - } - - switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? - { - case 1: // sometimes - randmaps.counttogametype = 5; // per "cup" - break; - default: - // fallthrough - happens when clearing buffer, but needs a reasonable countdown if cvar is modified - case 2: // frequent - randmaps.counttogametype = 2; // ...every 1/2th-ish cup? - break; - } - - // Only this response is prefgametype-based. - // todo custom gametypes - if (prefgametype == GT_BATTLE) - { - // Intentionally does not use encoremodifier! - if (cv_kartencore.value == 1) - return (GT_RACE|VOTEMODIFIER_ENCORE); - return (GT_RACE); - } - // This might appear wrong HERE, but the game will display the Encore possibility on the second voting choice instead. - return (GT_BATTLE|encoremodifier); -} - -// -// G_GetGametypeColor -// -// Pretty and consistent ^u^ -// See also M_GetGametypeColor (if that still exists). -// -UINT8 G_GetGametypeColor(INT16 gt) -{ - if (modeattacking) // == ATTACKING_RECORD - return orangemap[0]; - - if (gt == GT_BATTLE) - return redmap[0]; - - if (gt == GT_RACE) - return skymap[0]; - - return 255; // FALLBACK + return (gametype|encoremodifier); } /** Get the typeoflevel flag needed to indicate support of a gametype. @@ -3397,7 +3300,9 @@ UINT8 G_GetGametypeColor(INT16 gt) */ UINT32 G_TOLFlag(INT32 pgametype) { - return gametypetol[pgametype]; + if (pgametype >= 0 && pgametype < numgametypes) + return gametypes[pgametype]->tol; + return 0; } INT16 G_GetFirstMapOfGametype(UINT8 pgametype) @@ -3408,7 +3313,7 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype) templevelsearch.cup = NULL; templevelsearch.typeoflevel = G_TOLFlag(pgametype); - templevelsearch.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); + templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.timeattack = false; templevelsearch.checklocked = true; @@ -3500,7 +3405,7 @@ tryagain: if (!mapheaderinfo[ix] || mapheaderinfo[ix]->lumpnum == LUMPERROR) continue; - if ((mapheaderinfo[ix]->typeoflevel & tolflags) != tolflags + if (!(mapheaderinfo[ix]->typeoflevel & tolflags) || ix == pprevmap || M_MapLocked(ix+1) || (usehellmaps != (mapheaderinfo[ix]->menuflags & LF2_HIDEINMENU))) // this is bad @@ -3719,7 +3624,6 @@ static void G_HandleSaveLevel(void) static void G_GetNextMap(void) { - boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; // go to next level @@ -3736,7 +3640,7 @@ static void G_GetNextMap(void) } else { - INT32 lastgametype = gametype; + INT32 lastgametype = gametype, newgametype = GT_RACE; // 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1); UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1; @@ -3753,9 +3657,6 @@ static void G_GetNextMap(void) G_SetGametype(GT_RACE); if (gametype != lastgametype) D_GameTypeChanged(lastgametype); - - specialStage.active = false; - bossinfo.boss = false; } // Special stage else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) @@ -3776,11 +3677,11 @@ static void G_GetNextMap(void) if (totaltotalring >= 50) { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_SPECIAL|TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_SPECIAL; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } @@ -3791,37 +3692,27 @@ static void G_GetNextMap(void) // todo any other condition? { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_BONUS; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } + if (newgametype == -1) + { + // Don't permit invalid changes. + grandprixinfo.eventmode = GPEVENT_NONE; + newgametype = gametype; + } + if (grandprixinfo.eventmode != GPEVENT_NONE) { - // nextmap is set above - const INT32 newtol = mapheaderinfo[nextmap]->typeoflevel; - - if (newtol & TOL_SPECIAL) - { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - } - else //(if newtol & (TOL_BATTLE|TOL_BOSS)) -- safe to assume?? - { - G_SetGametype(GT_BATTLE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - if (newtol & TOL_BOSS) - { - K_ResetBossInfo(); - bossinfo.boss = true; - bossinfo.encore = grandprixinfo.encore; - } - } + G_SetGametype(newgametype); + if (gametype != lastgametype) + D_GameTypeChanged(lastgametype); } else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map { @@ -3845,10 +3736,6 @@ static void G_GetNextMap(void) } } } - else if (bossinfo.boss == true) - { - nextmap = NEXTMAP_TITLE; // temporary - } else { UINT32 tolflag = G_TOLFlag(gametype); @@ -3987,7 +3874,9 @@ static void G_GetNextMap(void) if (nextmap == NEXTMAP_INVALID || (nextmap < NEXTMAP_SPECIAL && (nextmap >= nummapheaders || !mapheaderinfo[nextmap] || mapheaderinfo[nextmap]->lumpnum == LUMPERROR))) I_Error("G_GetNextMap: Internal map ID %d not found (nummapheaders = %d)\n", nextmap, nummapheaders); +#if 0 // This is a surprise tool that will help us later. if (!spec) +#endif //#if 0 lastmap = nextmap; } @@ -4111,7 +4000,7 @@ void G_AfterIntermission(void) G_HandleSaveLevel(); } - if ((gametyperules & GTR_CAMPAIGN) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[prevmap]->cutscenenum-1, false, false); else { @@ -4248,7 +4137,7 @@ void G_EndGame(void) } // Only do evaluation and credits in singleplayer contexts - if (!netgame && (gametyperules & GTR_CAMPAIGN)) + if (!netgame && grandprixinfo.gp == true) { if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony { @@ -4306,12 +4195,11 @@ void G_LoadGameSettings(void) // Loads the main data file, which stores information such as emblems found, etc. void G_LoadGameData(void) { - size_t length; UINT32 i, j; UINT32 versionID; UINT8 versionMinor; UINT8 rtemp; - savebuffer_t save; + savebuffer_t save = {0}; //For records UINT32 numgamedatamapheaders; @@ -4340,16 +4228,13 @@ void G_LoadGameData(void) return; } - length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, va(pandf, srb2home, gamedatafilename)) == false) { // No gamedata. We can save a new one. gamedata->loaded = true; return; } - save.p = save.buffer; - // Version check versionID = READUINT32(save.p); if (versionID != GD_VERSIONCHECK) @@ -4358,16 +4243,14 @@ void G_LoadGameData(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } versionMinor = READUINT8(save.p); if (versionMinor > GD_VERSIONMINOR) { - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor); } @@ -4478,8 +4361,7 @@ void G_LoadGameData(void) } // done - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); // Don't consider loaded until it's a success! // It used to do this much earlier, but this would cause the gamedata to @@ -4499,8 +4381,7 @@ void G_LoadGameData(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Corrupt game data file.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } @@ -4513,7 +4394,7 @@ void G_SaveGameData(void) size_t length; INT32 i, j; UINT8 btemp; - savebuffer_t save; + savebuffer_t save = {0}; if (!gamedata->loaded) return; // If never loaded (-nodata), don't save @@ -4533,14 +4414,11 @@ void G_SaveGameData(void) } length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); - save.size = length; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, length) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } - save.end = save.buffer + save.size; // Version test @@ -4627,7 +4505,7 @@ void G_SaveGameData(void) length = save.p - save.buffer; FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); - free(save.buffer); + P_SaveBufferFree(&save); // Also save profiles here. PR_SaveProfiles(); @@ -4641,10 +4519,9 @@ void G_SaveGameData(void) // void G_LoadGame(UINT32 slot, INT16 mapoverride) { - size_t length; char vcheck[VERSIONSIZE]; char savename[255]; - savebuffer_t save; + savebuffer_t save = {0}; // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); @@ -4659,17 +4536,12 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) else sprintf(savename, savegamename, slot); - length = FIL_ReadFile(savename, &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, savename) == false) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); return; } - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; - memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); if (strcmp((const char *)save.p, (const char *)vcheck)) @@ -4682,7 +4554,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) M_ClearMenus(true); // so ESC backs out to title M_StartMessage(M_GetText("Save game from different version\n\nPress ESC\n"), NULL, MM_NOTHING); Command_ExitGame_f(); - Z_Free(save.buffer); + P_SaveBufferFree(&save); // no cheating! memset(&savedata, 0, sizeof(savedata)); @@ -4717,7 +4589,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } // done - Z_Free(save.buffer); + P_SaveBufferFree(&save); // gameaction = ga_nothing; // G_SetGamestate(GS_LEVEL); @@ -4743,7 +4615,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) boolean saved; char savename[256] = ""; const char *backup; - savebuffer_t save; + savebuffer_t save = {0}; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4756,14 +4628,11 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) char name[VERSIONSIZE]; size_t length; - save.size = SAVEGAMESIZE; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, SAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } - save.end = save.buffer + save.size; memset(name, 0, sizeof (name)); sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4781,7 +4650,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) length = save.p - save.buffer; saved = FIL_WriteFile(backup, save.buffer, length); - free(save.buffer); + P_SaveBufferFree(&save); } gameaction = ga_nothing; @@ -4801,7 +4670,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) char vcheck[VERSIONSIZE]; char savename[255]; const char *backup; - savebuffer_t save; + savebuffer_t save = {0}; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4809,22 +4678,19 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) sprintf(savename, savegamename, slot); backup = va("%s",savename); - length = FIL_ReadFile(savename, &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, savename) == false) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); return; } + length = save.size; + { char temp[sizeof(timeattackfolder)]; UINT8 *lives_p; SINT8 pllives; - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; - // Version check memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4895,9 +4761,8 @@ cleanup: CONS_Printf(M_GetText("Game saved.\n")); else if (!saved) CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); - Z_Free(save.buffer); - save.p = save.buffer = NULL; + P_SaveBufferFree(&save); } #undef CHECKPOS #undef BADSAVE @@ -4910,7 +4775,6 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; - INT32 dogametype; paused = false; @@ -4921,17 +4785,8 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss G_ResetRandMapBuffer(); - if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) - { - dogametype = GT_BATTLE; - } - else - { - dogametype = GT_RACE; - } - // this leave the actual game if needed - SV_StartSinglePlayerServer(dogametype, false); + SV_StartSinglePlayerServer(gametype, false); if (splitscreen != ssplayers) { @@ -5031,7 +4886,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr automapactive = false; imcontinuing = false; - if ((gametyperules & GTR_CAMPAIGN) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer); else { diff --git a/src/g_game.h b/src/g_game.h index cc4787ea9..de60bee49 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -183,24 +183,17 @@ void G_SaveGame(UINT32 slot, INT16 mapnum); void G_SaveGameOver(UINT32 slot, boolean modifylives); -extern UINT32 gametypedefaultrules[NUMGAMETYPES]; -extern UINT32 gametypetol[NUMGAMETYPES]; -extern INT16 gametyperankings[NUMGAMETYPES]; - void G_SetGametype(INT16 gametype); -INT16 G_AddGametype(UINT32 rules); -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst); -void G_UpdateGametypeSelections(void); +char *G_PrepareGametypeConstant(const char *newgtconst); void G_AddTOL(UINT32 newtol, const char *tolname); -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol); INT32 G_GetGametypeByName(const char *gametypestr); -boolean G_IsSpecialStage(INT32 mapnum); +INT32 G_GuessGametypeByTOL(UINT32 tol); + boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); #define VOTEMODIFIER_ENCORE 0x80 -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype); -UINT8 G_GetGametypeColor(INT16 gt); +INT16 G_SometimesGetDifferentGametype(void); void G_ExitLevel(void); void G_NextLevel(void); void G_Continue(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index bbccb232a..ed0ab076f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -16,7 +16,7 @@ #include "hu_stuff.h" #include "font.h" -#include "k_menu.h" // gametype_cons_t +#include "k_menu.h" // highlightflags #include "m_cond.h" // emblems #include "m_misc.h" // word jumping @@ -55,7 +55,8 @@ // SRB2Kart #include "s_sound.h" // song credits #include "k_kart.h" -#include "k_boss.h" +#include "k_battle.h" +#include "k_grandprix.h" #include "k_color.h" #include "k_hud.h" #include "r_fps.h" @@ -2392,60 +2393,110 @@ static inline void HU_DrawSpectatorTicker(void) static void HU_DrawRankings(void) { playersort_t tab[MAXPLAYERS]; - INT32 i, j, scorelines, hilicol, numplayersingame = 0; + INT32 i, j, scorelines, numplayersingame = 0, hilicol = highlightflags; boolean completed[MAXPLAYERS]; UINT32 whiteplayer = MAXPLAYERS; + boolean timedone = false, pointsdone = false; V_DrawFadeScreen(0xFF00, 16); // A little more readable, and prevents cheating the fades under other circumstances. - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((gametype == GT_RACE) ? V_SKYMAP : V_REDMAP); - // draw the current gametype in the lower right - if (modeattacking) - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack"); - else - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, Gametype_Names[gametype]); + if (grandprixinfo.gp == true) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Grand Prix"); + else if (battlecapsules) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Capsules"); + else if (gametype >= 0 && gametype < numgametypes) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); - if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) + // Left hand side + if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + const char *roundstr = NULL; + V_DrawCenteredString(64, 8, 0, "ROUND"); + switch (grandprixinfo.eventmode) { - UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); - if (timeval > timelimitintics+1) - timeval = timelimitintics+1; - timeval /= TICRATE; + case GPEVENT_BONUS: + roundstr = "BONUS"; + break; + case GPEVENT_SPECIAL: + roundstr = "SPECIAL"; + break; + default: + roundstr = va("%d", grandprixinfo.roundnum); + break; + } + V_DrawCenteredString(64, 16, hilicol, roundstr); + } + else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + { + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; - if (leveltime <= (timelimitintics + starttime)) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); - } - - // overtime - if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); - } + if (leveltime <= (timelimitintics + starttime)) + { + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); } - if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) { - V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); - V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); + } + + timedone = true; + } + else if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(64, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(64, 16, hilicol, va("%d", cv_pointlimit.value)); + pointsdone = true; + } + else if (gametyperules & GTR_CIRCUIT) + { + V_DrawCenteredString(64, 8, 0, "LAPS"); + V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); + } + + // Right hand side + if (battlecapsules == true) + { + if (numtargets < maptargets) + { + V_DrawCenteredString(256, 8, 0, "CAPSULES"); + V_DrawCenteredString(256, 16, hilicol, va("%d", maptargets - numtargets)); } } - else + else if (!timedone && (gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { - if (circuitmap) + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; + + if (leveltime <= (timelimitintics + starttime)) { - V_DrawCenteredString(64, 8, 0, "LAP COUNT"); - V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, va("%u", timeval)); } + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) + { + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, "OVERTIME"); + } + } + else if (!pointsdone && (gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + } + else if (gametyperules & GTR_CIRCUIT) + { V_DrawCenteredString(256, 8, 0, "GAME SPEED"); V_DrawCenteredString(256, 16, hilicol, kartspeed_cons_t[1+gamespeed].strvalue); } @@ -2494,13 +2545,12 @@ static void HU_DrawRankings(void) if ((gametyperules & GTR_CIRCUIT)) { - if (circuitmap) - tab[scorelines].count = players[i].laps; - else - tab[scorelines].count = players[i].realtime; + tab[scorelines].count = players[i].laps; } else + { tab[scorelines].count = players[i].roundscore; + } scorelines++; diff --git a/src/info.c b/src/info.c index 8e3757190..592487f3e 100644 --- a/src/info.c +++ b/src/info.c @@ -548,6 +548,9 @@ char sprnames[NUMSPRITES + 1][5] = "MGBX", // Heavy Magician transform box "MGBT", // Heavy Magician transform box top "MGBB", // Heavy Magician transform box bottom + "MSHD", // Item Monitor Big Shard + "IMDB", // Item Monitor Small Shard (Debris) + "MTWK", // Item Monitor Glass Twinkle "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks @@ -789,6 +792,8 @@ char sprnames[NUMSPRITES + 1][5] = "UFOA", "UFOS", + "UQMK", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -3900,6 +3905,24 @@ state_t states[NUMSTATES] = //{SPR_ICAP, FF_FLOORSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM //{SPR_ICAP, FF_FLOORSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_INSIDE + {SPR_NULL, 0, 1, {NULL}, 6, 1, S_SPAWNSTATE}, // S_MONITOR_DAMAGE + {SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_DEATH + {SPR_IMON, FF_PAPERSPRITE|1, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1B}, // S_MONITOR_SCREEN1A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2A}, // S_MONITOR_SCREEN1B + {SPR_IMON, FF_PAPERSPRITE|2, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2B}, // S_MONITOR_SCREEN2A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3A}, // S_MONITOR_SCREEN2B + {SPR_IMON, FF_PAPERSPRITE|3, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3B}, // S_MONITOR_SCREEN3A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4A}, // S_MONITOR_SCREEN3B + {SPR_IMON, FF_PAPERSPRITE|4, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4B}, // S_MONITOR_SCREEN4A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1A}, // S_MONITOR_SCREEN4B + {SPR_IMON, FF_PAPERSPRITE|5, -1, {NULL}, 3, 1, S_NULL}, // S_MONITOR_STAND + {SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_ADD|6, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKA + {SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_SUBTRACT|10, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKB + + {SPR_MSHD, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 2, S_NULL}, // S_MONITOR_BIG_SHARD + {SPR_IMDB, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_SMALL_SHARD + {SPR_MTWK, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_TWINKLE + {SPR_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX {SPR_MGBT, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP {SPR_MGBB, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_BOTTOM @@ -22420,6 +22443,87 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_MONITOR + -1, // doomednum + S_INVISIBLE, // spawnstate + FRACUNIT, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_MONITOR_DAMAGE, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_MONITOR_DEATH, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 112*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_SLIDEME|MF_DONTENCOREMAP|MF_NOHITLAGFORME, // flags + S_NULL // raisestate + }, + + { // MT_MONITOR_PART + -1, // doomednum + S_INVISIBLE, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 112*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + + { // MT_MONITOR_SHARD + -1, // doomednum + S_MONITOR_BIG_SHARD, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_MAGICIANBOX -1, // doomednum S_MAGICIANBOX, // spawnstate diff --git a/src/info.h b/src/info.h index d0d5228e3..2b768691d 100644 --- a/src/info.h +++ b/src/info.h @@ -1099,6 +1099,9 @@ typedef enum sprite SPR_MGBX, // Heavy Magician transform box SPR_MGBT, // Heavy Magician transform box top SPR_MGBB, // Heavy Magician transform box bottom + SPR_MSHD, // Item Monitor Big Shard + SPR_IMDB, // Item Monitor Small Shard (Debris) + SPR_MTWK, // Item Monitor Glass Twinkle SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks @@ -1340,6 +1343,8 @@ typedef enum sprite SPR_UFOA, SPR_UFOS, + SPR_UQMK, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -4309,6 +4314,24 @@ typedef enum state //S_ITEMCAPSULE_BOTTOM, //S_ITEMCAPSULE_INSIDE, + S_MONITOR_DAMAGE, + S_MONITOR_DEATH, + S_MONITOR_SCREEN1A, + S_MONITOR_SCREEN1B, + S_MONITOR_SCREEN2A, + S_MONITOR_SCREEN2B, + S_MONITOR_SCREEN3A, + S_MONITOR_SCREEN3B, + S_MONITOR_SCREEN4A, + S_MONITOR_SCREEN4B, + S_MONITOR_STAND, + S_MONITOR_CRACKA, + S_MONITOR_CRACKB, + + S_MONITOR_BIG_SHARD, + S_MONITOR_SMALL_SHARD, + S_MONITOR_TWINKLE, + S_MAGICIANBOX, S_MAGICIANBOX_TOP, S_MAGICIANBOX_BOTTOM, @@ -6360,6 +6383,9 @@ typedef enum mobj_type MT_FLOATINGITEM, MT_ITEMCAPSULE, MT_ITEMCAPSULE_PART, + MT_MONITOR, + MT_MONITOR_PART, + MT_MONITOR_SHARD, MT_MAGICIANBOX, MT_SIGNSPARKLE, diff --git a/src/io/streams.cpp b/src/io/streams.cpp index af134548b..6ba250a24 100644 --- a/src/io/streams.cpp +++ b/src/io/streams.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "streams.hpp" template class srb2::io::ZlibInputStream; diff --git a/src/io/streams.hpp b/src/io/streams.hpp index 60ad130b1..643cfd987 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_IO_STREAMS_HPP__ #define __SRB2_IO_STREAMS_HPP__ @@ -409,7 +418,7 @@ public: switch (seek_from) { case SeekFrom::kStart: - if (offset < 0 || offset >= static_cast(span_.size())) { + if (offset < 0) { throw std::logic_error("start offset is out of bounds"); } head = offset; @@ -421,7 +430,7 @@ public: head = span_.size() - offset; break; case SeekFrom::kCurrent: - if (head_ + offset < 0 || head_ + offset >= span_.size()) { + if (head_ + offset < 0) { throw std::logic_error("offset is out of bounds"); } head = head_ + offset; @@ -480,7 +489,7 @@ public: switch (seek_from) { case SeekFrom::kStart: - if (offset < 0 || offset >= static_cast(vec_.size())) { + if (offset < 0) { throw std::logic_error("start offset is out of bounds"); } head = offset; @@ -492,7 +501,7 @@ public: head = vec_.size() - offset; break; case SeekFrom::kCurrent: - if (head_ + offset < 0 || head_ + offset >= vec_.size()) { + if (head_ + offset < 0) { throw std::logic_error("offset is out of bounds"); } head = head_ + offset; diff --git a/src/k_battle.c b/src/k_battle.c index 15b81f81c..765df4966 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -2,7 +2,6 @@ /// \brief SRB2Kart Battle Mode specific code #include "k_battle.h" -#include "k_boss.h" #include "k_kart.h" #include "doomtype.h" #include "doomdata.h" @@ -19,6 +18,7 @@ #include "r_sky.h" // skyflatnum #include "k_grandprix.h" // K_CanChangeRules #include "p_spec.h" +#include "k_objects.h" // Battle overtime info struct battleovertime battleovertime; @@ -146,7 +146,7 @@ void K_CheckBumpers(void) } else if (numingame <= 1) { - if (!battlecapsules) + if ((gametyperules & GTR_CAPSULES) && !battlecapsules) { // Reset map to turn on battle capsules if (server) @@ -196,6 +196,11 @@ void K_CheckEmeralds(player_t *player) { UINT8 i; + if (!(gametyperules & GTR_POWERSTONES)) + { + return; + } + if (!ALLCHAOSEMERALDS(player->emeralds)) { return; @@ -221,6 +226,29 @@ void K_CheckEmeralds(player_t *player) K_CheckBumpers(); } +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) +{ + switch (emeraldType) + { + case EMERALD_CHAOS1: + return SKINCOLOR_CHAOSEMERALD1; + case EMERALD_CHAOS2: + return SKINCOLOR_CHAOSEMERALD2; + case EMERALD_CHAOS3: + return SKINCOLOR_CHAOSEMERALD3; + case EMERALD_CHAOS4: + return SKINCOLOR_CHAOSEMERALD4; + case EMERALD_CHAOS5: + return SKINCOLOR_CHAOSEMERALD5; + case EMERALD_CHAOS6: + return SKINCOLOR_CHAOSEMERALD6; + case EMERALD_CHAOS7: + return SKINCOLOR_CHAOSEMERALD7; + default: + return SKINCOLOR_NONE; + } +} + mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType) { boolean validEmerald = true; @@ -240,25 +268,13 @@ mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT switch (emeraldType) { case EMERALD_CHAOS1: - emerald->color = SKINCOLOR_CHAOSEMERALD1; - break; case EMERALD_CHAOS2: - emerald->color = SKINCOLOR_CHAOSEMERALD2; - break; case EMERALD_CHAOS3: - emerald->color = SKINCOLOR_CHAOSEMERALD3; - break; case EMERALD_CHAOS4: - emerald->color = SKINCOLOR_CHAOSEMERALD4; - break; case EMERALD_CHAOS5: - emerald->color = SKINCOLOR_CHAOSEMERALD5; - break; case EMERALD_CHAOS6: - emerald->color = SKINCOLOR_CHAOSEMERALD6; - break; case EMERALD_CHAOS7: - emerald->color = SKINCOLOR_CHAOSEMERALD7; + emerald->color = K_GetChaosEmeraldColor(emeraldType); break; default: CONS_Printf("Invalid emerald type %d\n", emeraldType); @@ -336,12 +352,17 @@ UINT8 K_NumEmeralds(player_t *player) return num; } +static inline boolean IsOnInterval(tic_t interval) +{ + return ((leveltime - starttime) % interval) == 0; +} + void K_RunPaperItemSpawners(void) { const boolean overtime = (battleovertime.enabled >= 10*TICRATE); - tic_t interval = 8*TICRATE; + const tic_t interval = BATTLE_SPAWN_INTERVAL; - const boolean canmakeemeralds = true; //(!(battlecapsules || bossinfo.boss)); + const boolean canmakeemeralds = (gametyperules & GTR_POWERSTONES); UINT32 emeraldsSpawned = 0; UINT32 firstUnspawnedEmerald = 0; @@ -352,7 +373,7 @@ void K_RunPaperItemSpawners(void) UINT8 pcount = 0; INT16 i; - if (battlecapsules || bossinfo.boss) + if (battlecapsules) { // Gametype uses paper items, but this specific expression doesn't return; @@ -364,13 +385,7 @@ void K_RunPaperItemSpawners(void) return; } - if (overtime == true) - { - // Double frequency of items - interval /= 2; - } - - if (((leveltime - starttime) % interval) != 0) + if (!IsOnInterval(interval)) { return; } @@ -458,9 +473,7 @@ void K_RunPaperItemSpawners(void) #define MAXITEM 64 mobj_t *spotList[MAXITEM]; UINT8 spotMap[MAXITEM]; - UINT8 spotCount = 0, spotBackup = 0; - - INT16 starti = 0; + UINT8 spotCount = 0, spotBackup = 0, spotAvailable = 0; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { @@ -474,14 +487,26 @@ void K_RunPaperItemSpawners(void) emeraldsSpawned |= mo->extravalue1; } + if (mo->type == MT_MONITOR) + { + emeraldsSpawned |= Obj_MonitorGetEmerald(mo); + } + if (mo->type != MT_PAPERITEMSPOT) continue; if (spotCount >= MAXITEM) continue; + if (Obj_ItemSpotIsAvailable(mo)) + { + // spotMap first only includes spots + // where a monitor doesn't exist + spotMap[spotAvailable] = spotCount; + spotAvailable++; + } + spotList[spotCount] = mo; - spotMap[spotCount] = spotCount; spotCount++; } @@ -499,7 +524,6 @@ void K_RunPaperItemSpawners(void) if (!(emeraldsSpawned & emeraldFlag)) { firstUnspawnedEmerald = emeraldFlag; - starti = -1; break; } } @@ -507,77 +531,72 @@ void K_RunPaperItemSpawners(void) //CONS_Printf("leveltime = %d ", leveltime); - spotBackup = spotCount; - for (i = starti; i < pcount; i++) + if (spotAvailable > 0) { - UINT8 r = 0, key = 0; - mobj_t *drop = NULL; - SINT8 flip = 1; + const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)]; - if (spotCount == 0) + Obj_ItemSpotAssignMonitor(spotList[r], Obj_SpawnMonitor( + spotList[r], 1 + pcount, firstUnspawnedEmerald)); + } + + for (i = 0; i < spotCount; ++i) + { + // now spotMap includes every spot + spotMap[i] = i; + } + + if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval)) + { + spotBackup = spotCount; + for (i = 0; i < pcount; i++) { - // all are accessible again - spotCount = spotBackup; - } + UINT8 r = 0, key = 0; + mobj_t *drop = NULL; + SINT8 flip = 1; - if (spotCount == 1) - { - key = 0; - } - else - { - key = P_RandomKey(PR_ITEM_ROULETTE, spotCount); - } - - r = spotMap[key]; - - //CONS_Printf("[%d %d %d] ", i, key, r); - - flip = P_MobjFlip(spotList[r]); - - // When -1, we're spawning a Chaos Emerald. - if (i == -1) - { - drop = K_SpawnChaosEmerald( - spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - firstUnspawnedEmerald - ); - } - else - { - if (gametyperules & GTR_SPHERES) + if (spotCount == 0) { - drop = K_SpawnSphereBox( - spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - 10 - ); - K_FlipFromObject(drop, spotList[r]); + // all are accessible again + spotCount = spotBackup; } - drop = K_CreatePaperItem( + if (spotCount == 1) + { + key = 0; + } + else + { + key = P_RandomKey(PR_ITEM_ROULETTE, spotCount); + } + + r = spotMap[key]; + + //CONS_Printf("[%d %d %d] ", i, key, r); + + flip = P_MobjFlip(spotList[r]); + + drop = K_SpawnSphereBox( spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - 0, 0 + FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, + 10 ); - } - K_FlipFromObject(drop, spotList[r]); + K_FlipFromObject(drop, spotList[r]); - spotCount--; - if (key != spotCount) - { - // So the core theory of what's going on is that we keep every - // available option at the front of the array, so we don't have - // to skip over any gaps or do recursion to avoid doubles. - // But because spotCount can be reset in the case of a low - // quanitity of item spawnpoints in a map, we still need every - // entry in the array, even outside of the "visible" range. - // A series of swaps allows us to adhere to both constraints. - // -toast 22/03/22 (semipalindromic!) - spotMap[key] = spotMap[spotCount]; - spotMap[spotCount] = r; // was set to spotMap[key] previously + spotCount--; + if (key != spotCount) + { + // So the core theory of what's going on is that we keep every + // available option at the front of the array, so we don't have + // to skip over any gaps or do recursion to avoid doubles. + // But because spotCount can be reset in the case of a low + // quanitity of item spawnpoints in a map, we still need every + // entry in the array, even outside of the "visible" range. + // A series of swaps allows us to adhere to both constraints. + // -toast 22/03/22 (semipalindromic!) + spotMap[key] = spotMap[spotCount]; + spotMap[spotCount] = r; // was set to spotMap[key] previously + } } } //CONS_Printf("\n"); @@ -789,7 +808,7 @@ void K_BattleInit(boolean singleplayercontext) { size_t i; - if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules && !bossinfo.boss) + if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules) { mapthing_t *mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) diff --git a/src/k_battle.h b/src/k_battle.h index 42d92cf33..f64cfa967 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -8,6 +8,9 @@ extern "C" { #endif +#define BATTLE_SPAWN_INTERVAL (4*TICRATE) +#define BATTLE_DESPAWN_TIME (15*TICRATE) + extern struct battleovertime { UINT16 enabled; ///< Has this been initalized yet? @@ -25,6 +28,7 @@ boolean K_IsPlayerWanted(player_t *player); void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount); void K_CheckBumpers(void); void K_CheckEmeralds(player_t *player); +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType); mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType); mobj_t *K_SpawnSphereBox(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 amount); void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType); diff --git a/src/k_boss.c b/src/k_boss.c index 490adb27e..ff10231ea 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -23,7 +23,7 @@ struct bossinfo bossinfo; /*-------------------------------------------------- - void K_ClearBossInfo(void) + void K_ResetBossInfo(void) See header file for description. --------------------------------------------------*/ @@ -32,6 +32,8 @@ void K_ResetBossInfo(void) Z_Free(bossinfo.enemyname); Z_Free(bossinfo.subtitle); memset(&bossinfo, 0, sizeof(struct bossinfo)); + bossinfo.barlen = BOSSHEALTHBARLEN; + bossinfo.titlesound = sfx_typri1; } /*-------------------------------------------------- @@ -43,7 +45,7 @@ void K_BossInfoTicker(void) { UINT8 i; - if (bossinfo.boss == false) + if (bossinfo.valid == false) return; // Update healthbar data. (only if the hud is visible) @@ -55,7 +57,7 @@ void K_BossInfoTicker(void) bossinfo.visualbar--; // If the boss is dying, start shrinking the healthbar. if (bossinfo.visualbar == 0) - bossinfo.barlen-= 2; + bossinfo.barlen -= 2; } // Less than the actual health? else if (bossinfo.visualbar < bossinfo.healthbar) @@ -108,6 +110,18 @@ void K_BossInfoTicker(void) void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions) { + if (!(gametyperules & GTR_BOSS)) + { + return; + } + + bossinfo.valid = true; + + if (!leveltime) + { + bossinfo.coolintro = true; + } + if (enemyname && enemyname[0]) { Z_Free(bossinfo.enemyname); @@ -158,6 +172,9 @@ void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen) { + if (bossinfo.valid == false) + return; + if (magnitude > FRACUNIT) magnitude = FRACUNIT; else if (magnitude < 0) @@ -177,6 +194,9 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean { UINT8 i; + if (bossinfo.valid == false) + return; + // First check whether the spot is already in the list and simply redeclaring weakness (for example, a vulnerable moment in the pattern). for (i = 0; i < NUMWEAKSPOTS; i++) if (bossinfo.weakspots[i].spot == spot) @@ -206,3 +226,16 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean bossinfo.doweakspotsound = spottype; } } + +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + See header file for description. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void) +{ + if (bossinfo.valid == false) + return false; + return bossinfo.coolintro; +} diff --git a/src/k_boss.h b/src/k_boss.h index 9986d3e36..2ce43adea 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -43,7 +43,8 @@ struct weakspot_t extern struct bossinfo { - boolean boss; ///< If true, then we are fighting a boss + boolean valid; ///< If true, then data in this struct is valid + UINT8 healthbar; ///< Actual health bar fill amount UINT8 visualbar; ///< Tracks above, but with delay fixed_t visualdiv; ///< How far apart health bar divisions should appear @@ -52,7 +53,7 @@ extern struct bossinfo UINT8 barlen; ///< The length of the bar (only reduced when a boss is deceased) char *enemyname; ///< The name next to the bar weakspot_t weakspots[NUMWEAKSPOTS]; ///< Array of weak spots (for minimap/object tracking) - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean coolintro; ///< Determines whether the map start(s/ed) with a boss-specific intro. spottype_t doweakspotsound; ///< If nonzero, at least one weakspot was declared this tic tic_t titleshow; ///< Show this many letters on the titlecard sfxenum_t titlesound; ///< Sound to play when title typing @@ -116,6 +117,18 @@ void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen); void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean minimap); +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + Checks whether the Versus-specific intro is playing for this map start. + + Return:- + true if cool intro in action, + otherwise false. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_bot.c b/src/k_bot.c index caf6aa55f..e6604d338 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -27,7 +27,6 @@ #include "m_random.h" #include "r_things.h" // numskins #include "k_race.h" // finishBeamLine -#include "k_boss.h" #include "m_perfstats.h" @@ -168,7 +167,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || !(gametyperules & GTR_BOTS) || bossinfo.boss == true) + if (difficulty == 0 || !(gametyperules & GTR_BOTS)) { wantedbots = 0; } diff --git a/src/k_collide.c b/src/k_collide.c index 06d6900e6..e9e832b3d 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -324,6 +324,11 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) if (!spin) { + if (minehitlag == 0) + { + minehitlag = actor->hitlag; + } + Obj_SpawnBrolyKi(actor, minehitlag); return minehitlag; diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 221e9d10c..6d879054b 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -11,7 +11,6 @@ /// \brief Grand Prix mode game logic & bot behaviors #include "k_grandprix.h" -#include "k_boss.h" #include "k_specialstage.h" #include "doomdef.h" #include "d_player.h" @@ -339,7 +338,7 @@ void K_UpdateGrandPrixBots(void) continue; } - players[i].spectator = (grandprixinfo.eventmode != GPEVENT_NONE); + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); } // Find the rival. @@ -529,7 +528,7 @@ void K_RetireBots(void) UINT8 i; if (grandprixinfo.gp == true - && ((grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + && ((grandprixinfo.cup != NULL && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) || grandprixinfo.eventmode != GPEVENT_NONE)) { // No replacement. @@ -676,7 +675,7 @@ void K_PlayerLoseLife(player_t *player) return; } - if (player->spectator || player->exiting || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) + if (player->spectator || (player->exiting && !(player->pflags & PF_NOCONTEST)) || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) { return; } @@ -703,24 +702,12 @@ void K_PlayerLoseLife(player_t *player) --------------------------------------------------*/ boolean K_CanChangeRules(boolean allowdemos) { - if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) + if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) { // Don't cheat the rules of the GP! return false; } - if (bossinfo.boss == true) - { - // Don't cheat the boss! - return false; - } - - if (specialStage.active == true) - { - // Don't cheat special stages! - return false; - } - if (marathonmode) { // Don't cheat the endurance challenge! diff --git a/src/k_hud.c b/src/k_hud.c index 8da9ca089..cc605df83 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -785,6 +785,29 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) return NULL; } +static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) +{ + UINT8 offset; + + item = K_ItemResultToType(item); + + switch (item) + { + case KITEM_INVINCIBILITY: + offset = 7; + break; + + case KITEM_ORBINAUT: + offset = 4; + break; + + default: + offset = 1; + } + + return K_GetCachedItemPatch(item, offset); +} + //} INT32 ITEM_X, ITEM_Y; // Item Window @@ -1863,7 +1886,7 @@ static boolean K_drawKartPositionFaces(void) ranklines--; i = ranklines; - if (gametype == GT_BATTLE || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) + if ((gametyperules & GTR_POINTLIMIT) || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) { if (i > 4) // could be both... i = 4; @@ -1951,7 +1974,7 @@ static boolean K_drawKartPositionFaces(void) if (i == strank) V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[rankplayer[i]].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumpers <= 0) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); else { @@ -1973,7 +1996,7 @@ static void K_drawBossHealthBar(void) UINT8 i = 0, barstatus = 1, randlen = 0, darken = 0; const INT32 startx = BASEVIDWIDTH - 23; INT32 starty = BASEVIDHEIGHT - 25; - INT32 rolrand = 0; + INT32 rolrand = 0, randtemp = 0; boolean randsign = false; if (bossinfo.barlen <= 1) @@ -2019,7 +2042,9 @@ static void K_drawBossHealthBar(void) barstatus = 2; } - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; randsign = M_RandomChance(FRACUNIT/2); // Right wing. @@ -2034,7 +2059,9 @@ static void K_drawBossHealthBar(void) randlen--; if (!randlen) { - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; if (barstatus > 1) { rolrand = M_RandomKey(barstatus)+1; @@ -2265,7 +2292,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); V_DrawMappedPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin][FACE_RANK], colormap); - /*if (gametype == GT_BATTLE && players[tab[i].num].bumpers > 0) -- not enough space for this + /*if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers > 0) -- not enough space for this { INT32 bumperx = x+19; V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); @@ -2280,7 +2307,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN if (tab[i].num == whiteplayer) V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[tab[i].num].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers <= 0) V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); else { @@ -2291,7 +2318,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); } - if (gametype == GT_RACE) + if ((gametyperules & GTR_CIRCUIT)) { #define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time)) if (scorelines >= 8) @@ -2300,7 +2327,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count)); } else @@ -2309,7 +2336,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count)); } #undef timestring @@ -2326,40 +2353,13 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN } } -#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) - -static void K_drawKartLapsAndRings(void) +static void K_drawKartLaps(void) { - const boolean uselives = G_GametypeUsesLives(); - SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; - UINT8 rn[2]; - INT32 ringflip = 0; - UINT8 *ringmap = NULL; - boolean colorring = false; - INT32 ringx = 0; - - rn[0] = ((abs(stplyr->rings) / 10) % 10); - rn[1] = (abs(stplyr->rings) % 10); - - if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt - { - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); - colorring = true; - } - else if (stplyr->rings >= 20) // Maxed out - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); - - if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) - { - ringflip = V_FLIP; - ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; - ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); - } if (r_splitscreen > 1) { - INT32 fx = 0, fy = 0, fr = 0; + INT32 fx = 0, fy = 0; INT32 flipflag = 0; // pain and suffering defined below @@ -2385,8 +2385,6 @@ static void K_drawKartLapsAndRings(void) } } - fr = fx; - // Laps V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); @@ -2413,6 +2411,75 @@ static void K_drawKartLapsAndRings(void) V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(numlaps) % 10]); } + } + else + { + // Laps + V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + } +} + +#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) + +static void K_drawRingCounter(void) +{ + const boolean uselives = G_GametypeUsesLives(); + SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + UINT8 rn[2]; + INT32 ringflip = 0; + UINT8 *ringmap = NULL; + boolean colorring = false; + INT32 ringx = 0, fy = 0; + + rn[0] = ((abs(stplyr->rings) / 10) % 10); + rn[1] = (abs(stplyr->rings) % 10); + + if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt + { + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); + colorring = true; + } + else if (stplyr->rings >= 20) // Maxed out + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); + + if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) + { + ringflip = V_FLIP; + ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; + ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); + } + + if (r_splitscreen > 1) + { + INT32 fx = 0, fr = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (r_splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = LAPS_X; + fy = LAPS_Y; + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = LAPS_X; + fy = LAPS_Y; + splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + flipflag = V_FLIP; // make the string right aligned and other shit + } + } + + fr = fx; // Rings if (!uselives) @@ -2447,41 +2514,42 @@ static void K_drawKartLapsAndRings(void) } else { - // Laps - V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + fy = LAPS_Y-11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; // Rings if (!uselives) - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); else - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); - V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); + V_DrawMappedPatch(LAPS_X+ringx+7, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); if (stplyr->rings < 0) // Draw the minus for ring debt { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+35, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } else { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) - V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); + V_DrawScaledPatch(LAPS_X-5, fy-17, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); + V_DrawMappedPatch(LAPS_X+46, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); if (stplyr->lives >= 0) - V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow + V_DrawScaledPatch(LAPS_X+63, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow } } } @@ -2498,7 +2566,7 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if (gametype == GT_BATTLE) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else @@ -2588,7 +2656,7 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if (gametype == GT_BATTLE) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) battleoffset = -4; V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); @@ -2793,14 +2861,15 @@ static void K_drawKartBumpersOrKarma(void) else V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); - if (bossinfo.boss) - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); - else // TODO BETTER HUD + if (gametyperules & GTR_KARMA) // TODO BETTER HUD V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); + else + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); } } } +#if 0 static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; @@ -2875,6 +2944,7 @@ static void K_drawKartWanted(void) } } } +#endif //if 0 static void K_drawKartPlayerCheck(void) { @@ -3358,7 +3428,7 @@ static void K_drawKartNameTags(void) c.z = viewz; // Maybe shouldn't be handling this here... but the camera info is too good. - if (bossinfo.boss) + if (bossinfo.valid == true) { weakspotdraw_t weakspotdraw[NUMWEAKSPOTS]; UINT8 numdraw = 0; @@ -3810,7 +3880,7 @@ static void K_drawKartMinimap(void) y -= SHORT(AutomapPic->topoffset); // Draw the super item in Battle - if (gametype == GT_BATTLE && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) { @@ -3920,8 +3990,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, faceprefix[skin][FACE_MINIMAP], colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[i].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[i]))) + if (((gametyperules & GTR_CIRCUIT) && players[i].position == spbplace) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -3979,7 +4049,7 @@ static void K_drawKartMinimap(void) } // ...but first, any boss targets. - if (bossinfo.boss) + if (bossinfo.valid == true) { for (i = 0; i < NUMWEAKSPOTS; i++) { @@ -4047,8 +4117,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[localplayers[i]]))) + if (((gametyperules & GTR_CIRCUIT) && players[localplayers[i]].position == spbplace) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -4836,7 +4906,7 @@ void K_drawKartFreePlay(void) if (!LUA_HudEnabled(hud_freeplay)) return; - if (modeattacking || grandprixinfo.gp || bossinfo.boss || stplyr->spectator) + if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator) return; if (lt_exitticker < TICRATE/2) @@ -4883,39 +4953,6 @@ K_drawMiniPing (void) static void K_drawDistributionDebugger(void) { - patch_t *patches[NUMKARTRESULTS] = { - kp_sadface[1], - kp_sneaker[1], - kp_rocketsneaker[1], - kp_invincibility[7], - kp_banana[1], - kp_eggman[1], - kp_orbinaut[4], - kp_jawz[1], - kp_mine[1], - kp_landmine[1], - kp_ballhog[1], - kp_selfpropelledbomb[1], - kp_grow[1], - kp_shrink[1], - kp_lightningshield[1], - kp_bubbleshield[1], - kp_flameshield[1], - kp_hyudoro[1], - kp_pogospring[1], - kp_superring[1], - kp_kitchensink[1], - kp_droptarget[1], - kp_gardentop[1], - - kp_sneaker[1], - kp_sneaker[1], - kp_banana[1], - kp_orbinaut[4], - kp_orbinaut[4], - kp_jawz[1] - }; - itemroulette_t rouletteData = {0}; const fixed_t scale = (FRACUNIT >> 1); @@ -4944,7 +4981,8 @@ static void K_drawDistributionDebugger(void) y = -pad; } - V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, patches[item], NULL); + V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, + K_GetSmallStaticCachedItemPatch(item), NULL); // Display amount for multi-items amount = K_ItemResultToAmount(item); @@ -5024,10 +5062,10 @@ void K_drawKartHUD(void) return; } - battlefullscreen = ((gametyperules & (GTR_BUMPERS|GTR_KARMA)) == (GTR_BUMPERS|GTR_KARMA) + battlefullscreen = (!(gametyperules & GTR_CIRCUIT) && (stplyr->exiting - || (stplyr->bumpers <= 0 - && stplyr->karmadelay > 0 + || ((gametyperules & GTR_BUMPERS) && (stplyr->bumpers <= 0) + && ((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) && !(stplyr->pflags & PF_ELIMINATED) && stplyr->playerstate == PST_LIVE))); @@ -5043,11 +5081,13 @@ void K_drawKartHUD(void) K_drawKartNameTags(); // Draw WANTED status +#if 0 if (gametype == GT_BATTLE) { if (LUA_HudEnabled(hud_wanted)) K_drawKartWanted(); } +#endif if (LUA_HudEnabled(hud_minimap)) K_drawKartMinimap(); @@ -5094,32 +5134,26 @@ void K_drawKartHUD(void) { if (LUA_HudEnabled(hud_position)) { - if (bossinfo.boss) + if (bossinfo.valid) { K_drawBossHealthBar(); } - else if (gametype == GT_RACE) // Race-only elements (not currently gametyperuleable) + else if (freecam) + ; + else if ((gametyperules & GTR_POWERSTONES)) { - if (!islonesome) - { - // Draw the numerical position - K_DrawKartPositionNum(stplyr->position); - } - } - else if (gametype == GT_BATTLE) // Battle-only (ditto) - { - if (!freecam && !battlecapsules) - { + if (!battlecapsules) K_drawKartEmeralds(); - } } + else if (!islonesome) + K_DrawKartPositionNum(stplyr->position); } if (LUA_HudEnabled(hud_gametypeinfo)) { if (gametyperules & GTR_CIRCUIT) { - K_drawKartLapsAndRings(); + K_drawKartLaps(); } else if (gametyperules & GTR_BUMPERS) { @@ -5141,8 +5175,12 @@ void K_drawKartHUD(void) { K_drawBlueSphereMeter(); } + else + { + K_drawRingCounter(); + } - if (modeattacking && !bossinfo.boss) + if (modeattacking && !bossinfo.valid) { // Draw the input UI if (LUA_HudEnabled(hud_position)) @@ -5172,10 +5210,12 @@ void K_drawKartHUD(void) } // Race overlays - if (gametype == GT_RACE && !freecam) + if (!freecam) { if (stplyr->exiting) K_drawKartFinish(true); + else if (!(gametyperules & GTR_CIRCUIT)) + ; else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } @@ -5187,7 +5227,7 @@ void K_drawKartHUD(void) if (modeattacking || freecam) // everything after here is MP and debug only return; - if (gametype == GT_BATTLE && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * + if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. diff --git a/src/k_kart.c b/src/k_kart.c index f1c0b8be2..603266aed 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6,7 +6,6 @@ #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_pwrlv.h" #include "k_color.h" #include "k_respawn.h" @@ -41,6 +40,7 @@ #include "k_follower.h" #include "k_objects.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_specialstage.h" #include "k_roulette.h" @@ -109,11 +109,43 @@ void K_TimerInit(void) boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); - if (specialStage.active == true) + // Rooooooolllling staaaaaaart + if ((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) + { + S_StartSound(NULL, sfx_s25f); + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + if (player->spectator == true) + { + continue; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + continue; + } + + // Rolling start? lol + P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); + } + } + + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { K_InitSpecialStage(); } - else if (bossinfo.boss == false) + else if (K_CheckBossIntro() == true) + ; + else { if (!domodeattack) { @@ -152,7 +184,7 @@ void K_TimerInit(void) K_BattleInit(domodeattack); - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) + if ((gametyperules & GTR_TIMELIMIT) && !modeattacking) { if (!K_CanChangeRules(true)) { @@ -162,7 +194,7 @@ void K_TimerInit(void) } else { - timelimitintics = timelimits[gametype] * (60*TICRATE); + timelimitintics = gametypes[gametype]->timelimit * (60*TICRATE); } } else @@ -310,8 +342,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartbumpers); CV_RegisterVar(&cv_kartfrantic); CV_RegisterVar(&cv_kartencore); - CV_RegisterVar(&cv_kartvoterulechanges); - CV_RegisterVar(&cv_kartgametypepreference); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_kartbot); @@ -342,15 +372,21 @@ boolean K_IsPlayerLosing(player_t *player) INT32 winningpos = 1; UINT8 i, pcount = 0; + if (player->pflags & PF_NOCONTEST) + return true; + if (battlecapsules && numtargets == 0) return true; // Didn't even TRY? - if (battlecapsules || bossinfo.boss) + if (battlecapsules || (gametyperules & GTR_BOSS)) return (player->bumpers <= 0); // anything short of DNF is COOL if (player->position == 1) return false; + if (specialstageinfo.valid == true) + return false; // anything short of DNF is COOL + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -515,7 +551,7 @@ boolean K_TimeAttackRules(void) UINT8 playing = 0; UINT8 i; - if (specialStage.active == true) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { // Kind of a hack -- Special Stages // are expected to be 1-player, so @@ -588,13 +624,22 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) // from causing super crazy bumps. fixed_t spd = K_GetKartSpeed(mobj->player, false, true); + fixed_t speedfactor = 8 * mapobjectscale; + weight = (mobj->player->kartweight) * FRACUNIT; - if (mobj->player->speed > spd) - weight += FixedDiv((mobj->player->speed - spd), 8 * mapobjectscale); + if (against && against->type == MT_MONITOR) + { + speedfactor /= 5; // speed matters more + } + else + { + if (mobj->player->itemtype == KITEM_BUBBLESHIELD) + weight += 9*FRACUNIT; + } - if (mobj->player->itemtype == KITEM_BUBBLESHIELD) - weight += 9*FRACUNIT; + if (mobj->player->speed > spd) + weight += FixedDiv((mobj->player->speed - spd), speedfactor); } return weight; @@ -1272,10 +1317,9 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed player->draftpower += add; } - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules - // Double speed in Battle + // Double speed in smaller environments player->draftpower += add; } } @@ -1304,8 +1348,7 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed */ static void K_UpdateDraft(player_t *player) { - const boolean addUfo = ((specialStage.active == true) - && (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)); + mobj_t *addUfo = K_GetPossibleSpecialTarget(); fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; @@ -1335,9 +1378,8 @@ static void K_UpdateDraft(player_t *player) minDist = 640 * player->mo->scale; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules minDist /= 4; draftdistance *= 2; leniency *= 4; @@ -1346,10 +1388,10 @@ static void K_UpdateDraft(player_t *player) // Not enough speed to draft. if (player->speed >= 20 * player->mo->scale) { - if (addUfo == true) + if (addUfo != NULL) { // Tether off of the UFO! - if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true) + if (K_TryDraft(player, addUfo, minDist, draftdistance, leniency) == true) { return; // Finished doing our draft. } @@ -1403,11 +1445,11 @@ static void K_UpdateDraft(player_t *player) fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true); } - else if (addUfo == true) + else if (addUfo != NULL) { // kind of a hack to not have to mess with how lastdraft works - fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z); - K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true); + fixed_t dist = P_AproxDistance(P_AproxDistance(addUfo->x - player->mo->x, addUfo->y - player->mo->y), addUfo->z - player->mo->z); + K_DrawDraftCombiring(player, addUfo, dist, draftdistance, true); } } else // Remove draft speed boost. @@ -2454,7 +2496,7 @@ void K_PlayOvertakeSound(mobj_t *source) { boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); - if (!gametype == GT_RACE) // Only in race + if (!(gametyperules & GTR_CIRCUIT)) // Only in race return; // 4 seconds from before race begins, 10 seconds afterwards @@ -2963,8 +3005,7 @@ fixed_t K_GetSpindashChargeSpeed(player_t *player) // (can be higher than this value when overcharged) const fixed_t val = (10*FRACUNIT/277) + (((player->kartspeed + player->kartweight) + 2) * FRACUNIT) / 45; - // TODO: gametyperules - return (gametype == GT_BATTLE) ? (4 * val) : val; + return (gametyperules & GTR_CLOSERPLAYERS) ? (4 * val) : val; } // sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be @@ -3099,9 +3140,8 @@ static void K_GetKartBoostPower(player_t *player) // 30% - 44%, each point of speed adds 1.75% fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * ((7*FRACUNIT)/400)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules draftspeed *= 2; } @@ -3224,7 +3264,7 @@ fixed_t K_GetKartAccel(player_t *player) k_accel += 17 * (9 - player->kartspeed); // 121 - 257 // karma bomb gets 2x acceleration - if (gametype == GT_BATTLE && player->bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) k_accel *= 2; // Marble Garden Top gets 1200% accel @@ -3238,9 +3278,8 @@ UINT16 K_GetKartFlashing(player_t *player) { UINT16 tics = flashingtics; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_BUMPERS) { - // TODO: gametyperules return 1; } @@ -3303,7 +3342,8 @@ SINT8 K_GetForwardMove(player_t *player) return 0; } - if (player->sneakertimer || player->spindashboost) + if (player->sneakertimer || player->spindashboost + || (((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) && (leveltime < TICRATE/2))) { return MAXPLMOVE; } @@ -3545,6 +3585,12 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN UINT8 points = 1; boolean trapItem = false; + if (!(gametyperules & GTR_POINTLIMIT)) + { + // No points in this gametype. + return; + } + if (player == NULL || victim == NULL) { // Invalid player or victim @@ -3580,11 +3626,8 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } } - if (gametyperules & GTR_POINTLIMIT) - { - P_AddPlayerScore(player, points); - K_SpawnBattlePoints(player, victim, points); - } + P_AddPlayerScore(player, points); + K_SpawnBattlePoints(player, victim, points); } void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type) @@ -4132,7 +4175,7 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) player->karmadelay = comebacktime; - if (bossinfo.boss) + if (gametyperules & GTR_BOSS) { P_DoTimeOver(player); } @@ -5056,7 +5099,7 @@ void K_SpawnDraftDust(mobj_t *mo) { UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) leniency *= 4; ang = mo->player->drawangle; @@ -6183,17 +6226,47 @@ void K_DropHnextList(player_t *player, boolean keepshields) } } +SINT8 K_GetTotallyRandomResult(UINT8 useodds) +{ + INT32 spawnchance[NUMKARTRESULTS]; + INT32 totalspawnchance = 0; + INT32 i; + + memset(spawnchance, 0, sizeof (spawnchance)); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + // Avoid calling K_FillItemRouletteData since that + // function resets PR_ITEM_ROULETTE. + spawnchance[i] = ( + totalspawnchance += K_KartGetItemOdds(NULL, NULL, useodds, i) + ); + } + + if (totalspawnchance > 0) + { + totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); + for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + } + else + { + i = KITEM_SAD; + } + + return i; +} + mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); mobj_t *backdrop = P_SpawnMobjFromMobj(drop, 0, 0, 0, MT_OVERLAY); - + P_SetTarget(&backdrop->target, drop); P_SetMobjState(backdrop, S_ITEMBACKDROP); P_SetScale(drop, drop->scale>>4); drop->destscale = (3*drop->destscale)/2; - + drop->angle = angle; P_Thrust(drop, FixedAngle(P_RandomFixed(PR_ITEM_ROULETTE) * 180) + angle, @@ -6205,62 +6278,31 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { - itemroulette_t rouletteData = {0}; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - INT32 i; + const SINT8 i = K_GetTotallyRandomResult(amount); - memset(spawnchance, 0, sizeof (spawnchance)); + // TODO: this is bad! + // K_KartGetItemResult requires a player + // but item roulette will need rewritten to change this - useodds = amount; + const SINT8 newType = K_ItemResultToType(i); + const UINT8 newAmount = K_ItemResultToAmount(i); - K_FillItemRouletteData(NULL, &rouletteData); - - for (i = 1; i < NUMKARTRESULTS; i++) + if (newAmount > 1) { - spawnchance[i] = ( - totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) - ); - } + UINT8 j; - if (totalspawnchance > 0) - { - UINT8 newType; - UINT8 newAmount; - - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - // TODO: this is bad! - // K_KartGetItemResult requires a player - // but item roulette will need rewritten to change this - - newType = K_ItemResultToType(i); - newAmount = K_ItemResultToAmount(i); - - if (newAmount > 1) + for (j = 0; j < newAmount-1; j++) { - UINT8 j; - - for (j = 0; j < newAmount-1; j++) - { - K_CreatePaperItem( - x, y, z, - angle, flip, - newType, 1 - ); - } + K_CreatePaperItem( + x, y, z, + angle, flip, + newType, 1 + ); } + } - drop->threshold = newType; - drop->movecount = 1; - } - else - { - drop->threshold = 1; - drop->movecount = 1; - } + drop->threshold = newType; + drop->movecount = 1; } else { @@ -6273,6 +6315,11 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 P_SetTarget(&backdrop->tracer, drop); backdrop->flags2 |= MF2_LINKDRAW; + if (gametyperules & GTR_BUMPERS) + { + drop->fuse = BATTLE_DESPAWN_TIME; + } + return drop; } @@ -6793,10 +6840,10 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) mobj_t *wtarg = NULL; INT32 i; - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - // Always target the UFO. - return specialStage.ufo; + // Always target the UFO (but not the emerald) + return K_GetPossibleSpecialTarget(); } for (i = 0; i < MAXPLAYERS; i++) @@ -7472,7 +7519,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_SpawnGrowShrinkParticles(player->mo, player->growshrinktimer); } - if (gametype == GT_RACE && player->rings <= 0) // spawn ring debt indicator + if (!(gametyperules & GTR_SPHERES) && player->rings <= 0) // spawn ring debt indicator { mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, player->mo->z + P_GetMobjZMovement(player->mo) + player->mo->height + (24*player->mo->scale), MT_THOK); @@ -7538,8 +7585,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); } - player->karthud[khud_timeovercam] = 0; - // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0) { @@ -7790,7 +7835,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->eggmanexplode) { - if (player->spectator || (gametype == GT_BATTLE && !player->bumpers)) + if (player->spectator || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) player->eggmanexplode = 0; else { @@ -8032,10 +8077,11 @@ void K_KartPlayerAfterThink(player_t *player) mobj_t *ret = NULL; - if (specialStage.active == true && lastTargID == MAXPLAYERS) + if (specialstageinfo.valid == true + && lastTargID == MAXPLAYERS) { - // Aiming at the UFO. - lastTarg = specialStage.ufo; + // Aiming at the UFO (but never the emerald). + lastTarg = K_GetPossibleSpecialTarget(); } else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) && playeringame[lastTargID] == true) @@ -9332,15 +9378,18 @@ static INT32 K_FlameShieldMax(player_t *player) UINT8 numplayers = 0; UINT8 i; - for (i = 0; i < MAXPLAYERS; i++) + if (gametyperules & GTR_CIRCUIT) { - if (playeringame[i] && !players[i].spectator) - numplayers++; - if (players[i].position == 1) - disttofinish = players[i].distancetofinish; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + numplayers++; + if (players[i].position == 1) + disttofinish = players[i].distancetofinish; + } } - if (numplayers <= 1 || gametype == GT_BATTLE) + if (numplayers <= 1) { return 16; // max when alone, for testing // and when in battle, for chaos @@ -9594,8 +9643,7 @@ static void K_KartSpindash(player_t *player) { fixed_t thrust = FixedMul(player->mo->scale, player->spindash*FRACUNIT/5); - // TODO: gametyperules - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) thrust *= 2; // Give a bit of a boost depending on charge. @@ -10402,8 +10450,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } - // TODO: gametyperules - player->growshrinktimer = max(player->growshrinktimer, (gametype == GT_BATTLE ? 8 : 12) * TICRATE); + player->growshrinktimer = max(player->growshrinktimer, ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE); if (player->invincibilitytimer > 0) { @@ -10561,8 +10608,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) { - // TODO: gametyperules - const INT32 incr = gametype == GT_BATTLE ? 4 : 2; + const INT32 incr = (gametyperules & GTR_CLOSERPLAYERS) ? 4 : 2; if (player->flamedash == 0) { @@ -10598,8 +10644,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { player->pflags |= PF_HOLDREADY; - // TODO: gametyperules - if (gametype != GT_BATTLE || leveltime % 6 == 0) + if (!(gametyperules & GTR_CLOSERPLAYERS) || leveltime % 6 == 0) { if (player->flamemeter > 0) player->flamemeter--; @@ -10721,18 +10766,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->hyudorotimer > 0) { - INT32 hyu = hyudorotime; - - if (gametype == GT_RACE) - hyu *= 2; // double in race - if (leveltime & 1) { player->mo->renderflags |= RF_DONTDRAW; } else { - if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyu-(TICRATE/2)) + if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyudorotime-(TICRATE/2)) player->mo->renderflags &= ~K_GetPlayerDontDrawFlag(player); else player->mo->renderflags &= ~RF_DONTDRAW; @@ -10745,17 +10785,17 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->renderflags &= ~RF_DONTDRAW; } - if (gametype == GT_BATTLE && player->bumpers <= 0) // dead in match? you da bomb + if (!(gametyperules & GTR_BUMPERS) || player->bumpers > 0) + { + player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); + } + else // dead in match? you da bomb { K_DropItems(player); //K_StripItems(player); K_StripOther(player); player->mo->renderflags |= RF_GHOSTLY; player->flashing = player->karmadelay; } - else if (gametype == GT_RACE || player->bumpers > 0) - { - player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); - } if (player->trickpanel == 1) { @@ -10968,7 +11008,7 @@ void K_CheckSpectateStatus(void) continue; if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in return; - if (gametype == GT_RACE && players[i].laps >= 2) // DON'T allow if the race is at 2 laps + if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) // DON'T allow if the race is at 2 laps return; continue; } @@ -11164,4 +11204,27 @@ void K_HandleDirectionalInfluence(player_t *player) player->mo->momy = FixedMul(speed, finalY); } +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) +{ + switch (itemType) + { + case KITEM_ORBINAUT: + part->sprite = SPR_ITMO; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(itemCount); + break; + case KITEM_INVINCIBILITY: + part->sprite = SPR_ITMI; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); + break; + case KITEM_SAD: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; + break; + default: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); + break; + } +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index f74f14e12..76043919b 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -138,6 +138,7 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage); void K_SpawnDriftBoostExplosion(player_t *player, int stage); void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); +SINT8 K_GetTotallyRandomResult(UINT8 useodds); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); void K_DropRocketSneaker(player_t *player); @@ -197,6 +198,8 @@ fixed_t K_ItemScaleForPlayer(player_t *player); void K_SetItemOut(player_t *player); void K_UnsetItemOut(player_t *player); +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_menu.h b/src/k_menu.h index cfb325fd6..f7a9c9725 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -119,7 +119,11 @@ struct menucolor_t { extern menucolor_t *menucolorhead, *menucolortail; -extern CV_PossibleValue_t gametype_cons_t[]; +extern INT16 menugametype; +void M_NextMenuGametype(UINT32 forbidden); +void M_PrevMenuGametype(UINT32 forbidden); +void M_HandleHostMenuGametype(INT32 choice); +void M_HandlePauseMenuGametype(INT32 choice); // // MENU TYPEDEFS @@ -417,6 +421,7 @@ extern menu_t MISC_StatisticsDef; typedef enum { mpause_addons = 0, + mpause_changegametype, mpause_switchmap, mpause_restartmap, mpause_tryagain, @@ -435,9 +440,6 @@ typedef enum mpause_title, } mpause_e; -extern menuitem_t PAUSE_GamemodesMenu[]; -extern menu_t PAUSE_GamemodesDef; - extern menuitem_t PAUSE_PlaybackMenu[]; extern menu_t PAUSE_PlaybackMenuDef; @@ -696,7 +698,6 @@ extern struct cupgrid_s { size_t cappages; tic_t previewanim; boolean grandprix; // Setup grand prix server after picking - boolean netgame; // Start the game in an actual server } cupgrid; typedef struct levelsearch_s { @@ -713,6 +714,7 @@ extern struct levellist_s { UINT16 dest; INT16 choosemap; UINT8 newgametype; + UINT8 guessgt; levelsearch_t levelsearch; boolean netgame; // Start the game in an actual server } levellist; @@ -773,7 +775,6 @@ void M_MPOptSelect(INT32 choice); void M_MPOptSelectInit(INT32 choice); void M_MPOptSelectTick(void); boolean M_MPResetOpts(void); -extern consvar_t cv_dummygametype; // lazy hack to allow us to select the GT on the server host submenu extern consvar_t cv_dummyip; // I HAVE // HAVE YOUR IP ADDRESS (This just the hack Cvar we'll type into and then it apends itself to "connect" in the console for IP join) diff --git a/src/k_menudef.c b/src/k_menudef.c index 99903d788..a4b8de7bd 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -89,12 +89,15 @@ 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 hedgehog standing in this free-for-all!", + {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}, }; @@ -359,8 +362,8 @@ menuitem_t PLAY_MP_Host[] = {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_CVAR, "Gamemode", "Are we racing? Or perhaps battling?", - NULL, {.cvar = &cv_dummygametype}, 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}, @@ -1132,9 +1135,6 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "Vote Timer", "Set how long players have to vote.", NULL, {.cvar = &cv_votetime}, 0, 0}, - {IT_STRING | IT_CVAR, "Vote Mode Change", "Set how often voting proposes a different gamemode.", - NULL, {.cvar = &cv_kartvoterulechanges}, 0, 0}, - {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, @@ -1593,8 +1593,11 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", NULL, {.routine = M_Addons}, 0, 0}, - {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", - NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM", + NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0}, + + {IT_STRING | IT_CALL, "CHANGE MAP", "M_ICOMAP", + NULL, {.routine = M_LevelSelectInit}, 0, -1}, {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", NULL, {.routine = M_RestartMap}, 0, 0}, @@ -1647,20 +1650,6 @@ menu_t PAUSE_MainDef = { M_PauseInputs }; -// PAUSE : Map switching gametype selection (In case you want to pick from battle / race...) -menuitem_t PAUSE_GamemodesMenu[] = -{ - {IT_STRING | IT_CALL, "Race", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE}, - - {IT_STRING | IT_CALL, "Battle", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, - - {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, -}; - -menu_t PAUSE_GamemodesDef = KARTGAMEMODEMENU(PAUSE_GamemodesMenu, &PAUSE_MainDef); - // Replay popup menu menuitem_t PAUSE_PlaybackMenu[] = { diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9071907bc..7cdbb91a7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -541,12 +541,7 @@ void M_Drawer(void) } else { -#ifdef DEVELOP // Development -- show revision / branch info - V_DrawThinString(vid.dupx, vid.height - 20*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, compbranch); - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, comprevision); -#else // Regular build - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, va("%s", VERSIONSTRING)); -#endif + F_VersionDrawer(); } @@ -1931,9 +1926,7 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) INT16 map, start = M_GetFirstLevelInList(&i, levelsearch); UINT8 starti = i; - V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - - if (levelsearch->cup && !M_CupLocked(levelsearch->cup)) + if (levelsearch->cup && maxlevels > 0) { add = (cupgrid.previewanim / 82) % maxlevels; map = start; @@ -1979,16 +1972,18 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) } } -static void M_DrawCupTitle(INT16 y, cupheader_t *cup) +static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) { + UINT8 temp = 0; + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); - if (cup) + if (levelsearch->cup) { - boolean unlocked = !M_CupLocked(cup); + boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); - patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); - const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); + patch_t *icon = W_CachePatchName(levelsearch->cup->icon, PU_CACHE); + const char *str = (unlocked ? va("%s Cup", levelsearch->cup->name) : "???"); INT16 offset = V_LSTitleLowStringWidth(str, 0) / 2; V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); @@ -2002,32 +1997,35 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) else { if (currentMenu == &PLAY_LevelSelectDef) - V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", Gametype_Names[levellist.newgametype])); + { + UINT8 namedgt = (levellist.guessgt != MAXGAMETYPES) ? levellist.guessgt : levellist.newgametype; + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[namedgt]->name)); + } } } void M_DrawCupSelect(void) { - UINT8 i, j; + UINT8 i, j, temp = 0; levelsearch_t templevelsearch = levellist.levelsearch; // full copy - templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; for (i = 0; i < CUPMENU_COLUMNS; i++) { for (j = 0; j < CUPMENU_ROWS; j++) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); - cupheader_t *iconcup = cupgrid.builtgrid[id]; patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; - if (!iconcup) + if (!cupgrid.builtgrid[id]) break; - /*if (iconcup->emeraldnum == 0) + templevelsearch.cup = cupgrid.builtgrid[id]; + + /*if (templevelsearch.cup->emeraldnum == 0) patch = W_CachePatchName("CUPMON3A", PU_CACHE); - else*/ if (iconcup->emeraldnum > 7) + else*/ if (templevelsearch.cup->emeraldnum > 7) { patch = W_CachePatchName("CUPMON2A", PU_CACHE); icony = 5; @@ -2040,14 +2038,14 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (M_CupLocked(iconcup)) + if (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); } else { - V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(iconcup->icon, PU_CACHE)); + V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); } } @@ -2058,8 +2056,12 @@ void M_DrawCupSelect(void) 0, W_CachePatchName("CUPCURS", PU_CACHE) ); + templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + + V_DrawFill(0, 146 + (24*menutransition.tics), BASEVIDWIDTH, 54, 31); M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); - M_DrawCupTitle(120 - (24*menutransition.tics), templevelsearch.cup); + + M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) @@ -2225,7 +2227,7 @@ void M_DrawLevelSelect(void) map = M_GetNextLevelInList(map, &j, &levellist.levelsearch); } - M_DrawCupTitle(tay, levellist.levelsearch.cup); + M_DrawCupTitle(tay, &levellist.levelsearch); } void M_DrawTimeAttack(void) @@ -2261,7 +2263,8 @@ void M_DrawTimeAttack(void) laprec = mapheaderinfo[map]->mainrecord->lap; } - if (levellist.newgametype != GT_BATTLE) + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[map]->numlaps != 1)) { V_DrawRightAlignedString(rightedge-12, timeheight, highlightflags, "BEST LAP:"); K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2); @@ -2462,6 +2465,20 @@ void M_DrawMPHost(void) } break; } + case IT_KEYHANDLER: + { + if (currentMenu->menuitems[i].itemaction.routine != M_HandleHostMenuGametype) + break; + + w = V_ThinStringWidth(gametypes[menugametype]->name, V_6WIDTHSPACE); + V_DrawThinString(xp + 138 - w, yp, highlightflags|V_6WIDTHSPACE, gametypes[menugametype]->name); + if (i == itemOn) + { + V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow + } + break; + } } xp += 5; @@ -3680,27 +3697,19 @@ void M_DrawPause(void) case IT_STRING: { patch_t *pp; + UINT8 *colormap = NULL; - if (i == itemOn) + if (i == itemOn && (i == mpause_restartmap || i == mpause_tryagain)) { - if (i == mpause_restartmap || i == mpause_tryagain) - { - pp = W_CachePatchName( - va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), - PU_CACHE); - } - else - { - char iconame[9]; // 8 chars + \0 - strcpy(iconame, currentMenu->menuitems[i].tooltip); - iconame[7] = '2'; // Yes this is a stupid hack. Replace the last character with a 2 when we're selecting this graphic. - - pp = W_CachePatchName(iconame, PU_CACHE); - } + pp = W_CachePatchName( + va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), + PU_CACHE); } else { pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE); + if (i == itemOn) + colormap = yellowmap; } // 294 - 261 = 33 @@ -3711,7 +3720,7 @@ void M_DrawPause(void) // This double ternary is awful, yes. dypos = ypos + pausemenu.offset; - V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, NULL); + V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, colormap); ypos += 50; itemsdrawn++; // We drew that! @@ -3760,12 +3769,26 @@ void M_DrawPause(void) word1[word1len] = '\0'; word2[word2len] = '\0'; - // If there's no 2nd word, take this opportunity to center this line of text. - if (word1len) - V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + if (itemOn == mpause_changegametype) + { + INT32 w = V_LSTitleLowStringWidth(gametypes[menugametype]->name, 0)/2; - if (word2len) - V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75, 0, word1); + + V_DrawLSTitleLowString(220-w + offset*2, 103, V_YELLOWMAP, gametypes[menugametype]->name); + V_DrawCharacter(220-w + offset*2 - 8 - (skullAnimCounter/5), 103+6, '\x1C' | V_YELLOWMAP, false); // left arrow + V_DrawCharacter(220+w + offset*2 + 4 + (skullAnimCounter/5), 103+6, '\x1D' | V_YELLOWMAP, false); // right arrow + } + else + { + // If there's no 2nd word, take this opportunity to center this line of text. + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + + if (word2len) + V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + } } tic_t playback_last_menu_interaction_leveltime = 0; @@ -3964,9 +3987,22 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) if (demoref->numlaps) V_DrawThinString(x, y+9, V_SNAPTOTOP|V_ALLOWLOWERCASE, va("(%d laps)", demoref->numlaps)); - V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->gametype == GT_RACE ? - va("Race (%s speed)", kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue) : - "Battle Mode"); + { + const char *gtstring; + if (demoref->gametype < 0) + { + gtstring = "Custom (not loaded)"; + } + else + { + gtstring = gametypes[demoref->gametype]->name; + + if ((gametypes[demoref->gametype]->rules & GTR_CIRCUIT)) + gtstring = va("%s (%s)", gtstring, kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue); + } + + V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, gtstring); + } if (!demoref->standings[0].ranking) { @@ -3979,30 +4015,33 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+29, V_SNAPTOTOP|highlightflags, "WINNER"); V_DrawString(x+38, y+30, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->standings[0].name); - if (demoref->gametype == GT_RACE) + if (demoref->gametype >= 0) { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); - } - else - { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); - } + if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); + } + else + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); + } - if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) - { - V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); - } - else if (demoref->gametype == GT_RACE) - { - V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", - G_TicsToMinutes(demoref->standings[0].timeorscore, true), - G_TicsToSeconds(demoref->standings[0].timeorscore), - G_TicsToCentiseconds(demoref->standings[0].timeorscore) - )); - } - else - { - V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) + { + V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); + } + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + { + V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + } + else + { + V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", + G_TicsToMinutes(demoref->standings[0].timeorscore, true), + G_TicsToSeconds(demoref->standings[0].timeorscore), + G_TicsToCentiseconds(demoref->standings[0].timeorscore) + )); + } } // Character face! @@ -4197,14 +4236,16 @@ void M_DrawReplayStartMenu(void) if (demoref->standings[i].timeorscore == UINT32_MAX-1) V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demoref->gametype == GT_RACE) + else if (demoref->gametype < 0) + ; + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) + V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); + else V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", G_TicsToMinutes(demoref->standings[i].timeorscore, true), G_TicsToSeconds(demoref->standings[i].timeorscore), G_TicsToCentiseconds(demoref->standings[i].timeorscore) )); - else - V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); // Character face! @@ -4469,15 +4510,14 @@ void M_DrawAddons(void) // Challenges Menu -#define challengesbordercolor 27 - static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) { unlockable_t *ref = NULL; patch_t *pat = missingpat; UINT8 *colormap = NULL; fixed_t siz; - UINT8 id, num, work; + UINT8 id, num; + UINT32 edgelength; id = (i * CHALLENGEGRIDHEIGHT) + j; num = gamedata->challengegrid[id]; @@ -4485,19 +4525,19 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili // Empty spots in the grid are always unconnected. if (num >= MAXUNLOCKABLES) { - V_DrawFill(x, y, 16, 16, challengesbordercolor); goto drawborder; } // Okay, this is what we want to draw. ref = &unlockables[num]; + edgelength = (ref->majorunlock ? 30 : 14); + // ...unless we simply aren't unlocked yet. if ((gamedata->unlocked[num] == false) || (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME)) { - work = (ref->majorunlock) ? 2 : 1; - V_DrawFill(x, y, 16*work, 16*work, + V_DrawFill(x+1, y+1, edgelength, edgelength, ((challengesmenu.extradata[id] == CHE_HINT) ? 132 : 11)); goto drawborder; } @@ -4548,6 +4588,12 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili siz = (SHORT(pat->width) << FRACBITS); siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); + V_SetClipRect( + (x+1) << FRACBITS, (y+1) << FRACBITS, + edgelength << FRACBITS, edgelength << FRACBITS, + 0 + ); + V_DrawFixedPatch( x*FRACUNIT, y*FRACUNIT, siz, @@ -4555,19 +4601,11 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili colormap ); + V_ClearClipRect(); + drawborder: if (!hili) { - if (ref != NULL) - { - work = 16 * (ref->majorunlock ? 2 : 1); - // Horizontal - V_DrawFill(x, y , work, 1, challengesbordercolor); - V_DrawFill(x, y + work-1, work, 1, challengesbordercolor); - // Vertical - V_DrawFill(x , y+1, 1, work-2, challengesbordercolor); - V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor); - } return; } @@ -4587,23 +4625,40 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) if (challengesmenu.currentunlock >= MAXUNLOCKABLES) { - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); return; } // Okay, this is what we want to draw. ref = &unlockables[challengesmenu.currentunlock]; + // Funny question mark? if (!gamedata->unlocked[challengesmenu.currentunlock]) { - // todo draw some sort of question mark? - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); + spritedef_t *sprdef = &sprites[SPR_UQMK]; + spriteframe_t *sprframe; + patch_t *patch; + UINT32 useframe; + UINT32 addflags = 0; + + if (!sprdef->numframes) + { + return; + } + + useframe = (challengesmenu.ticker / 2) % sprdef->numframes; + + sprframe = &sprdef->spriteframes[useframe]; + patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE); + + if (sprframe->flip & 1) // Only for first sprite + { + addflags ^= V_FLIP; // This sprite is left/right flipped! + } + + V_DrawFixedPatch(x*FRACUNIT, (y+6)*FRACUNIT, FRACUNIT, addflags, patch, NULL); return; } - if (ref->type != SECRET_CUP) - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); - switch (ref->type) { case SECRET_SKIN: @@ -4692,6 +4747,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = btcmapcache; break; } + case SECRET_SPECIALATTACK: + { + static UINT16 sscmapcache = NEXTMAP_INVALID; + if (sscmapcache > nummapheaders) + { + sscmapcache = G_RandMap(G_TOLFlag(GT_SPECIAL), -1, 2, 0, false, NULL); + } + specialmap = sscmapcache; + break; + } case SECRET_HARDSPEED: { static UINT16 hardmapcache = NEXTMAP_INVALID; @@ -4761,7 +4826,7 @@ void M_DrawChallenges(void) INT16 offset; { - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + patch_t *bg = W_CachePatchName("BGUNLCK2", PU_CACHE); V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } @@ -4796,15 +4861,10 @@ void M_DrawChallenges(void) i = gamedata->challengegridwidth-1; explodex = x - (i*16)/2; x += (i*16)/2; - - V_DrawFill(0, currentMenu->y, explodex, (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); - V_DrawFill((x+16), currentMenu->y, BASEVIDWIDTH - (x+16), (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); } selectx = explodex + (challengesmenu.hilix*16); - V_DrawFill(0, (currentMenu->y)-1 , BASEVIDWIDTH, 1, challengesbordercolor); - V_DrawFill(0, (currentMenu->y) + (CHALLENGEGRIDHEIGHT*16), BASEVIDWIDTH, 1, challengesbordercolor); while (i >= 0 && x >= -32) { y = currentMenu->y-16; @@ -4859,7 +4919,6 @@ challengedesc: // Name bar { y = 120; - V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); if (challengesmenu.currentunlock < MAXUNLOCKABLES) { @@ -4889,6 +4948,7 @@ challengedesc: i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; if (challengesmenu.unlockcondition != NULL + && challengesmenu.currentunlock < MAXUNLOCKABLES && ((gamedata->unlocked[challengesmenu.currentunlock] == true) || ((challengesmenu.extradata != NULL) && (challengesmenu.extradata[i] & CHE_HINT)) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 71a447a9e..321dfcb31 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -147,10 +147,6 @@ consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_On static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}}; static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL); -// This gametype list is integral for many different reasons. -// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! -CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; - static CV_PossibleValue_t serversort_cons_t[] = { {0,"Ping"}, {1,"AVG. Power Level"}, @@ -189,18 +185,18 @@ static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2 static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}}; static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}}; static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t dummygametype_cons_t[] = {{0, "Race"}, {1, "Battle"}, {0, NULL}}; //static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDDEN, dummyteam_cons_t, NULL); //static cv_dummyspectate = CVAR_INITconsvar_t ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDDEN, dummyscramble_cons_t, NULL); static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); -consvar_t cv_dummygametype = CVAR_INIT ("dummygametype", "Race", CV_HIDDEN, dummygametype_cons_t, NULL); consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); +INT16 menugametype = GT_RACE; + 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); @@ -1009,6 +1005,8 @@ void M_ClearMenus(boolean callexitmenufunc) if (!menuactive) return; + CON_ClearHUD(); + if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine()) return; // we can't quit this menu (also used to set parameter from the menu) @@ -1221,18 +1219,33 @@ static boolean M_MenuConfirmPressed(UINT8 pid) return M_MenuButtonPressed(pid, MBT_A); } +/*static boolean M_MenuConfirmHeld(UINT8 pid) +{ + return M_MenuButtonHeld(pid, MBT_A); +}*/ + // Returns true if we press the Cancel button static boolean M_MenuBackPressed(UINT8 pid) { return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X)); } +/*static boolean M_MenuBackHeld(UINT8 pid) +{ + return (M_MenuButtonHeld(pid, MBT_B) || M_MenuButtonHeld(pid, MBT_X)); +}*/ + // Retrurns true if we press the tertiary option button (C) static boolean M_MenuExtraPressed(UINT8 pid) { return M_MenuButtonPressed(pid, MBT_C); } +static boolean M_MenuExtraHeld(UINT8 pid) +{ + return M_MenuButtonHeld(pid, MBT_C); +} + // Updates the x coordinate of the keybord so prevent it from going in weird places static void M_UpdateKeyboardX(void) @@ -1713,7 +1726,6 @@ void M_Init(void) CV_RegisterVar(&cv_dummyspectate); CV_RegisterVar(&cv_dummyscramble); CV_RegisterVar(&cv_dummystaff); - CV_RegisterVar(&cv_dummygametype); CV_RegisterVar(&cv_dummyip); CV_RegisterVar(&cv_dummyprofilename); @@ -3295,25 +3307,40 @@ void M_SetupGametypeMenu(INT32 choice) PLAY_GamemodesDef.prevMenu = currentMenu; - // Battle and Capsules disabled + // 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 if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) - { - // Re-add Capsules - PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; - } else { - // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(-1); - return; + 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); @@ -3406,9 +3433,6 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - if (levelsearch->checklocked && M_MapLocked(mapnum+1)) - return false; // not unlocked - // Check for TOL if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; @@ -3427,6 +3451,19 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) && mapheaderinfo[mapnum]->cup != levelsearch->cup) return false; + // Finally, the most complex check: does the map have lock conditions? + if (levelsearch->checklocked) + { + // Check for completion + if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(mapnum+1)) + return false; + } + // Survived our checks. return true; } @@ -3440,6 +3477,9 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + return 0; + for (i = 0; i < CUPCACHE_MAX; i++) { if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) @@ -3466,6 +3506,12 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + { + *i = CUPCACHE_MAX; + return NEXTMAP_INVALID; + } + *i = 0; mapnum = NEXTMAP_INVALID; for (; *i < CUPCACHE_MAX; (*i)++) @@ -3530,20 +3576,39 @@ static void M_LevelSelectScrollDest(void) } // Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. -static void M_LevelListFromGametype(INT16 gt) +static boolean M_LevelListFromGametype(INT16 gt) { static boolean first = true; - if (first || gt != levellist.newgametype) + UINT8 temp = 0; + + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { + if (first) + { + cupgrid.cappages = 0; + cupgrid.builtgrid = NULL; + } + levellist.newgametype = gt; + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); - levellist.levelsearch.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); + if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) + { + // Sneak in an extra. + levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); + levellist.guessgt = gt; + } + else + { + levellist.guessgt = MAXGAMETYPES; + } + + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); levellist.levelsearch.cup = NULL; + first = false; } - PLAY_CupSelectDef.prevMenu = currentMenu; - // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. @@ -3551,32 +3616,35 @@ static void M_LevelListFromGametype(INT16 gt) { levelsearch_t templevelsearch = levellist.levelsearch; // full copy size_t currentid = 0, highestunlockedid = 0; - const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + boolean foundany = false; templevelsearch.cup = kartcupheaders; - templevelsearch.checklocked = false; - // Make sure there's valid cups before going to this menu. +#if 0 + // Make sure there's valid cups before going to this menu. -- rip sweet prince if (templevelsearch.cup == NULL) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); +#endif - if (!cupgrid.builtgrid) + if (cupgrid.cappages == 0) { cupgrid.cappages = 2; cupgrid.builtgrid = Z_Calloc( - cupgrid.cappages * unitlen, + cupgrid.cappages * pagelen, PU_STATIC, - cupgrid.builtgrid); + NULL); if (!cupgrid.builtgrid) { I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); } } - memset(cupgrid.builtgrid, 0, cupgrid.cappages * unitlen); + memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); while (templevelsearch.cup) { + templevelsearch.checklocked = false; if (!M_CountLevelsToShowInList(&templevelsearch)) { // No valid maps, skip. @@ -3584,10 +3652,12 @@ static void M_LevelListFromGametype(INT16 gt) continue; } - if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * unitlen) + foundany = true; + + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) { // Double the size of the buffer, and clear the other stuff. - const size_t firstlen = cupgrid.cappages * unitlen; + const size_t firstlen = cupgrid.cappages * pagelen; cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, firstlen * 2, PU_STATIC, NULL); @@ -3603,7 +3673,8 @@ static void M_LevelListFromGametype(INT16 gt) cupgrid.builtgrid[currentid] = templevelsearch.cup; - if (!M_CupLocked(templevelsearch.cup)) + templevelsearch.checklocked = true; + if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) { highestunlockedid = currentid; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) @@ -3618,16 +3689,29 @@ static void M_LevelListFromGametype(INT16 gt) templevelsearch.cup = templevelsearch.cup->next; } + if (foundany == false) + { + return false; + } + cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; if (cupgrid.pageno >= cupgrid.numpages) { cupgrid.pageno = 0; } + PLAY_CupSelectDef.prevMenu = currentMenu; PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; M_SetupNextMenu(&PLAY_CupSelectDef, false); - return; + return true; + } + + // Okay, just a list of maps then. + + if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) + { + return false; } // Reset position properly if you go back & forth between gametypes @@ -3643,6 +3727,7 @@ static void M_LevelListFromGametype(INT16 gt) PLAY_LevelSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_LevelSelectDef, false); + return true; } // Init level select for use in local play using the last choice we made. @@ -3651,10 +3736,11 @@ static void M_LevelListFromGametype(INT16 gt) void M_LevelSelectInit(INT32 choice) { + INT32 gt = currentMenu->menuitems[itemOn].mvar2; + (void)choice; // Make sure this is reset as we'll only be using this function for offline games! - cupgrid.netgame = false; levellist.netgame = false; levellist.levelsearch.checklocked = true; @@ -3677,7 +3763,111 @@ void M_LevelSelectInit(INT32 choice) return; } - M_LevelListFromGametype(currentMenu->menuitems[itemOn].mvar2); + if (gt == -1) + { + gt = menugametype; + } + + if (!M_LevelListFromGametype(gt)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[gt]->name), NULL, MM_NOTHING); + } +} + +static void M_LevelSelected(INT16 add) +{ + UINT8 i = 0; + INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); + + while (add > 0) + { + map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); + + if (map >= nummapheaders) + { + break; + } + + add--; + } + + if (map >= nummapheaders) + { + // This shouldn't happen + return; + } + + levellist.choosemap = map; + + if (levellist.levelsearch.timeattack) + { + S_StartSound(NULL, sfx_s3k63); + + if (levellist.guessgt != MAXGAMETYPES) + levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); + + PLAY_TimeAttackDef.lastOn = ta_start; + PLAY_TimeAttackDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_TimeAttackDef, false); + } + else + { + if (gamestate == GS_MENU) + { + UINT8 ssplayers = cv_splitplayers.value-1; + + netgame = false; + multiplayer = true; + + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + + // Still need to reset devmode + cht_debug = 0; + + if (demo.playback) + G_StopDemo(); + if (metalrecording) + G_StopMetalDemo(); + + /*if (levellist.choosemap == 0) + levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + 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, levellist.netgame); + + CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); + CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); + CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); + + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + else + { + // directly do the map change + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + + M_ClearMenus(true); + } } void M_CupSelectHandler(INT32 choice) @@ -3733,13 +3923,18 @@ void M_CupSelectHandler(INT32 choice) 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) - || (M_CupLocked(newcup)) - || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) + || (count <= 0) + || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); return; @@ -3803,13 +3998,17 @@ void M_CupSelectHandler(INT32 choice) 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 (levellist.levelsearch.cup != newcup) + if (oldcup != newcup || levellist.cursor >= count) { levellist.cursor = 0; - levellist.levelsearch.cup = newcup; } M_LevelSelectScrollDest(); @@ -3868,94 +4067,10 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { - UINT8 i = 0; - INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); - INT16 add = levellist.cursor; - M_SetMenuDelay(pid); - while (add > 0) - { - map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); - - if (map >= nummapheaders) - { - break; - } - - add--; - } - - if (map >= nummapheaders) - { - // This shouldn't happen - return; - } - - levellist.choosemap = map; - - if (levellist.levelsearch.timeattack) - { - M_SetupNextMenu(&PLAY_TimeAttackDef, false); - S_StartSound(NULL, sfx_s3k63); - } - else - { - if (gamestate == GS_MENU) - { - UINT8 ssplayers = cv_splitplayers.value-1; - - netgame = false; - multiplayer = true; - - strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); - - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - /*if (levellist.choosemap == 0) - levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - 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, levellist.netgame); - - CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); - CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); - CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); - - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - else - { - // directly do the map change - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - - M_ClearMenus(true); - } + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; + M_LevelSelected(levellist.cursor); } else if (M_MenuBackPressed(pid)) { @@ -4006,14 +4121,12 @@ void M_StartTimeAttack(INT32 choice) (void)choice; - switch (levellist.newgametype) + modeattacking = ATTACKING_TIME; + + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[levellist.choosemap]->numlaps != 1)) { - case GT_BATTLE: - modeattacking = ATTACKING_CAPSULES; - break; - default: - modeattacking = ATTACKING_TIME; - break; + modeattacking |= ATTACKING_LAP; } // Still need to reset devmode @@ -4078,6 +4191,7 @@ 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; @@ -4088,6 +4202,10 @@ void M_MPOptSelectInit(INT32 choice) 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); } @@ -4119,41 +4237,93 @@ void M_MPHostInit(INT32 choice) itemOn = mhost_go; } +void M_NextMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + menugametype++; + if (menugametype >= numgametypes) + menugametype = 0; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +void M_PrevMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + if (menugametype == 0) + menugametype = numgametypes; + menugametype--; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +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) { - - INT16 gt = GT_RACE; (void)choice; // Yep, we'll be starting a netgame. levellist.netgame = true; - cupgrid.netgame = true; // Make sure we reset those levellist.levelsearch.timeattack = false; levellist.levelsearch.checklocked = true; cupgrid.grandprix = false; - // In case we ever want to add new gamemodes there somehow, have at it! - switch (cv_dummygametype.value) - { - case 1: // Battle - { - gt = GT_BATTLE; - break; - } - - default: - { - gt = GT_RACE; - break; - } - } - // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. mpmenu.modewinextend[0][0] = 1; - M_LevelListFromGametype(gt); // Setup the level select. - // (This will also automatically send us to the apropriate menu) + 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); + } } // MULTIPLAYER JOIN BY IP @@ -6063,6 +6233,7 @@ void M_OpenPauseMenu(void) // By default, disable anything sensitive: PAUSE_Main[mpause_addons].status = IT_DISABLED; + PAUSE_Main[mpause_changegametype].status = IT_DISABLED; PAUSE_Main[mpause_switchmap].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED; PAUSE_Main[mpause_tryagain].status = IT_DISABLED; @@ -6084,14 +6255,10 @@ void M_OpenPauseMenu(void) if (server || IsPlayerAdmin(consoleplayer)) { - PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_SUBMENU; - for (i = 0; i < PAUSE_GamemodesDef.numitems; i++) - { - if (PAUSE_GamemodesMenu[i].mvar2 != gametype) - continue; - PAUSE_GamemodesDef.lastOn = i; - break; - } + PAUSE_Main[mpause_changegametype].status = IT_STRING | IT_KEYHANDLER; + menugametype = gametype; + + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; } @@ -6192,6 +6359,46 @@ boolean M_PauseInputs(INT32 ch) return false; } +// Change gametype +void M_HandlePauseMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuConfirmPressed(pid)) + { + if (menugametype != gametype) + { + M_ClearMenus(true); + COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + return; + } + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (M_MenuExtraPressed(pid)) + { + menugametype = gametype; + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (menucmd[pid].dpad_lr > 0) + { + 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); + } +} + // Restart map void M_RestartMap(INT32 choice) { @@ -6269,7 +6476,7 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - M_StartMessage(M_GetText("Are you sure you want to return\nto the title screen?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you want to\nreturn to the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); } @@ -6429,7 +6636,9 @@ void M_PrepReplayList(void) else { extrasmenu.demolist[i].type = MD_NOTLOADED; - snprintf(extrasmenu.demolist[i].filepath, 255, "%s%s", menupath, dirmenu[i] + DIR_STRING); + 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, "....."); } } @@ -6650,7 +6859,7 @@ void M_Addons(INT32 choice) #if 1 if (cv_addons_option.value == 0) - pathname = usehome ? srb2home : srb2path; + pathname = addonsdir; else if (cv_addons_option.value == 1) pathname = srb2home; else if (cv_addons_option.value == 2) @@ -7177,6 +7386,7 @@ void M_Challenges(INT32 choice) void M_ChallengesTick(void) { + const UINT8 pid = 0; UINT8 i, newunlock = MAXUNLOCKABLES; boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); @@ -7220,8 +7430,9 @@ void M_ChallengesTick(void) else { // Unlock sequence. + tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; - if (++challengesmenu.unlockanim >= MAXUNLOCKTIME) + if (++challengesmenu.unlockanim >= nexttime) { challengesmenu.requestnew = true; } @@ -7546,9 +7757,16 @@ void M_Statistics(INT32 choice) 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; diff --git a/src/k_objects.h b/src/k_objects.h index dbe3b6ffc..b1fe2014b 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -60,7 +60,7 @@ void Obj_DuelBombInit(mobj_t *bomb); /* Broly Ki */ mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); -void Obj_BrolyKiThink(mobj_t *ki); +boolean Obj_BrolyKiThink(mobj_t *ki); /* Special Stage UFO */ waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo); @@ -73,6 +73,22 @@ void Obj_UFOPieceRemoved(mobj_t *piece); mobj_t *Obj_CreateSpecialUFO(void); UINT32 K_GetSpecialUFODistance(void); +/* Monitors */ +mobj_t *Obj_SpawnMonitor(mobj_t *origin, UINT8 numItemTypes, UINT8 emerald); +void Obj_MonitorSpawnParts(mobj_t *monitor); +void Obj_MonitorPartThink(mobj_t *part); +fixed_t Obj_MonitorGetDamage(mobj_t *monitor, mobj_t *inflictor, UINT8 damagetype); +void Obj_MonitorOnDamage(mobj_t *monitor, mobj_t *inflictor, INT32 damage); +void Obj_MonitorOnDeath(mobj_t *monitor); +void Obj_MonitorShardThink(mobj_t *shard); +UINT32 Obj_MonitorGetEmerald(const mobj_t *monitor); +void Obj_MonitorSetItemSpot(mobj_t *monitor, mobj_t *spot); + +/* Item Spot */ +boolean Obj_ItemSpotIsAvailable(const mobj_t *spot); +void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); +void Obj_ItemSpotUpdate(mobj_t *spot); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_profiles.c b/src/k_profiles.c index 1163b1f8d..5a9ddd75a 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -217,16 +217,13 @@ void PR_SaveProfiles(void) size_t length = 0; const size_t headerlen = strlen(PROFILEHEADER); UINT8 i, j, k; - savebuffer_t save; + savebuffer_t save = {0}; - save.size = sizeof(UINT32) + (numprofiles * sizeof(profile_t)); - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, sizeof(UINT32) + (numprofiles * sizeof(profile_t))) == false) { I_Error("No more free memory for saving profiles\n"); return; } - save.end = save.buffer + save.size; // Add header. WRITESTRINGN(save.p, PROFILEHEADER, headerlen); @@ -270,15 +267,14 @@ void PR_SaveProfiles(void) if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length)) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); } - free(save.buffer); + P_SaveBufferFree(&save); } void PR_LoadProfiles(void) { - size_t length = 0; const size_t headerlen = strlen(PROFILEHEADER); UINT8 i, j, k, version; profile_t *dprofile = PR_MakeProfile( @@ -289,26 +285,22 @@ void PR_LoadProfiles(void) gamecontroldefault, true ); - savebuffer_t save; + savebuffer_t save = {0}; - length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, va(pandf, srb2home, PROFILESFILE)) == false) { // No profiles. Add the default one. PR_AddProfile(dprofile); return; } - save.p = save.buffer; - if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen)) { const char *gdfolder = "the Ring Racers folder"; if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder); } save.p += headerlen; @@ -316,8 +308,7 @@ void PR_LoadProfiles(void) version = READUINT8(save.p); if (version > PROFILEVER) { - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); } diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 6ad088cae..db46a9266 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -16,7 +16,6 @@ #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime #include "k_grandprix.h" -#include "k_boss.h" #include "k_profiles.h" // Client-sided calculations done for Power Levels. @@ -38,7 +37,7 @@ SINT8 K_UsingPowerLevels(void) { SINT8 pt = PWRLV_DISABLED; - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true || bossinfo.boss == true) + if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true) { return PWRLV_DISABLED; } diff --git a/src/k_race.c b/src/k_race.c index 2e7f477e9..0bbe0bfe0 100644 --- a/src/k_race.c +++ b/src/k_race.c @@ -400,7 +400,7 @@ static void K_DrawFinishLineBeamForLine(fixed_t offset, angle_t aiming, line_t * void K_RunFinishLineBeam(void) { - if (!(leveltime < starttime || rainbowstartavailable == true)) + if ((gametyperules & GTR_ROLLINGSTART) || !(leveltime < starttime || rainbowstartavailable == true)) { return; } diff --git a/src/k_respawn.c b/src/k_respawn.c index 0b4c02ccf..5e3a3ecc6 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -175,14 +175,14 @@ void K_DoIngameRespawn(player_t *player) mapthing_t *beststart = NULL; UINT8 numstarts = 0; - if (gametype == GT_RACE) - { - numstarts = numcoopstarts; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { numstarts = numdmstarts; } + else + { + numstarts = numcoopstarts; + } if (numstarts > 0) { @@ -193,17 +193,13 @@ void K_DoIngameRespawn(player_t *player) UINT32 dist = UINT32_MAX; mapthing_t *checkstart = NULL; - if (gametype == GT_RACE) - { - checkstart = playerstarts[i]; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { checkstart = deathmatchstarts[i]; } else { - break; + checkstart = playerstarts[i]; } dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x, diff --git a/src/k_roulette.c b/src/k_roulette.c index 4f3de015f..88cf2a3bb 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -176,6 +176,12 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 } // Gachabom x3 }; +static kartitems_t K_KartItemReelSpecialEnd[] = +{ + KITEM_SUPERRING, + KITEM_NONE +}; + static kartitems_t K_KartItemReelTimeAttack[] = { KITEM_SNEAKER, @@ -359,7 +365,7 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return 0; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 ufoDis = K_GetSpecialUFODistance(); @@ -411,6 +417,171 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return pdis; } +/*-------------------------------------------------- + static boolean K_DenyShieldOdds(kartitems_t item) + + Checks if this type of shield already exists in + another player's inventory. + + Input Arguments:- + item - The item type of the shield. + + Return:- + Whether this item is a shield and may not be awarded + at this time. +--------------------------------------------------*/ +static boolean K_DenyShieldOdds(kartitems_t item) +{ + INT32 shieldType = K_GetShieldFromItem(item); + + if ((gametyperules & GTR_CIRCUIT) == 0) + { + return false; + } + + switch (shieldType) + { + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + { + break; + } + + default: + { + size_t i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (shieldType == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return true; + } + } + } + } + + return false; +} + +/*-------------------------------------------------- + static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) + + Adjust odds of SPB according to distances of first and + second place players. + + Input Arguments:- + roulette - The roulette data that we intend to + insert this item into. + position - Position of player to consider for these + odds. + + Return:- + New item odds. +--------------------------------------------------*/ +static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) +{ + I_Assert(roulette != NULL); + + if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done + || position == 1) // No SPB for 1st ever + { + return 0; + } + else + { + const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); + const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const fixed_t maxOdds = 20 << FRACBITS; + fixed_t multiplier = FixedDiv(dist, distRange); + + if (multiplier < 0) + { + multiplier = 0; + } + + if (multiplier > FRACUNIT) + { + multiplier = FRACUNIT; + } + + return FixedMul(maxOdds, multiplier); + } +} + +typedef struct { + boolean powerItem; + boolean cooldownOnStart; + boolean notNearEnd; + + // gameplay state + boolean rival; // player is a bot Rival +} itemconditions_t; + +/*-------------------------------------------------- + static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) + + Adjust item odds to certain group conditions. + + Input Arguments:- + newOdds - The item odds to adjust. + conditions - The conditions state. + roulette - The roulette data that we intend to + insert this item into. + + Return:- + New item odds. +--------------------------------------------------*/ +static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) +{ + // None if this applies outside of Race modes (for now?) + if ((gametyperules & GTR_CIRCUIT) == 0) + { + return newOdds; + } + + if ((conditions->cooldownOnStart == true) && (leveltime < (30*TICRATE) + starttime)) + { + // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + newOdds = 0; + } + else if ((conditions->notNearEnd == true) && (roulette != NULL && roulette->baseDist < ENDDIST)) + { + // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + newOdds = 0; + } + else if (conditions->powerItem == true) + { + // This item is a "power item". This activates "frantic item" toggle related functionality. + if (franticitems == true) + { + // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. + newOdds *= 2; + } + + if (conditions->rival == true) + { + // The Rival bot gets frantic-like items, also :p + newOdds *= 2; + } + + if (roulette != NULL) + { + newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); + } + } + + return newOdds; +} + /*-------------------------------------------------- INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) @@ -419,19 +590,16 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { boolean bot = false; - boolean rival = false; UINT8 position = 0; - INT32 shieldType = KSHIELD_NONE; - - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean notNearEnd = false; + itemconditions_t conditions = { + .powerItem = false, + .cooldownOnStart = false, + .notNearEnd = false, + .rival = false, + }; fixed_t newOdds = 0; - size_t i; - - I_Assert(roulette != NULL); I_Assert(item > KITEM_NONE); // too many off by one scenarioes. I_Assert(item < NUMKARTRESULTS); @@ -439,7 +607,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, if (player != NULL) { bot = player->bot; - rival = (bot == true && player->botvars.rival == true); + conditions.rival = (bot == true && player->botvars.rival == true); position = player->position; } @@ -472,33 +640,9 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, */ (void)bot; - shieldType = K_GetShieldFromItem(item); - switch (shieldType) + if (K_DenyShieldOdds(item)) { - case KSHIELD_NONE: - /* Marble Garden Top is not REALLY - a Sonic 3 shield */ - case KSHIELD_TOP: - { - break; - } - - default: - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - if (shieldType == K_GetShieldFromItem(players[i].itemtype)) - { - // Don't allow more than one of each shield type at a time - return 0; - } - } - } + return 0; } if (gametype == GT_BATTLE) @@ -506,7 +650,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table newOdds = K_KartItemOddsBattle[item-1][pos]; } - else if (specialStage.active == true) + else if (specialstageinfo.valid == true) { I_Assert(pos < 4); // Ditto newOdds = K_KartItemOddsSpecial[item-1][pos]; @@ -525,7 +669,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_EGGMAN: case KITEM_SUPERRING: { - notNearEnd = true; + conditions.notNearEnd = true; break; } @@ -539,15 +683,15 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KRITEM_QUADORBINAUT: case KRITEM_DUALJAWZ: { - powerItem = true; + conditions.powerItem = true; break; } case KITEM_HYUDORO: case KRITEM_TRIPLEBANANA: { - powerItem = true; - notNearEnd = true; + conditions.powerItem = true; + conditions.notNearEnd = true; break; } @@ -557,59 +701,34 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: { - cooldownOnStart = true; - powerItem = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; break; } case KITEM_SPB: { - cooldownOnStart = true; - notNearEnd = true; + conditions.cooldownOnStart = true; + conditions.notNearEnd = true; - if ((gametyperules & GTR_CIRCUIT) == 0) + if (roulette != NULL && + (gametyperules & GTR_CIRCUIT) && + specialstageinfo.valid == false) { - // Needs to be a race. - return 0; - } - - if (specialStage.active == false) - { - if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done - || position == 1) // No SPB for 1st ever - { - return 0; - } - else - { - const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); - const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const fixed_t maxOdds = 20 << FRACBITS; - fixed_t multiplier = FixedDiv(dist, distRange); - - if (multiplier < 0) - { - multiplier = 0; - } - - if (multiplier > FRACUNIT) - { - multiplier = FRACUNIT; - } - - newOdds = FixedMul(maxOdds, multiplier); - } + newOdds = K_AdjustSPBOdds(roulette, position); } break; } case KITEM_SHRINK: { - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; + conditions.notNearEnd = true; - if (roulette->playing - 1 <= roulette->exiting) + if (roulette != NULL && + (gametyperules & GTR_CIRCUIT) && + roulette->playing - 1 <= roulette->exiting) { return 0; } @@ -618,10 +737,10 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_LIGHTNINGSHIELD: { - cooldownOnStart = true; - powerItem = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; - if (spbplace != -1) + if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) { return 0; } @@ -640,35 +759,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return newOdds; } - if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime)) - { - // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - newOdds = 0; - } - else if ((notNearEnd == true) && (roulette->baseDist < ENDDIST)) - { - // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - newOdds = 0; - } - else if (powerItem == true) - { - // This item is a "power item". This activates "frantic item" toggle related functionality. - if (franticitems == true) - { - // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. - newOdds *= 2; - } - - if (rival == true) - { - // The Rival bot gets frantic-like items, also :p - newOdds *= 2; - } - - newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); - } - - newOdds = FixedInt(FixedRound(newOdds)); + newOdds = FixedInt(FixedRound(K_AdjustItemOddsToConditions(newOdds, &conditions, roulette))); return newOdds; } @@ -705,7 +796,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett oddsValid[i] = false; continue; } - else if (specialStage.active == true && i > 3) + else if (specialstageinfo.valid == true && i > 3) { oddsValid[i] = false; continue; @@ -734,7 +825,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett } else { - if (specialStage.active == true) // Special Stages + if (specialstageinfo.valid == true) // Special Stages { SETUPDISTTABLE(0,2); SETUPDISTTABLE(1,2); @@ -808,7 +899,7 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett return false; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { return false; } @@ -904,7 +995,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->exiting++; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 dis = K_UndoMapScaling(players[i].distancetofinish); if (dis < roulette->secondDist) @@ -926,7 +1017,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) } } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance()); } @@ -1113,8 +1204,19 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } // SPECIAL CASE No. 2: - // Use a special, pre-determined item reel for Time Attack / Free Play - if (bossinfo.boss == true) + // Use a special, pre-determined item reel for Time Attack / Free Play / End of Sealed Stars + if (specialstageinfo.valid) + { + if (K_GetPossibleSpecialTarget() == NULL) + { + for (i = 0; K_KartItemReelSpecialEnd[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]); + } + return; + } + } + else if (gametyperules & GTR_BOSS) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) { @@ -1125,25 +1227,17 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else if (K_TimeAttackRules() == true) { - switch (gametype) + kartitems_t *presetlist = K_KartItemReelTimeAttack; + + // If the objective is not to go fast, it's to cause serious damage. + if (gametyperules & GTR_CAPSULES) { - case GT_RACE: - default: - { - for (i = 0; K_KartItemReelTimeAttack[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelTimeAttack[i]); - } - break; - } - case GT_BATTLE: - { - for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); - } - break; - } + presetlist = K_KartItemReelBreakTheCapsules; + } + + for (i = 0; presetlist[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, presetlist[i]); } return; diff --git a/src/k_specialstage.c b/src/k_specialstage.c index e0551bef1..b0defccb9 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -22,7 +22,7 @@ #include "k_waypoint.h" #include "k_objects.h" -struct specialStage specialStage; +struct specialstageinfo specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void) @@ -31,7 +31,8 @@ struct specialStage specialStage; --------------------------------------------------*/ void K_ResetSpecialStage(void) { - memset(&specialStage, 0, sizeof(struct specialStage)); + memset(&specialstageinfo, 0, sizeof(struct specialstageinfo)); + specialstageinfo.beamDist = UINT32_MAX; } /*-------------------------------------------------- @@ -41,34 +42,15 @@ void K_ResetSpecialStage(void) --------------------------------------------------*/ void K_InitSpecialStage(void) { - INT32 i; - - specialStage.beamDist = UINT32_MAX; // TODO: make proper value - P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO()); - - for (i = 0; i < MAXPLAYERS; i++) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) != (GTR_CATCHER|GTR_CIRCUIT)) { - player_t *player = NULL; - - if (playeringame[i] == false) - { - continue; - } - - player = &players[i]; - if (player->spectator == true) - { - continue; - } - - if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) - { - continue; - } - - // Rolling start? lol - P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); + return; } + + specialstageinfo.valid = true; + + specialstageinfo.beamDist = UINT32_MAX; // TODO: make proper value + P_SetTarget(&specialstageinfo.ufo, Obj_CreateSpecialUFO()); } /*-------------------------------------------------- @@ -88,15 +70,15 @@ static void K_MoveExitBeam(void) moveDist = (8 * mapobjectscale) / FRACUNIT; - if (specialStage.beamDist <= moveDist) + if (specialstageinfo.beamDist <= moveDist) { - specialStage.beamDist = 0; + specialstageinfo.beamDist = 0; // TODO: Fail Special Stage } else { - specialStage.beamDist -= moveDist; + specialstageinfo.beamDist -= moveDist; } // Find players who are now outside of the level. @@ -118,7 +100,7 @@ static void K_MoveExitBeam(void) continue; } - if (player->distancetofinish > specialStage.beamDist) + if (player->distancetofinish > specialstageinfo.beamDist) { P_DoTimeOver(player); } @@ -132,10 +114,30 @@ static void K_MoveExitBeam(void) --------------------------------------------------*/ void K_TickSpecialStage(void) { - if (specialStage.active == false) + if (specialstageinfo.valid == false) { return; } + if (P_MobjWasRemoved(specialstageinfo.ufo)) + { + P_SetTarget(&specialstageinfo.ufo, NULL); + } + K_MoveExitBeam(); } + +mobj_t *K_GetPossibleSpecialTarget(void) +{ + if (specialstageinfo.valid == false) + return NULL; + + if (specialstageinfo.ufo == NULL + || P_MobjWasRemoved(specialstageinfo.ufo)) + return NULL; + + if (specialstageinfo.ufo->health <= 1) //UFOEmeraldChase(specialstageinfo.ufo) + return NULL; + + return specialstageinfo.ufo; +} diff --git a/src/k_specialstage.h b/src/k_specialstage.h index 34df449fa..6b20ca220 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -20,14 +20,13 @@ extern "C" { #endif -extern struct specialStage +extern struct specialstageinfo { - boolean active; ///< If true, then we are in a special stage - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean valid; ///< If true, then data in this struct is valid UINT32 beamDist; ///< Where the exit beam is. mobj_t *ufo; ///< The Chaos Emerald capsule. -} specialStage; +} specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void); @@ -55,6 +54,18 @@ void K_InitSpecialStage(void); void K_TickSpecialStage(void); +/*-------------------------------------------------- + mobj_t *K_GetPossibleSpecialTarget(void) + + Gets the global special stage target if valid + (for Jawz, tether, etc) + + Return:- + Target or NULL +--------------------------------------------------*/ + +mobj_t *K_GetPossibleSpecialTarget(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_waypoint.c b/src/k_waypoint.c index d53a4bbd3..cf70c1dcd 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -1949,6 +1949,7 @@ static waypoint_t *K_MakeWaypoint(mobj_t *const mobj) madewaypoint = &waypointheap[numwaypoints]; numwaypoints++; + madewaypoint->mobj = NULL; P_SetTarget(&madewaypoint->mobj, mobj); // Don't allow a waypoint that has its next ID set to itself to work diff --git a/src/lua_baselib.c b/src/lua_baselib.c index a5dcdfe60..971834a8b 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2973,15 +2973,16 @@ static int lib_gAddGametype(lua_State *L) const char *k; lua_Integer i; + gametype_t *newgametype = NULL; + const char *gtname = NULL; const char *gtconst = NULL; - INT16 newgtidx = 0; UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; + INT16 j; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one. @@ -2990,8 +2991,10 @@ static int lib_gAddGametype(lua_State *L) return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) - return luaL_error(L, "Ran out of free gametype slots!"); + if (numgametypes == GT_LASTFREESLOT) + { + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); + } #define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("G_AddGametype") " (%s)", e); #define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1))) @@ -3024,19 +3027,15 @@ static int lib_gAddGametype(lua_State *L) if (!lua_isnumber(L, 3)) TYPEERROR("typeoflevel", LUA_TNUMBER) newgttol = (UINT32)lua_tointeger(L, 3); - } else if (i == 5 || (k && fasticmp(k, "rankingtype"))) { - if (!lua_isnumber(L, 3)) - TYPEERROR("rankingtype", LUA_TNUMBER) - newgtrankingstype = (INT16)lua_tointeger(L, 3); - } else if (i == 6 || (k && fasticmp(k, "intermissiontype"))) { + } else if (i == 5 || (k && fasticmp(k, "intermissiontype"))) { if (!lua_isnumber(L, 3)) TYPEERROR("intermissiontype", LUA_TNUMBER) newgtinttype = (int)lua_tointeger(L, 3); - } else if (i == 7 || (k && fasticmp(k, "defaultpointlimit"))) { + } else if (i == 6 || (k && fasticmp(k, "defaultpointlimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaultpointlimit", LUA_TNUMBER) newgtpointlimit = (INT32)lua_tointeger(L, 3); - } else if (i == 8 || (k && fasticmp(k, "defaulttimelimit"))) { + } else if (i == 7 || (k && fasticmp(k, "defaulttimelimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaulttimelimit", LUA_TNUMBER) newgttimelimit = (INT32)lua_tointeger(L, 3); @@ -3047,38 +3046,44 @@ static int lib_gAddGametype(lua_State *L) #undef FIELDERROR #undef TYPEERROR + if (gtname == NULL) + return luaL_error(L, "Custom gametype must have a name"); + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + return luaL_error(L, "Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + + for (j = 0; j < numgametypes; j++) + if (!strcmp(gtname, gametypes[j]->name)) + break; + + if (j < numgametypes) + return luaL_error(L, "Custom gametype \"%s\"'s name is already in use", gtname); + // pop gametype table lua_pop(L, 1); - // Set defaults - if (gtname == NULL) - gtname = Z_StrDup("Unnamed gametype"); - // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = gtname; - - // Write the constant name. if (gtconst == NULL) gtconst = gtname; - G_AddGametypeConstant(newgtidx, gtconst); - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); + newgametype->name = gtname; + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant(gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; + + gametypes[numgametypes++] = newgametype; // done - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + CONS_Printf("Added gametype %s\n", gtname); return 0; } @@ -3286,15 +3291,6 @@ static int lib_gExitLevel(lua_State *L) return 0; } -static int lib_gIsSpecialStage(lua_State *L) -{ - INT32 mapnum = luaL_optinteger(L, 1, gamemap); - //HUDSAFE - INLEVEL - lua_pushboolean(L, G_IsSpecialStage(mapnum)); - return 1; -} - static int lib_gGametypeUsesLives(lua_State *L) { //HUDSAFE @@ -4103,7 +4099,6 @@ static luaL_Reg lib[] = { {"G_DoReborn",lib_gDoReborn}, {"G_SetCustomExitVars",lib_gSetCustomExitVars}, {"G_ExitLevel",lib_gExitLevel}, - {"G_IsSpecialStage",lib_gIsSpecialStage}, {"G_GametypeUsesLives",lib_gGametypeUsesLives}, {"G_GametypeHasTeams",lib_gGametypeHasTeams}, {"G_GametypeHasSpectators",lib_gGametypeHasSpectators}, diff --git a/src/lua_script.c b/src/lua_script.c index 3a6d53c98..be6fed523 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -162,9 +162,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"maptol")) { lua_pushinteger(L, maptol); return 1; - } else if (fastcmp(word,"circuitmap")) { - lua_pushboolean(L, circuitmap); - return 1; } else if (fastcmp(word,"stoppedclock")) { lua_pushboolean(L, stoppedclock); return 1; diff --git a/src/m_cond.c b/src/m_cond.c index 052f23dc0..d93ff8d55 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -97,21 +97,27 @@ void M_PopulateChallengeGrid(void) { // Getting the number of 2-highs you can fit into two adjacent columns. UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); - majorpad = (nummajorunlocks+1)/majorpad; + numempty = nummajorunlocks%majorpad; + majorpad = (nummajorunlocks+(majorpad-1))/majorpad; gamedata->challengegridwidth = majorpad*2; + numempty *= 4; #if (CHALLENGEGRIDHEIGHT % 2) - // One empty per column. - numempty = gamedata->challengegridwidth; + // One extra empty per column. + numempty += gamedata->challengegridwidth; #endif + + //CONS_Printf("%d major unlocks means width of %d, numempty of %d\n", nummajorunlocks, gamedata->challengegridwidth, numempty); } if (numunlocks > numempty) { // Getting the number of extra columns to store normal unlocks - gamedata->challengegridwidth += ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + UINT16 temp = ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + gamedata->challengegridwidth += temp; majorcompact = 1; + //CONS_Printf("%d normal unlocks means %d extra entries, additional width of %d\n", numunlocks, (numunlocks - numempty), temp); } else if (challengegridloops) { @@ -570,7 +576,10 @@ static char *M_BuildConditionTitle(UINT16 map) { char *title, *ref; - if (M_MapLocked(map+1)) + if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) + // the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X" + && !(mapheaderinfo[map]->mapvisited & MV_VISITED)) + || M_MapLocked(map+1)) return Z_StrDup("???"); title = ref = G_BuildMapTitle(map+1); @@ -629,7 +638,7 @@ static const char *M_GetConditionString(condition_t *cn) title = BUILDCONDITIONTITLE(cn->requirement); work = va("%s %s%s", - (cn->type == UC_MAPVISITED) ? "Visit" : "Beat", + (cn->type == UC_MAPVISITED) ? "Visit" : "Finish a round on", title, (cn->type == UC_MAPENCORE) ? " in Encore Mode" : ""); Z_Free(title); diff --git a/src/m_cond.h b/src/m_cond.h index 5ab291c35..735894c71 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -123,7 +123,8 @@ typedef enum // Menu restrictions SECRET_TIMEATTACK, // Permit Time attack - SECRET_BREAKTHECAPSULES, // Permit SP Capsules + SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack + SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) SECRET_SOUNDTEST, // Permit Sound Test SECRET_ALTTITLE, // Permit alternate titlescreen diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 00eadea36..440f700de 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -10,4 +10,6 @@ target_sources(SRB2SDL2 PRIVATE duel-bomb.c broly.c ufo.c + monitor.c + item-spot.c ) diff --git a/src/objects/broly.c b/src/objects/broly.c index d041c23b7..4c283a175 100644 --- a/src/objects/broly.c +++ b/src/objects/broly.c @@ -34,14 +34,16 @@ Obj_SpawnBrolyKi ( mobj_t * source, tic_t duration) { - mobj_t *x = P_SpawnMobjFromMobj( - source, 0, 0, 0, MT_BROLY); + mobj_t *x; - if (duration == 0) + if (duration <= 0) { - return x; + return NULL; } + x = P_SpawnMobjFromMobj( + source, 0, 0, 0, MT_BROLY); + // Shrink into center of source object. x->z = (source->z + source->height / 2); @@ -61,12 +63,20 @@ Obj_SpawnBrolyKi return x; } -void +boolean Obj_BrolyKiThink (mobj_t *x) { + if (broly_duration(x) <= 0) + { + P_RemoveMobj(x); + return false; + } + const fixed_t t = get_unit_linear(x), n = Easing_OutSine(t, 0, broly_maxscale(x)); P_InstaScale(x, n); + + return true; } diff --git a/src/objects/item-spot.c b/src/objects/item-spot.c new file mode 100644 index 000000000..0fdff3056 --- /dev/null +++ b/src/objects/item-spot.c @@ -0,0 +1,35 @@ +#include "../doomdef.h" +#include "../m_fixed.h" +#include "../k_objects.h" +#include "../k_battle.h" +#include "../p_tick.h" +#include "../p_local.h" + +#define spot_monitor(o) ((o)->target) +#define spot_cool(o) ((o)->threshold) + +boolean +Obj_ItemSpotIsAvailable (const mobj_t *spot) +{ + return P_MobjWasRemoved(spot_monitor(spot)) && + (leveltime - spot_cool(spot)) > BATTLE_SPAWN_INTERVAL; +} + +void +Obj_ItemSpotAssignMonitor +( mobj_t * spot, + mobj_t * monitor) +{ + P_SetTarget(&spot_monitor(spot), monitor); + Obj_MonitorSetItemSpot(monitor, spot); +} + +void +Obj_ItemSpotUpdate (mobj_t *spot) +{ + if (P_MobjWasRemoved(spot_monitor(spot)) || + spot_monitor(spot)->health <= 0) + { + spot_cool(spot) = leveltime; + } +} diff --git a/src/objects/monitor.c b/src/objects/monitor.c new file mode 100644 index 000000000..50d13ee3c --- /dev/null +++ b/src/objects/monitor.c @@ -0,0 +1,705 @@ +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_objects.h" +#include "../p_local.h" +#include "../r_state.h" +#include "../k_kart.h" +#include "../k_battle.h" +#include "../m_random.h" +#include "../r_main.h" + +#define FINE90 (FINEANGLES/4) +#define FINE180 (FINEANGLES/2) +#define TRUETAN(n) FINETANGENT(FINE90 + (n)) // bruh + +#define HEALTHFACTOR (FRACUNIT/4) // Always takes at most, 4 hits. + +#define MONITOR_PART_DEFINE(dispoffset, nsides, ...) \ +{dispoffset, nsides, (statenum_t[]){__VA_ARGS__, 0}} + +static const struct monitor_part_config { + INT32 dispoffset; + UINT8 nsides; + statenum_t * states; +} monitor_parts[] = { + MONITOR_PART_DEFINE (0, 3, + S_MONITOR_SCREEN1A, + S_ITEMICON, + S_MONITOR_CRACKB, + S_MONITOR_CRACKA), + + MONITOR_PART_DEFINE (-5, 5, S_MONITOR_STAND), +}; + +#define monitor_spot(o) ((o)->target) +#define monitor_rngseed(o) ((o)->movedir) +#define monitor_itemcount(o) ((o)->movecount) +#define monitor_spawntic(o) ((o)->reactiontime) +#define monitor_emerald(o) ((o)->extravalue1) +#define monitor_damage(o) ((o)->extravalue2) +#define monitor_rammingspeed(o) ((o)->movefactor) + +static inline UINT8 +get_monitor_itemcount (const mobj_t *monitor) +{ + // protects against divide by zero + return max(monitor_itemcount(monitor), 1); +} + +#define part_monitor(o) ((o)->target) +#define part_type(o) ((o)->extravalue1) +#define part_index(o) ((o)->extravalue2) +#define part_theta(o) ((o)->movedir) + +#define shard_can_roll(o) ((o)->extravalue1) + +static const sprcache_t * get_state_sprcache (statenum_t); + +static const sprcache_t * +get_sprcache +( spritenum_t sprite, + UINT8 frame) +{ + const spritedef_t *sprdef = &sprites[sprite]; + + if (frame < sprdef->numframes) + { + size_t lump = sprdef->spriteframes[frame].lumpid[0]; + + return &spritecachedinfo[lump]; + } + else + { + return get_state_sprcache(S_UNKNOWN); + } +} + +static const sprcache_t * +get_state_sprcache (statenum_t statenum) +{ + return get_sprcache(states[statenum].sprite, + states[statenum].frame & FF_FRAMEMASK); +} + +static inline fixed_t +get_inradius +( fixed_t length, + INT32 nsides) +{ + return FixedDiv(length, 2 * TRUETAN(FINE180 / nsides)); +} + +static inline void +center_item_sprite +( mobj_t * part, + fixed_t scale) +{ + part->spriteyoffset = 25*FRACUNIT; + part->spritexscale = scale; + part->spriteyscale = scale; +} + +static mobj_t * +spawn_part +( mobj_t * monitor, + statenum_t state) +{ + mobj_t *part = P_SpawnMobjFromMobj( + monitor, 0, 0, 0, MT_MONITOR_PART); + + P_SetMobjState(part, state); + P_SetTarget(&part_monitor(part), monitor); + + part_type(part) = state; + + switch (state) + { + case S_ITEMICON: + // The first frame of the monitor is TV static so + // this should be invisible on the first frame. + part->renderflags |= RF_DONTDRAW; + break; + + default: + break; + } + + return part; +} + +static void +spawn_part_side +( mobj_t * monitor, + fixed_t rad, + fixed_t ang, + const struct monitor_part_config * p, + size_t side) +{ + INT32 i = 0; + + while (p->states[i]) + { + mobj_t *part = spawn_part(monitor, p->states[i]); + + part->radius = rad; + part_theta(part) = ang; + + // add one point for each layer (back to front order) + part->dispoffset = p->dispoffset + i; + + part_index(part) = side; + + i++; + } +} + +static void +spawn_monitor_parts +( mobj_t * monitor, + const struct monitor_part_config *p) +{ + const sprcache_t *info = get_state_sprcache(p->states[0]); + const fixed_t width = FixedMul(monitor->scale, info->width); + const fixed_t rad = get_inradius(width, p->nsides); + const fixed_t angle_factor = ANGLE_MAX / p->nsides; + + INT32 i; + angle_t ang = 0; + + for (i = 0; i < p->nsides; ++i) + { + spawn_part_side(monitor, rad, ang, p, i); + ang += angle_factor; + } +} + +static inline boolean +can_shard_state_roll (statenum_t state) +{ + switch (state) + { + case S_MONITOR_BIG_SHARD: + case S_MONITOR_SMALL_SHARD: + return true; + + default: + return false; + } +} + +static void +spawn_shard +( mobj_t * part, + statenum_t state) +{ + mobj_t *monitor = part_monitor(part); + + // These divisions and multiplications are done on the + // offsets to give bigger increments of randomness. + + const fixed_t half = FixedDiv( + monitor->height, monitor->scale) / 2; + + const UINT16 rad = (monitor->radius / monitor->scale) / 4; + const UINT16 tall = (half / FRACUNIT) / 4; + + mobj_t *p = P_SpawnMobjFromMobj(monitor, + P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, + P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, + (half / 4) + P_RandomKey(PR_ITEM_DEBRIS, tall + 1) * 4 * FRACUNIT, + MT_MONITOR_SHARD); + + angle_t th = (part->angle + ANGLE_90); + + th -= P_RandomKey(PR_ITEM_DEBRIS, ANGLE_45) - ANGLE_22h; + + p->hitlag = 0; + + P_Thrust(p, th, 6 * p->scale + monitor_rammingspeed(monitor)); + p->momz = P_RandomRange(PR_ITEM_DEBRIS, 3, 10) * p->scale; + + P_SetMobjState(p, state); + + shard_can_roll(p) = can_shard_state_roll(state); + + if (shard_can_roll(p)) + { + p->rollangle = P_Random(PR_ITEM_DEBRIS); + } + + if (P_RandomChance(PR_ITEM_DEBRIS, FRACUNIT/2)) + { + p->renderflags |= RF_DONTDRAW; + } +} + +static void +spawn_debris (mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + + fixed_t i; + + for (i = monitor->health; + i <= FRACUNIT; i += HEALTHFACTOR/2) + { + spawn_shard(part, S_MONITOR_BIG_SHARD); + spawn_shard(part, S_MONITOR_SMALL_SHARD); + spawn_shard(part, S_MONITOR_TWINKLE); + } +} + +static void +spawn_monitor_explosion (mobj_t *monitor) +{ + mobj_t *smoldering = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_SMOLDERING); + + UINT8 i; + + // Note that a Broly Ki is purposefully not spawned. This + // is to reduce visual clutter since these monitors would + // probably get popped a lot. + + K_MineFlashScreen(monitor); + + P_SetScale(smoldering, (smoldering->destscale /= 3)); + smoldering->tics = TICRATE*3; + + for (i = 0; i < 8; ++i) + { + mobj_t *x = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_BOOMEXPLODE); + x->hitlag = 0; + x->color = SKINCOLOR_WHITE; + x->momx = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale, + x->momy = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale, + x->momz = P_RandomRange(PR_EXPLOSION, 0, 6) * monitor->scale * P_MobjFlip(monitor); + P_SetScale(x, (x->destscale *= 3)); + } +} + +static void +kill_monitor_part (mobj_t *part) +{ + const statenum_t statenum = part_type(part); + + switch (statenum) + { + case S_ITEMICON: + P_RemoveMobj(part); + return; + + case S_MONITOR_STAND: + part->momx = 0; + part->momy = 0; + break; + + case S_MONITOR_SCREEN1A: + spawn_debris(part); + P_SetMobjState(part, S_MONITOR_SCREEN1B); + /*FALLTHRU*/ + + default: + /* To be clear, momx/y do not need to set because + those fields are set every tic to offset each + part. */ + part->momz = (part->height / 8) * P_MobjFlip(part); + } + + part->fuse = TICRATE; + part->flags &= ~(MF_NOGRAVITY); +} + +static inline UINT32 +restore_item_rng (UINT32 seed) +{ + const UINT32 oldseed = P_GetRandSeed(PR_ITEM_ROULETTE); + + P_SetRandSeedNet(PR_ITEM_ROULETTE, + P_GetInitSeed(PR_ITEM_ROULETTE), seed); + + return oldseed; +} + +static inline SINT8 +get_item_result (void) +{ + return K_GetTotallyRandomResult(0); +} + +static SINT8 +get_cycle_result +( const mobj_t * monitor, + size_t cycle) +{ + const size_t rem = cycle % + get_monitor_itemcount(monitor); + + SINT8 result; + size_t i; + + const UINT32 oldseed = restore_item_rng( + monitor_rngseed(monitor)); + + for (i = 0; i <= rem; ++i) + { + result = get_item_result(); + } + + restore_item_rng(oldseed); + + return result; +} + +static inline tic_t +get_age (const mobj_t *monitor) +{ + return (leveltime - monitor_spawntic(monitor)); +} + +static inline boolean +is_flickering (const mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + + return monitor->fuse > 0 && monitor->fuse <= TICRATE; +} + +static void +flicker +( mobj_t * part, + UINT8 interval) +{ + const tic_t age = get_age(part_monitor(part)); + + if (age % interval) + { + part->renderflags |= RF_DONTDRAW; + } + else + { + part->renderflags &= ~(RF_DONTDRAW); + } +} + +static void +project_icon (mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + const tic_t age = get_age(monitor); + + // Item displayed on monitor cycles every N tics + if (age % 64 == 0) + { + const SINT8 result = get_cycle_result(monitor, + part_index(part) + (age / 64)); + + K_UpdateMobjItemOverlay(part, + K_ItemResultToType(result), + K_ItemResultToAmount(result)); + + center_item_sprite(part, 5*FRACUNIT/4); + } + + flicker(part, is_flickering(part) ? 4 : 2); +} + +static void +translate (mobj_t *part) +{ + const angle_t ang = part_theta(part) + + part_monitor(part)->angle; + + part->angle = (ang - ANGLE_90); + + // Because of MF_NOCLIPTHING, no friction is applied. + // This object is teleported back to the monitor every + // tic so its position is in total only ever translated + // by this much. + part->momx = P_ReturnThrustX(NULL, ang, part->radius); + part->momy = P_ReturnThrustY(NULL, ang, part->radius); +} + +static inline fixed_t +get_damage_multiplier (const mobj_t *monitor) +{ + return FixedDiv(monitor_damage(monitor), HEALTHFACTOR); +} + +static inline boolean +has_state +( const mobj_t * mobj, + statenum_t state) +{ + return mobj->hitlag == 0 && + (size_t)(mobj->state - states) == (size_t)state; +} + +static mobj_t * +adjust_monitor_drop +( mobj_t * monitor, + mobj_t * drop) +{ + P_InstaThrust(drop, drop->angle, 4*mapobjectscale); + + drop->momz *= 8; + + K_FlipFromObject(drop, monitor); + + return drop; +} + +void +Obj_MonitorSpawnParts (mobj_t *monitor) +{ + const size_t nparts = + sizeof monitor_parts / sizeof *monitor_parts; + + size_t i; + + P_SetScale(monitor, (monitor->destscale *= 2)); + + monitor_itemcount(monitor) = 0; + monitor_rngseed(monitor) = P_GetRandSeed(PR_ITEM_ROULETTE); + monitor_spawntic(monitor) = leveltime; + monitor_emerald(monitor) = 0; + + for (i = 0; i < nparts; ++i) + { + spawn_monitor_parts(monitor, &monitor_parts[i]); + } +} + +mobj_t * +Obj_SpawnMonitor +( mobj_t * origin, + UINT8 numItemTypes, + UINT8 emerald) +{ + mobj_t *monitor = P_SpawnMobj(origin->x, origin->y, + origin->z, MT_MONITOR); + + monitor->angle = P_Random(PR_DECORATION); + + monitor_itemcount(monitor) = numItemTypes; + monitor_emerald(monitor) = emerald; + + monitor->color = K_GetChaosEmeraldColor(emerald); + + return monitor; +} + +void +Obj_MonitorPartThink (mobj_t *part) +{ + const statenum_t statenum = part_type(part); + + mobj_t *monitor = part_monitor(part); + + if (part->fuse > 0) + { + return; + } + + if (P_MobjWasRemoved(monitor)) + { + P_RemoveMobj(part); + return; + } + + if (has_state(monitor, monitor->info->deathstate)) + { + kill_monitor_part(part); + return; + } + + if (is_flickering(part)) + { + flicker(part, 2); + } + + if (monitor->hitlag) + { + const fixed_t shake = FixedMul( + 2 * get_damage_multiplier(monitor), + monitor->radius / 8); + + part->sprxoff = P_AltFlip(shake, 2); + part->spryoff = P_AltFlip(shake, 4); + } + else + { + part->sprxoff = 0; + part->spryoff = 0; + } + + switch (statenum) + { + case S_MONITOR_SCREEN1A: + if (has_state(monitor, monitor->info->painstate)) + { + spawn_debris(part); + } + break; + + case S_MONITOR_CRACKA: + case S_MONITOR_CRACKB: + if (monitor->health < monitor->info->spawnhealth) + { + part->sprite = SPR_IMON; // initially SPR_NULL + part->frame = part->state->frame + + (monitor->health / HEALTHFACTOR); + } + break; + + case S_ITEMICON: + project_icon(part); + break; + + default: + break; + } + + P_MoveOrigin(part, monitor->x, monitor->y, monitor->z); + + translate(part); +} + +fixed_t +Obj_MonitorGetDamage +( mobj_t * monitor, + mobj_t * inflictor, + UINT8 damagetype) +{ + fixed_t damage; + + switch (damagetype & DMG_TYPEMASK) + { + case DMG_VOLTAGE: + if (monitor->health < HEALTHFACTOR) + { + return HEALTHFACTOR; + } + else + { + // always reduce to final damage state + return (monitor->health - HEALTHFACTOR) + 1; + } + } + + if (inflictor == NULL) + { + return HEALTHFACTOR; + } + + if (inflictor->player) + { + const fixed_t weight = + K_GetMobjWeight(inflictor, monitor); + + // HEALTHFACTOR is the minimum damage that can be + // dealt but player's weight (and speed) can buff the hit. + damage = HEALTHFACTOR + + (FixedMul(weight, HEALTHFACTOR) / 9); + + if (inflictor->scale > mapobjectscale) + { + damage = P_ScaleFromMap(damage, inflictor->scale); + } + } + else + { + damage = FRACUNIT; // kill instantly + } + + return damage; +} + +void +Obj_MonitorOnDamage +( mobj_t * monitor, + mobj_t * inflictor, + INT32 damage) +{ + monitor->fuse = BATTLE_DESPAWN_TIME; + monitor_damage(monitor) = damage; + monitor_rammingspeed(monitor) = inflictor + ? FixedDiv(FixedHypot(inflictor->momx, inflictor->momy), 4 * inflictor->radius) : 0; + monitor->hitlag = + 6 * get_damage_multiplier(monitor) / FRACUNIT; +} + +void +Obj_MonitorOnDeath (mobj_t *monitor) +{ + const UINT8 itemcount = get_monitor_itemcount(monitor); + const angle_t ang = ANGLE_MAX / itemcount; + const SINT8 flip = P_MobjFlip(monitor); + + INT32 i; + + UINT32 sharedseed = restore_item_rng( + monitor_rngseed(monitor)); + + for (i = 0; i < itemcount; ++i) + { + const SINT8 result = get_item_result(); + const UINT32 localseed = restore_item_rng(sharedseed); + + adjust_monitor_drop(monitor, + K_CreatePaperItem( + monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip), + i * ang, flip, + K_ItemResultToType(result), + K_ItemResultToAmount(result))); + + // K_CreatePaperItem may advance RNG, so update our + // copy of the seed afterward + sharedseed = restore_item_rng(localseed); + } + + restore_item_rng(sharedseed); + + if (monitor_emerald(monitor) != 0) + { + adjust_monitor_drop(monitor, + K_SpawnChaosEmerald(monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip), + ang, flip, monitor_emerald(monitor))); + } + + spawn_monitor_explosion(monitor); + + // There is hitlag from being damaged, so remove + // tangibility RIGHT NOW. + monitor->flags &= ~(MF_SOLID); + + if (!P_MobjWasRemoved(monitor_spot(monitor))) + { + Obj_ItemSpotUpdate(monitor_spot(monitor)); + } +} + +void +Obj_MonitorShardThink (mobj_t *shard) +{ + if (shard_can_roll(shard)) + { + shard->rollangle += ANGLE_45; + } + + shard->renderflags ^= RF_DONTDRAW; +} + +UINT32 +Obj_MonitorGetEmerald (const mobj_t *monitor) +{ + return monitor_emerald(monitor); +} + +void +Obj_MonitorSetItemSpot +( mobj_t * monitor, + mobj_t * spot) +{ + P_SetTarget(&monitor_spot(monitor), spot); +} diff --git a/src/objects/spb.c b/src/objects/spb.c index b76dfd4f8..af1041049 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -549,7 +549,7 @@ static void SPBSeek(mobj_t *spb, mobj_t *bestMobj) SetSPBSpeed(spb, xySpeed, zSpeed); - if (specialStage.active == false) + if (specialstageinfo.valid == false) { // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! steerDist = 1536 * mapobjectscale; @@ -857,6 +857,27 @@ void Obj_SPBThink(mobj_t *spb) ghost->colorized = true; } + if (spb_nothink(spb) <= 1) + { + if (specialstageinfo.valid == true) + { + bestRank = 0; + + if ((bestMobj = K_GetPossibleSpecialTarget()) == NULL) + { + // experimental - I think it's interesting IMO + Obj_MantaRingCreate( + spb, + spb_owner(spb), + NULL + ); + + spb->fuse = TICRATE/3; + spb_nothink(spb) = spb->fuse + 2; + } + } + } + if (spb_nothink(spb) > 0) { // Init values, don't think yet. @@ -874,15 +895,6 @@ void Obj_SPBThink(mobj_t *spb) } else { - if (specialStage.active == true) - { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) - { - bestRank = 1; - bestMobj = specialStage.ufo; - } - } - // Find the player with the best rank for (i = 0; i < MAXPLAYERS; i++) { diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 5eaf7f645..f9a44127b 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -239,9 +239,9 @@ static void UFOUpdateAngle(mobj_t *ufo) waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo) { - if ((ufo == NULL) && (specialStage.active == true)) + if ((ufo == NULL) && (specialstageinfo.valid == true)) { - ufo = specialStage.ufo; + ufo = specialstageinfo.ufo; } if (ufo != NULL && P_MobjWasRemoved(ufo) == false @@ -281,10 +281,11 @@ static void UFOMove(mobj_t *ufo) if (curWaypoint == NULL || destWaypoint == NULL) { // Waypoints aren't valid. - // Just stand still. + // Just go straight up. + // :japanese_ogre: : "Abrupt and funny is the funniest way to end the special stage anyways" ufo->momx = 0; ufo->momy = 0; - ufo->momz = 0; + ufo->momz = ufo_speed(ufo); return; } @@ -365,8 +366,23 @@ static void UFOMove(mobj_t *ufo) if (reachedEnd == true) { - CONS_Printf("You lost...\n"); - ufo_waypoint(ufo) = -1; // Invalidate + UINT8 i; + + // Invalidate UFO/emerald + ufo_waypoint(ufo) = -1; + ufo->flags &= ~(MF_SPECIAL|MF_PICKUPFROMBELOW); + + // Disable player + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + + players[i].pflags |= PF_NOCONTEST; + P_DoPlayerExit(&players[i]); + } } if (pathfindsuccess == true) @@ -591,6 +607,8 @@ boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UIN ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW); ufo->shadowscale = FRACUNIT/3; + P_LinedefExecute(LE_PINCHPHASE, ufo, NULL); + ufo_speed(ufo) += addSpeed; // Even more speed! return true; } @@ -655,7 +673,10 @@ void Obj_UFOPieceThink(mobj_t *piece) fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT); UFOMoveTo(piece, ufo->x, ufo->y, stemZ); - piece->spriteyscale = sc; + if (sc > 0) + { + piece->spriteyscale = sc; + } break; } default: @@ -820,11 +841,11 @@ mobj_t *Obj_CreateSpecialUFO(void) UINT32 K_GetSpecialUFODistance(void) { - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { - return (UINT32)ufo_distancetofinish(specialStage.ufo); + return (UINT32)ufo_distancetofinish(specialstageinfo.ufo); } } diff --git a/src/p_enemy.c b/src/p_enemy.c index 2e1ed8557..fc67cc882 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13053,7 +13053,7 @@ void A_ItemPop(mobj_t *actor) } // Here at mapload in battle? - if ((gametyperules & GTR_BUMPERS) && (actor->flags2 & MF2_BOSSNOTRAP)) + if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSNOTRAP)) { numgotboxes++; diff --git a/src/p_inter.c b/src/p_inter.c index ff6ba99b0..399d74918 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -32,9 +32,9 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" +#include "k_specialstage.h" #include "k_pwrlv.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_respawn.h" #include "p_spec.h" #include "k_objects.h" @@ -414,6 +414,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; + P_LinedefExecute(LE_BOSSDEAD, toucher, NULL); + CONS_Printf("You win!\n"); break; /* @@ -859,7 +861,7 @@ boolean P_CheckRacers(void) // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) + if (!playeringame[i] || players[i].spectator || (players[i].lives <= 0 && !players[i].exiting)) { // Y'all aren't even playing continue; @@ -893,7 +895,7 @@ boolean P_CheckRacers(void) } } - if (numPlaying <= 1) + if (numPlaying <= 1 || specialstageinfo.valid == true) { // Never do this without enough players. eliminateLast = false; @@ -1097,7 +1099,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { P_SetTarget(&target->target, source); - if (gametyperules & GTR_BUMPERS) + if (!(gametyperules & GTR_CIRCUIT)) { target->fuse = 2; } @@ -1664,6 +1666,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } + case MT_MONITOR: + Obj_MonitorOnDeath(target); + break; + default: break; } @@ -2005,6 +2011,17 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da laglength = 0; // handled elsewhere } + switch (target->type) + { + case MT_MONITOR: + damage = Obj_MonitorGetDamage(target, inflictor, damagetype); + Obj_MonitorOnDamage(target, inflictor, damage); + break; + + default: + break; + } + // Everything above here can't be forced. if (!metalrecording) { @@ -2198,7 +2215,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { tic_t kinvextend; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) kinvextend = 2*TICRATE; else kinvextend = 5*TICRATE; diff --git a/src/p_map.c b/src/p_map.c index 4b81e8c14..ebb9a1786 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1528,6 +1528,18 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) K_KartBouncing(tm.thing, thing); return BMIT_CONTINUE; } + else if (thing->type == MT_MONITOR) + { + // see if it went over / under + if (tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (tm.thing->z + tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + + P_DamageMobj(thing, tm.thing, tm.thing, 1, DMG_NORMAL); + K_KartBouncing(tm.thing, thing); + return BMIT_CONTINUE; + } else if (thing->flags & MF_SOLID) { // see if it went over / under diff --git a/src/p_mobj.c b/src/p_mobj.c index 895c337c2..183f292c4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -39,7 +39,6 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_color.h" #include "k_respawn.h" #include "k_bot.h" @@ -1226,6 +1225,21 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_ITEM_DEBRIS: gravityadd *= 6; break; + case MT_FLOATINGITEM: { + // Basically this accelerates gravity after + // the object reached its peak vertical + // momentum. It's a gradual acceleration up + // to 2x normal gravity. It's not instant to + // give it some 'weight'. + const fixed_t z = P_MobjFlip(mo) * mo->momz; + if (z < 0) + { + const fixed_t d = (z - (mo->height / 2)); + const fixed_t f = 2 * abs(FixedDiv(d, mo->height)); + gravityadd = FixedMul(gravityadd, FRACUNIT + min(f, 2*FRACUNIT)); + } + break; + } default: break; } @@ -3080,6 +3094,17 @@ boolean P_SceneryZMovement(mobj_t *mo) P_RemoveMobj(mo); return false; } + break; + case MT_MONITOR_SHARD: + // Hits the ground + if ((mo->eflags & MFE_VERTICALFLIP) + ? (mo->ceilingz <= (mo->z + mo->height)) + : (mo->z <= mo->floorz)) + { + P_RemoveMobj(mo); + return false; + } + break; default: break; } @@ -4348,25 +4373,7 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) part->threshold = mobj->threshold; part->movecount = mobj->movecount; - switch (itemType) - { - case KITEM_ORBINAUT: - part->sprite = SPR_ITMO; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount); - break; - case KITEM_INVINCIBILITY: - part->sprite = SPR_ITMI; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); - break; - } + K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number @@ -5761,6 +5768,21 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_AddOverlay(mobj); if (mobj->target->hitlag) // move to the correct position, update to the correct properties, but DON'T STATE-ANIMATE return; + switch (mobj->target->type) + { + case MT_FLOATINGITEM: + // Spawn trail for item drop as it flies upward. + // Done here so it applies to backdrop too. + if (mobj->target->momz * P_MobjFlip(mobj->target) > 0) + { + P_SpawnGhostMobj(mobj); + P_SpawnGhostMobj(mobj->target); + } + break; + + default: + break; + } break; case MT_WATERDROP: P_SceneryCheckWater(mobj); @@ -6182,7 +6204,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->color = mobj->target->color; K_MatchGenericExtraFlags(mobj, mobj->target); - if ((gametype == GT_RACE || mobj->target->player->bumpers <= 0) + if ((!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers <= 0) #if 1 // Set to 0 to test without needing to host || (P_IsDisplayPlayer(mobj->target->player)) #endif @@ -6553,7 +6575,13 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->renderflags ^= RF_DONTDRAW; break; case MT_BROLY: - Obj_BrolyKiThink(mobj); + if (Obj_BrolyKiThink(mobj) == false) + { + return; + } + break; + case MT_MONITOR_SHARD: + Obj_MonitorShardThink(mobj); break; case MT_VWREF: case MT_VWREB: @@ -7417,6 +7445,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } case MT_EMERALD: + { + if (mobj->threshold > 0) + mobj->threshold--; + } + /*FALLTHRU*/ + case MT_MONITOR: { if (battleovertime.enabled >= 10*TICRATE) { @@ -7429,6 +7463,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } + // Don't spawn sparkles on a monitor with no + // emerald inside + if (mobj->type == MT_MONITOR && + Obj_MonitorGetEmerald(mobj) == 0) + { + break; + } + if (leveltime % 3 == 0) { mobj_t *sparkle = P_SpawnMobjFromMobj( @@ -7442,9 +7484,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) sparkle->color = mobj->color; sparkle->momz += 8 * mobj->scale * P_MobjFlip(mobj); } - - if (mobj->threshold > 0) - mobj->threshold--; } break; case MT_DRIFTEXPLODE: @@ -8381,7 +8420,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) statenum_t state = (mobj->state-states); if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator - || (gametype == GT_RACE || mobj->target->player->bumpers)) + || (!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers)) { P_RemoveMobj(mobj); return false; @@ -9385,7 +9424,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { if (gametyperules & GTR_PAPERITEMS) { - if (battlecapsules == true || bossinfo.boss == true) + if (battlecapsules == true) { ; } @@ -9408,12 +9447,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } // FALLTHRU case MT_SPHEREBOX: - if (gametype == GT_BATTLE && mobj->threshold == 70) + if (mobj->threshold == 70) { mobj->color = K_RainbowColor(leveltime); mobj->colorized = true; - if (battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { angle_t ang = FixedAngle((leveltime % 360) << FRACBITS); fixed_t z = battleovertime.z; @@ -9449,6 +9488,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->colorized = false; } break; + case MT_MONITOR_PART: + Obj_MonitorPartThink(mobj); + break; default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -9547,13 +9589,40 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s P_RemoveMobj(mobj); // make sure they disappear } +static boolean P_CanFlickerFuse(mobj_t *mobj) +{ + switch (mobj->type) + { + case MT_SNAPPER_HEAD: + case MT_SNAPPER_LEG: + case MT_MINECARTSEG: + case MT_MONITOR_PART: + return true; + + case MT_RANDOMITEM: + case MT_EGGMANITEM: + case MT_FALLINGROCK: + case MT_FLOATINGITEM: + if (mobj->fuse <= TICRATE) + { + return true; + } + break; + + default: + break; + } + + return false; + +} + static boolean P_FuseThink(mobj_t *mobj) { - if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG) - mobj->renderflags ^= RF_DONTDRAW; - - if (mobj->fuse <= TICRATE && (mobj->type == MT_RANDOMITEM || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK)) + if (P_CanFlickerFuse(mobj)) + { mobj->renderflags ^= RF_DONTDRAW; + } mobj->fuse--; @@ -9601,7 +9670,7 @@ static boolean P_FuseThink(mobj_t *mobj) { ; } - else if ((gametyperules & GTR_BUMPERS) && (mobj->state == &states[S_INVISIBLE])) + else if (!(gametyperules & GTR_CIRCUIT) && (mobj->state == &states[S_INVISIBLE])) { break; } @@ -10629,6 +10698,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) break; } + case MT_MONITOR: { + Obj_MonitorSpawnParts(mobj); + break; + } case MT_KARMAHITBOX: { const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale); @@ -11440,7 +11513,7 @@ void P_RespawnBattleBoxes(void) { thinker_t *th; - if (!(gametyperules & GTR_BUMPERS)) + if (gametyperules & GTR_CIRCUIT) return; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -11705,13 +11778,16 @@ void P_SpawnPlayer(INT32 playernum) K_InitStumbleIndicator(p); - if (gametyperules & GTR_BUMPERS) + if (gametyperules & GTR_ITEMARROWS) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); P_SetTarget(&overheadarrow->target, mobj); overheadarrow->renderflags |= RF_DONTDRAW; P_SetScale(overheadarrow, mobj->destscale); + } + if (gametyperules & GTR_BUMPERS) + { if (p->spectator) { // HEY! No being cheap... @@ -12095,7 +12171,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) // No bosses outside of a combat situation. // (just in case we want boss arenas to do double duty as battle maps) - if (!bossinfo.boss && (mobjinfo[i].flags & MF_BOSS)) + if (!(gametyperules & GTR_BOSS) && (mobjinfo[i].flags & MF_BOSS)) { return false; } @@ -12116,7 +12192,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i) if ((i == MT_RING) && (gametyperules & GTR_SPHERES)) return MT_BLUESPHERE; - if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT) && !bossinfo.boss) + if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT)) return MT_PAPERITEMSPOT; return i; @@ -12564,6 +12640,21 @@ static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height) return corona; } +void P_InitSkyboxPoint(mobj_t *mobj, mapthing_t *mthing) +{ + mtag_t tag = Tag_FGet(&mthing->tags); + if (tag < 0 || tag > 15) + { + CONS_Debug(DBG_GAMELOGIC, "P_InitSkyboxPoint: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings))); + return; + } + + if (mthing->args[0]) + P_SetTarget(&skyboxcenterpnts[tag], mobj); + else + P_SetTarget(&skyboxviewpnts[tag], mobj); +} + static boolean P_MapAlreadyHasStarPost(mobj_t *mobj) { thinker_t *th; @@ -12609,17 +12700,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } case MT_SKYBOX: { - mtag_t tag = Tag_FGet(&mthing->tags); - if (tag < 0 || tag > 15) - { - CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings))); - break; - } - - if (mthing->args[0]) - skyboxcenterpnts[tag] = mobj; - else - skyboxviewpnts[tag] = mobj; + P_InitSkyboxPoint(mobj, mthing); break; } case MT_EGGSTATUE: diff --git a/src/p_saveg.c b/src/p_saveg.c index aa22e6732..319916f98 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1063,34 +1063,6 @@ static void P_NetUnArchiveColormaps(savebuffer_t *save) net_colormaps = NULL; } -static void P_NetArchiveTubeWaypoints(savebuffer_t *save) -{ - INT32 i, j; - - for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) - { - WRITEUINT16(save->p, numtubewaypoints[i]); - for (j = 0; j < numtubewaypoints[i]; j++) - WRITEUINT32(save->p, tubewaypoints[i][j] ? tubewaypoints[i][j]->mobjnum : 0); - } -} - -static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) -{ - INT32 i, j; - UINT32 mobjnum; - - for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) - { - numtubewaypoints[i] = READUINT16(save->p); - for (j = 0; j < numtubewaypoints[i]; j++) - { - mobjnum = READUINT32(save->p); - tubewaypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(mobjnum); - } - } -} - /// /// World Archiving /// @@ -1759,7 +1731,6 @@ static void UnArchiveLines(savebuffer_t *save) li->executordelay = READINT32(save->p); if (diff3 & LD_ACTIVATION) li->activation = READUINT32(save->p); - } } @@ -2133,9 +2104,6 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 if (diff & MD_MORE) WRITEUINT32(save->p, diff2); - // save pointer, at load time we will search this pointer to reinitilize pointers - WRITEUINT32(save->p, (size_t)mobj); - WRITEFIXED(save->p, mobj->z); // Force this so 3dfloor problems don't arise. WRITEFIXED(save->p, mobj->floorz); WRITEFIXED(save->p, mobj->ceilingz); @@ -3105,6 +3073,7 @@ static void P_NetUnArchiveWaypoints(savebuffer_t *save) for (i = 0U; i < numArchiveWaypoints; i++) { waypoint = K_GetWaypointFromIndex(i); temp = READUINT32(save->p); + waypoint->mobj = NULL; if (!P_SetTarget(&waypoint->mobj, P_FindNewPosition(temp))) { CONS_Debug(DBG_GAMELOGIC, "waypoint mobj not found for %d\n", i); } @@ -3113,6 +3082,38 @@ static void P_NetUnArchiveWaypoints(savebuffer_t *save) } } +static void P_NetArchiveTubeWaypoints(savebuffer_t *save) +{ + INT32 i, j; + + for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) + { + WRITEUINT16(save->p, numtubewaypoints[i]); + for (j = 0; j < numtubewaypoints[i]; j++) + { + WRITEUINT32(save->p, SaveMobjnum(tubewaypoints[i][j])); + } + } +} + +static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) +{ + INT32 i, j; + UINT32 mobjnum; + + for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) + { + numtubewaypoints[i] = READUINT16(save->p); + for (j = 0; j < numtubewaypoints[i]; j++) + { + mobjnum = READUINT32(save->p); + tubewaypoints[i][j] = NULL; + if (mobjnum != 0) + P_SetTarget(&tubewaypoints[i][j], P_FindNewPosition(mobjnum)); + } + } +} + // Now save the pointers, tracer and target, but at load time we must // relink to this; the savegame contains the old position in the pointer // field copyed in the info field temporarily, but finally we just search @@ -3133,7 +3134,7 @@ mobj_t *P_FindNewPosition(UINT32 oldposition) return mobj; } - CONS_Debug(DBG_GAMELOGIC, "mobj not found\n"); + CONS_Debug(DBG_GAMELOGIC, "mobj %d not found\n", oldposition); return NULL; } @@ -3175,7 +3176,6 @@ static inline pslope_t *LoadSlope(UINT32 slopeid) static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { - thinker_t *next; mobj_t *mobj; UINT32 diff; UINT32 diff2; @@ -3189,8 +3189,6 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) else diff2 = 0; - next = (void *)(size_t)READUINT32(save->p); - z = READFIXED(save->p); // Force this so 3dfloor problems don't arise. floorz = READFIXED(save->p); ceilingz = READFIXED(save->p); @@ -3495,17 +3493,9 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight; } - if (mobj->type == MT_SKYBOX) + if (mobj->type == MT_SKYBOX && mobj->spawnpoint) { - mtag_t tag = mobj->movedir; - if (tag < 0 || tag > 15) - { - CONS_Debug(DBG_GAMELOGIC, "LoadMobjThinker: Skybox ID %d of netloaded object is not between 0 and 15!\n", tag); - } - else if (mobj->flags2 & MF2_AMBUSH) - skyboxcenterpnts[tag] = mobj; - else - skyboxviewpnts[tag] = mobj; + P_InitSkyboxPoint(mobj, mobj->spawnpoint); } if (diff2 & MD2_WAYPOINTCAP) @@ -3514,8 +3504,6 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) if (diff2 & MD2_KITEMCAP) P_SetTarget(&kitemcap, mobj); - mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function - R_AddMobjInterpolator(mobj); return &mobj->thinker; @@ -4135,7 +4123,6 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) // remove all the current thinkers for (i = 0; i < NUM_THINKERLISTS; i++) { - currentthinker = thlist[i].next; for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next) { next = currentthinker->next; @@ -4155,6 +4142,13 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) iquetail = iquehead = 0; P_InitThinkers(); + // Oh my god don't blast random memory with our reference counts. + waypointcap = kitemcap = NULL; + for (i = 0; i <= 15; i++) + { + skyboxcenterpnts[i] = skyboxviewpnts[i] = NULL; + } + // clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity for (i = 0; i < numsectors; i++) { @@ -4458,28 +4452,11 @@ static inline void P_UnArchivePolyObjects(savebuffer_t *save) P_UnArchivePolyObj(save, &PolyObjects[i]); } -static inline void P_FinishMobjs(void) -{ - thinker_t *currentthinker; - mobj_t *mobj; - - // put info field there real value - for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; - currentthinker = currentthinker->next) - { - if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) - continue; - - mobj = (mobj_t *)currentthinker; - mobj->info = &mobjinfo[mobj->type]; - } -} - static void P_RelinkPointers(void) { thinker_t *currentthinker; mobj_t *mobj; - UINT32 temp; + UINT32 temp, i; // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; @@ -4544,85 +4521,89 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&mobj->terrainOverlay, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "terrainOverlay not found on %d\n", mobj->type); } - if (mobj->player) + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (players[i].skybox.viewpoint) { - if ( mobj->player->skybox.viewpoint) + temp = (UINT32)(size_t)players[i].skybox.viewpoint; + players[i].skybox.viewpoint = NULL; + if (!P_SetTarget(&players[i].skybox.viewpoint, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "skybox.viewpoint not found on player %d\n", i); + } + if (players[i].skybox.centerpoint) + { + temp = (UINT32)(size_t)players[i].skybox.centerpoint; + players[i].skybox.centerpoint = NULL; + if (!P_SetTarget(&players[i].skybox.centerpoint, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on player %d\n", i); + } + if (players[i].awayviewmobj) + { + temp = (UINT32)(size_t)players[i].awayviewmobj; + players[i].awayviewmobj = NULL; + if (!P_SetTarget(&players[i].awayviewmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on player %d\n", i); + } + if (players[i].followmobj) + { + temp = (UINT32)(size_t)players[i].followmobj; + players[i].followmobj = NULL; + if (!P_SetTarget(&players[i].followmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "followmobj not found on player %d\n", i); + } + if (players[i].follower) + { + temp = (UINT32)(size_t)players[i].follower; + players[i].follower = NULL; + if (!P_SetTarget(&players[i].follower, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "follower not found on player %d\n", i); + } + if (players[i].currentwaypoint) + { + temp = (UINT32)(size_t)players[i].currentwaypoint; + players[i].currentwaypoint = K_GetWaypointFromIndex(temp); + if (players[i].currentwaypoint == NULL) { - temp = (UINT32)(size_t)mobj->player->skybox.viewpoint; - mobj->player->skybox.viewpoint = NULL; - if (!P_SetTarget(&mobj->player->skybox.viewpoint, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "skybox.viewpoint not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "currentwaypoint not found on player %d\n", i); } - if ( mobj->player->skybox.centerpoint) + } + if (players[i].nextwaypoint) + { + temp = (UINT32)(size_t)players[i].nextwaypoint; + players[i].nextwaypoint = K_GetWaypointFromIndex(temp); + if (players[i].nextwaypoint == NULL) { - temp = (UINT32)(size_t)mobj->player->skybox.centerpoint; - mobj->player->skybox.centerpoint = NULL; - if (!P_SetTarget(&mobj->player->skybox.centerpoint, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on player %d\n", i); } - if ( mobj->player->awayviewmobj) + } + if (players[i].respawn.wp) + { + temp = (UINT32)(size_t)players[i].respawn.wp; + players[i].respawn.wp = K_GetWaypointFromIndex(temp); + if (players[i].respawn.wp == NULL) { - temp = (UINT32)(size_t)mobj->player->awayviewmobj; - mobj->player->awayviewmobj = NULL; - if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); - } - if (mobj->player->followmobj) - { - temp = (UINT32)(size_t)mobj->player->followmobj; - mobj->player->followmobj = NULL; - if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type); - } - if (mobj->player->follower) - { - temp = (UINT32)(size_t)mobj->player->follower; - mobj->player->follower = NULL; - if (!P_SetTarget(&mobj->player->follower, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "follower not found on %d\n", mobj->type); - } - if (mobj->player->currentwaypoint) - { - temp = (UINT32)(size_t)mobj->player->currentwaypoint; - mobj->player->currentwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->currentwaypoint == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "currentwaypoint not found on %d\n", mobj->type); - } - } - if (mobj->player->nextwaypoint) - { - temp = (UINT32)(size_t)mobj->player->nextwaypoint; - mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->nextwaypoint == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); - } - } - if (mobj->player->respawn.wp) - { - temp = (UINT32)(size_t)mobj->player->respawn.wp; - mobj->player->respawn.wp = K_GetWaypointFromIndex(temp); - if (mobj->player->respawn.wp == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on %d\n", mobj->type); - } - } - if (mobj->player->hoverhyudoro) - { - temp = (UINT32)(size_t)mobj->player->hoverhyudoro; - mobj->player->hoverhyudoro = NULL; - if (!P_SetTarget(&mobj->player->hoverhyudoro, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on %d\n", mobj->type); - } - if (mobj->player->stumbleIndicator) - { - temp = (UINT32)(size_t)mobj->player->stumbleIndicator; - mobj->player->stumbleIndicator = NULL; - if (!P_SetTarget(&mobj->player->stumbleIndicator, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on player %d\n", i); } } + if (players[i].hoverhyudoro) + { + temp = (UINT32)(size_t)players[i].hoverhyudoro; + players[i].hoverhyudoro = NULL; + if (!P_SetTarget(&players[i].hoverhyudoro, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on player %d\n", i); + } + if (players[i].stumbleIndicator) + { + temp = (UINT32)(size_t)players[i].stumbleIndicator; + players[i].stumbleIndicator = NULL; + if (!P_SetTarget(&players[i].stumbleIndicator, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on player %d\n", i); + } } } @@ -5174,7 +5155,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) { thinker_t *th; mobj_t *mobj; - INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise + UINT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise CV_SaveNetVars(&save->p); P_NetArchiveMisc(save, resending); @@ -5254,7 +5235,6 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) P_NetUnArchiveTubeWaypoints(save); P_NetUnArchiveWaypoints(save); P_RelinkPointers(); - P_FinishMobjs(); } ACS_UnArchive(save); @@ -5270,3 +5250,92 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) return P_UnArchiveLuabanksAndConsistency(save); } + +boolean P_SaveBufferZAlloc(savebuffer_t *save, size_t alloc_size, INT32 tag, void *user) +{ + I_Assert(save->buffer == NULL); + save->buffer = (UINT8 *)Z_Malloc(alloc_size, tag, user); + + if (save->buffer == NULL) + { + return false; + } + + save->size = alloc_size; + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromExisting(savebuffer_t *save, UINT8 *existing_buffer, size_t existing_size) +{ + I_Assert(save->buffer == NULL); + + if (existing_buffer == NULL || existing_size == 0) + { + return false; + } + + save->buffer = existing_buffer; + save->size = existing_size; + + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromLump(savebuffer_t *save, lumpnum_t lump) +{ + I_Assert(save->buffer == NULL); + + if (lump == LUMPERROR) + { + return false; + } + + save->buffer = (UINT8 *)W_CacheLumpNum(lump, PU_STATIC); + + if (save->buffer == NULL) + { + return false; + } + + save->size = W_LumpLength(lump); + + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromFile(savebuffer_t *save, char const *name) +{ + size_t len = 0; + + I_Assert(save->buffer == NULL); + len = FIL_ReadFile(name, &save->buffer); + + if (len != 0) + { + save->size = len; + + save->p = save->buffer; + save->end = save->buffer + save->size; + } + + return len; +} + +static void P_SaveBufferInvalidate(savebuffer_t *save) +{ + save->buffer = save->p = save->end = NULL; + save->size = 0; +} + +void P_SaveBufferFree(savebuffer_t *save) +{ + Z_Free(save->buffer); + P_SaveBufferInvalidate(save); +} diff --git a/src/p_saveg.h b/src/p_saveg.h index a0b0f8587..66ba58b00 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -57,6 +57,13 @@ struct savebuffer_t size_t size; }; +boolean P_SaveBufferZAlloc(savebuffer_t *save, size_t alloc_size, INT32 tag, void *user); +#define P_SaveBufferAlloc(a,b) P_SaveBufferZAlloc(a, b, PU_STATIC, NULL) +boolean P_SaveBufferFromExisting(savebuffer_t *save, UINT8 *existing_buffer, size_t existing_size); +boolean P_SaveBufferFromLump(savebuffer_t *save, lumpnum_t lump); +boolean P_SaveBufferFromFile(savebuffer_t *save, char const *name); +void P_SaveBufferFree(savebuffer_t *save); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/p_setup.c b/src/p_setup.c index 8d3d642f0..ab319c5c6 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -185,7 +185,7 @@ UINT16 numtubewaypoints[NUMTUBEWAYPOINTSEQUENCES]; void P_AddTubeWaypoint(UINT8 sequence, UINT8 id, mobj_t *waypoint) { - tubewaypoints[sequence][id] = waypoint; + P_SetTarget(&tubewaypoints[sequence][id], waypoint); if (id >= numtubewaypoints[sequence]) numtubewaypoints[sequence] = id + 1; } @@ -723,13 +723,10 @@ void P_WriteThings(void) const char * filename; size_t i, length; mapthing_t *mt; - savebuffer_t save; + savebuffer_t save = {0}; INT16 temp; - save.size = nummapthings * sizeof (mapthing_t); - save.p = save.buffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t)); - - if (!save.p) + if (P_SaveBufferAlloc(&save, nummapthings * sizeof (mapthing_t)) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for thing writing!\n")); return; @@ -755,8 +752,7 @@ void P_WriteThings(void) filename = va("newthings-%s.lmp", G_BuildMapName(gamemap)); FIL_WriteFile(filename, save.buffer, length); - free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); CONS_Printf(M_GetText("%s saved.\n"), filename); } @@ -6891,7 +6887,6 @@ static void P_InitLevelSettings(void) rflagpoint = bflagpoint = NULL; // circuit, race and competition stuff - circuitmap = false; numstarposts = 0; timeinmap = 0; @@ -6913,7 +6908,7 @@ static void P_InitLevelSettings(void) if (playeringame[i] && !players[i].spectator) p++; - if (grandprixinfo.gp == false && bossinfo.boss == false) + if (grandprixinfo.gp == false) players[i].lives = 3; G_PlayerReborn(i, true); @@ -6923,39 +6918,27 @@ static void P_InitLevelSettings(void) racecountdown = exitcountdown = exitfadestarted = 0; curlap = bestlap = 0; // SRB2Kart - // SRB2Kart: map load variables + // Gamespeed and frantic items + gamespeed = KARTSPEED_EASY; + franticitems = false; + if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_BUMPERS)) - { - gamespeed = KARTSPEED_EASY; - } - else + if (gametyperules & GTR_CIRCUIT) { gamespeed = grandprixinfo.gamespeed; } - - franticitems = false; - } - else if (bossinfo.boss) - { - gamespeed = KARTSPEED_EASY; - franticitems = false; } else if (modeattacking) { - // Just play it safe and set everything - if ((gametyperules & GTR_BUMPERS)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) + { gamespeed = KARTSPEED_HARD; - franticitems = false; + } } else { - if ((gametyperules & GTR_BUMPERS)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) { if (cv_kartspeed.value == KARTSPEED_AUTO) gamespeed = ((speedscramble == -1) ? KARTSPEED_NORMAL : (UINT8)speedscramble); @@ -6970,6 +6953,9 @@ static void P_InitLevelSettings(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; + + K_ResetSpecialStage(); + K_ResetBossInfo(); } #if 0 @@ -7071,20 +7057,23 @@ static void P_LoadRecordGhosts(void) gpath = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap))); // Best Time ghost - if (cv_ghost_besttime.value) + if (modeattacking & ATTACKING_TIME) { - for (i = 0; i < numskins; ++i) + if (cv_ghost_besttime.value) { - if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) - continue; + for (i = 0; i < numskins; ++i) + { + if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) + continue; - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) + G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + } } } // Best Lap ghost - if (modeattacking != ATTACKING_CAPSULES) + if (modeattacking & ATTACKING_LAP) { if (cv_ghost_bestlap.value) { @@ -7291,7 +7280,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // This is needed. Don't touch. maptol = mapheaderinfo[gamemap-1]->typeoflevel; - gametyperules = gametypedefaultrules[gametype]; CON_Drawer(); // let the user know what we are going to do I_FinishUpdate(); // page flip or blit buffer @@ -7405,13 +7393,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } G_ClearModeAttackRetryFlag(); } - /* - else if (rendermode != render_none && G_IsSpecialStage(gamemap)) - { - P_RunSpecialStageWipe(); - ranspecialwipe = 1; - } - */ // Make sure all sounds are stopped before Z_FreeTags. S_StopSounds(); @@ -7446,7 +7427,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) S_Start(); } - levelfadecol = (encoremode ? 0 : 31); + if (gametyperules & GTR_SPECIALSTART) + { + if (ranspecialwipe != 2) + S_StartSound(NULL, sfx_s3kaf); + levelfadecol = 0; + } + else if (encoremode) + { + levelfadecol = 0; + } + else + { + levelfadecol = 31; + } if (rendermode != render_none) { @@ -7689,19 +7683,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) K_UpdateMatchRaceBots(); } - if (bossinfo.boss) - { - // Reset some pesky boss state that can't be handled elsewhere. - bossinfo.barlen = BOSSHEALTHBARLEN; - bossinfo.visualbar = 0; - Z_Free(bossinfo.enemyname); - Z_Free(bossinfo.subtitle); - bossinfo.enemyname = bossinfo.subtitle = NULL; - bossinfo.titleshow = 0; - bossinfo.titlesound = sfx_typri1; - memset(&(bossinfo.weakspots), 0, sizeof(weakspot_t)*NUMWEAKSPOTS); - } - if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; @@ -7851,7 +7832,7 @@ UINT8 P_InitMapData(boolean existingmapheaders) for (i = 0; i < nummapheaders; ++i) { name = mapheaderinfo[i]->lumpname; - maplump = W_CheckNumForMap(name); + maplump = W_CheckNumForMap(name, (mapheaderinfo[i]->lumpnum == LUMPERROR)); // Always check for cup cache reassociations. // (The core assumption is that cups < headers.) diff --git a/src/p_spec.c b/src/p_spec.c index 47d8b0727..bf1170a7b 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -43,6 +43,7 @@ // SRB2kart #include "k_kart.h" +#include "k_specialstage.h" #include "console.h" // CON_LogMessage #include "k_respawn.h" #include "k_terrain.h" @@ -1903,7 +1904,7 @@ static void K_HandleLapIncrement(player_t *player) { if (player) { - if (leveltime < starttime) + if (leveltime < starttime && !(gametyperules & GTR_ROLLINGSTART)) { // Will fault the player K_DoIngameRespawn(player); @@ -1962,7 +1963,7 @@ static void K_HandleLapIncrement(player_t *player) if (P_IsDisplayPlayer(player)) { - if (player->laps == numlaps) // final lap + if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap S_StartSound(NULL, sfx_s221); @@ -1985,6 +1986,14 @@ static void K_HandleLapIncrement(player_t *player) // finished race exit setup if (player->laps > numlaps) { + if (specialstageinfo.valid == true) + { + // Don't permit a win just by sneaking ahead of the UFO/emerald. + if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) + { + player->pflags |= PF_NOCONTEST; + } + } P_DoPlayerExit(player); P_SetupSignExit(player); } @@ -7206,12 +7215,6 @@ void P_SpawnSpecials(boolean fromnetsave) break; } - // SRB2Kart - case 2001: // Finish Line - if ((gametyperules & GTR_CIRCUIT)) - circuitmap = true; - break; - default: break; } diff --git a/src/p_spec.h b/src/p_spec.h index fcd5348a8..3ba3eb9c8 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -24,6 +24,8 @@ extern "C" { extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs +void P_InitSkyboxPoint(mobj_t *mobj, mapthing_t *mthing); + // Amount (dx, dy) vector linedef is shifted right to get scroll amount #define SCROLL_SHIFT 5 diff --git a/src/p_tick.c b/src/p_tick.c index d8af4129c..e3cd1230f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -362,7 +362,7 @@ static inline void P_RunThinkers(void) if (gametyperules & GTR_PAPERITEMS) K_RunPaperItemSpawners(); - if ((gametyperules & GTR_BUMPERS) && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) K_RunBattleOvertime(); ps_acs_time = I_GetPreciseTime(); @@ -651,7 +651,7 @@ void P_Ticker(boolean run) P_PlayerAfterThink(&players[i]); // Bosses have a punchy start, so no position. - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { if (leveltime == 3) { @@ -724,7 +724,7 @@ void P_Ticker(boolean run) K_TickSpecialStage(); - if ((gametyperules & GTR_BUMPERS)) + if ((gametyperules & GTR_POINTLIMIT)) { if (wantedcalcdelay && --wantedcalcdelay <= 0) K_CalculateBattleWanted(); diff --git a/src/p_user.c b/src/p_user.c index ac96ea345..34c016d18 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -54,6 +54,7 @@ #include "k_bot.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" @@ -501,7 +502,7 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) if (!player->mo) return 0; - if ((gametyperules & GTR_BUMPERS)) // No rings in Battle Mode + if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode return 0; test = player->rings + num_rings; @@ -519,6 +520,9 @@ INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres) { num_spheres += player->spheres; + if (!(gametyperules & GTR_SPHERES)) // No spheres in Race mode) + return 0; + // Not alive if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0)) return 0; @@ -554,7 +558,7 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) // Adds to the player's score void P_AddPlayerScore(player_t *player, UINT32 amount) { - if (!((gametyperules & GTR_BUMPERS))) + if (!((gametyperules & GTR_POINTLIMIT))) return; if (player->exiting) // srb2kart @@ -720,6 +724,7 @@ boolean P_EndingMusic(player_t *player) { char buffer[9]; boolean looping = true; + boolean racetracks = !!(gametyperules & GTR_CIRCUIT); INT32 bestlocalpos, test; player_t *bestlocalplayer; @@ -773,7 +778,7 @@ boolean P_EndingMusic(player_t *player) #undef getplayerpos - if ((gametyperules & GTR_CIRCUIT) && bestlocalpos == MAXPLAYERS+1) + if (racetracks == true && bestlocalpos == MAXPLAYERS+1) sprintf(buffer, "k*fail"); // F-Zero death results theme else { @@ -787,9 +792,11 @@ boolean P_EndingMusic(player_t *player) S_SpeedMusic(1.0f); - if ((gametyperules & GTR_CIRCUIT)) + if (racetracks == true) + { buffer[1] = 'r'; - else if ((gametyperules & GTR_BUMPERS)) + } + else { buffer[1] = 'b'; looping = false; @@ -828,7 +835,8 @@ void P_RestoreMusic(player_t *player) return; // Event - Level Start - if (bossinfo.boss == false && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled + if ((K_CheckBossIntro() == false) + && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled return; { @@ -1303,7 +1311,16 @@ void P_DoPlayerExit(player_t *player) P_EndingMusic(player); if (P_CheckRacers() && !exitcountdown) - exitcountdown = raceexittime+1; + { + if (specialstageinfo.valid == true && losing == true) + { + exitcountdown = (5*TICRATE)/2; + } + else + { + exitcountdown = raceexittime+1; + } + } } else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting { @@ -1775,6 +1792,38 @@ static void P_DoBubbleBreath(player_t *player) } } +static inline boolean P_IsMomentumAngleLocked(player_t *player) +{ + // This timer is used for the animation too and the + // animation should continue for a bit after the physics + // stop. + + if (player->stairjank > 8) + { + const angle_t th = K_MomentumAngle(player->mo); + const angle_t d = AngleDelta(th, player->mo->angle); + + // A larger difference between momentum and facing + // angles awards back control. + // <45 deg: 3/4 tics + // >45 deg: 2/4 tics + // >90 deg: 1/4 tics + // >135 deg: 0/4 tics + + if ((leveltime & 3) > (d / ANGLE_45)) + { + return true; + } + } + + if (K_IsRidingFloatingTop(player)) + { + return true; + } + + return false; +} + //#define OLD_MOVEMENT_CODE 1 static void P_3dMovement(player_t *player) { @@ -1795,7 +1844,7 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - if ((player->stairjank > 8 && leveltime & 3) || K_IsRidingFloatingTop(player)) + if (P_IsMomentumAngleLocked(player)) { movepushangle = K_MomentumAngle(player->mo); } @@ -2622,7 +2671,7 @@ static void P_DeathThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V @@ -3044,7 +3093,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall return true; } - if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT)) // 1 for momentum keep, 2 for turnaround + if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT) && player->karthud[khud_timeovercam] != 0) // 1 for momentum keep, 2 for turnaround timeover = (player->karthud[khud_timeovercam] > 2*TICRATE ? 2 : 1); else timeover = 0; @@ -3612,7 +3661,7 @@ void P_DoTimeOver(player_t *player) legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p } - if (netgame && !player->bot && !bossinfo.boss) + if (netgame && !player->bot && !(gametyperules & GTR_BOSS)) { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); } @@ -3981,7 +4030,7 @@ void P_PlayerThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V diff --git a/src/s_sound.c b/src/s_sound.c index 96211d653..1fc80cf8e 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -30,7 +30,6 @@ #include "m_misc.h" // for tunes command #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook -#include "k_boss.h" // bossinfo #include "byteptr.h" #ifdef HW3SOUND @@ -1366,6 +1365,28 @@ musicdef_t *musicdefstart = NULL; struct cursongcredit cursongcredit; // Currently displayed song credit info int musicdef_volume; +// +// S_FindMusicDef +// +// Find music def by 6 char name +// +static musicdef_t *S_FindMusicDef(const char *name) +{ + musicdef_t *def = musicdefstart; + + while (def) + { + if (!stricmp(def->name, name)) + { + return def; + } + + def = def->next; + } + + return NULL; +} + static boolean MusicDefError ( @@ -1413,21 +1434,10 @@ ReadMusicDefFields } else { - musicdef_t **tail = &musicdefstart; - - // Search if this is a replacement - while (*tail) - { - if (!stricmp((*tail)->name, value)) - { - break; - } - - tail = &(*tail)->next; - } + def = S_FindMusicDef(value); // Nothing found, add to the end. - if (!(*tail)) + if (!def) { def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL); @@ -1435,10 +1445,11 @@ ReadMusicDefFields strlwr(def->name); def->volume = DEFAULT_MUSICDEF_VOLUME; - (*tail) = def; + def->next = musicdefstart; + musicdefstart = def; } - (*defp) = (*tail); + (*defp) = def; } } else @@ -1612,7 +1623,11 @@ void S_InitMusicDefs(void) // void S_ShowMusicCredit(void) { - musicdef_t *def = musicdefstart; + musicdef_t *def = S_FindMusicDef(music_name); + + char credittext[128] = ""; + char *work = NULL; + size_t len = 128, worklen; if (!cv_songcredits.value || demo.rewinding) return; @@ -1620,58 +1635,45 @@ void S_ShowMusicCredit(void) if (!def) // No definitions return; - while (def) + if (!def->title) { - if (!stricmp(def->name, music_name)) - { - char credittext[128] = ""; - char *work = NULL; - size_t len = 128, worklen; + return; + } - if (!def->title) - { - return; - } - - work = va("\x1F %s", def->title); - worklen = strlen(work); - if (worklen <= len) - { - strncat(credittext, work, len); - len -= worklen; + work = va("\x1F %s", def->title); + worklen = strlen(work); + if (worklen <= len) + { + strncat(credittext, work, len); + len -= worklen; #define MUSICCREDITAPPEND(field)\ - if (field)\ - {\ - work = va(" - %s", field);\ - worklen = strlen(work);\ - if (worklen <= len)\ - {\ - strncat(credittext, work, len);\ - len -= worklen;\ - }\ - } - - MUSICCREDITAPPEND(def->author); - MUSICCREDITAPPEND(def->source); - -#undef MUSICCREDITAPPEND - } - - if (credittext[0] == '\0') - return; - - cursongcredit.def = def; - Z_Free(cursongcredit.text); - cursongcredit.text = Z_StrDup(credittext); - cursongcredit.anim = 5*TICRATE; - cursongcredit.x = cursongcredit.old_x = 0; - cursongcredit.trans = NUMTRANSMAPS; - return; + if (field)\ + {\ + work = va(" - %s", field);\ + worklen = strlen(work);\ + if (worklen <= len)\ + {\ + strncat(credittext, work, len);\ + len -= worklen;\ + }\ } - def = def->next; + MUSICCREDITAPPEND(def->author); + MUSICCREDITAPPEND(def->source); + +#undef MUSICCREDITAPPEND } + + if (credittext[0] == '\0') + return; + + cursongcredit.def = def; + Z_Free(cursongcredit.text); + cursongcredit.text = Z_StrDup(credittext); + cursongcredit.anim = 5*TICRATE; + cursongcredit.x = cursongcredit.old_x = 0; + cursongcredit.trans = NUMTRANSMAPS; } /// ------------------------ @@ -2243,13 +2245,11 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 musicdef_volume = DEFAULT_MUSICDEF_VOLUME; { - musicdef_t *def; - for (def = musicdefstart; def; def = def->next) + musicdef_t *def = S_FindMusicDef(music_name); + + if (def) { - if (strcasecmp(def->name, music_name) == 0) - { - musicdef_volume = def->volume; - } + musicdef_volume = def->volume; } } @@ -2457,6 +2457,27 @@ void S_StartEx(boolean reset) music_stack_fadein = JINGLEPOSTFADE; } +static inline void PrintMusicDefField(const char *label, const char *field) +{ + if (field) + { + CONS_Printf("%s%s\n", label, field); + } +} + +static void PrintSongAuthors(const musicdef_t *def) +{ + CONS_Printf("Volume: %d/100\n\n", def->volume); + + PrintMusicDefField("Title: ", def->title); + PrintMusicDefField("Author: ", def->author); + + CONS_Printf("\n"); + + PrintMusicDefField("Original Source: ", def->source); + PrintMusicDefField("Original Composers: ", def->composers); +} + // TODO: fix this function, needs better support for map names static void Command_Tunes_f(void) { @@ -2481,8 +2502,15 @@ static void Command_Tunes_f(void) if (!strcasecmp(tunearg, "-show")) { + const musicdef_t *def = S_FindMusicDef(mapmusname); + CONS_Printf(M_GetText("The current tune is: %s [track %d]\n"), mapmusname, (mapmusflags & MUSIC_TRACKMASK)); + + if (def != NULL) + { + PrintSongAuthors(def); + } return; } if (!strcasecmp(tunearg, "-none")) diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index ee9c4cddc..62e93a2d6 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -1,12 +1,12 @@ # Declare SDL2 interface sources target_sources(SRB2SDL2 PRIVATE - mixer_sound.c + new_sound.cpp ogl_sdl.c i_threads.c i_net.c i_system.c - i_main.c + i_main.cpp i_video.c dosstr.c endtxt.c @@ -57,9 +57,9 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Darwin) endif() if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") - target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static SDL2_mixer::SDL2_mixer-static) + target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static) else() - target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2 SDL2_mixer::SDL2_mixer) + target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2) endif() if("${CMAKE_SYSTEM_NAME}" MATCHES Linux) diff --git a/src/sdl/SDL_main/SDL_macosx_main.m b/src/sdl/SDL_main/SDL_macosx_main.m index 226afe13d..273dcfd39 100644 --- a/src/sdl/SDL_main/SDL_macosx_main.m +++ b/src/sdl/SDL_main/SDL_macosx_main.m @@ -287,8 +287,8 @@ static void CustomApplicationMain (int argc, char **argv) [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; #endif - if (!getenv("SRB2WADDIR")) - setenv("SRB2WADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1); + if (!getenv("RINGRACERSWADDIR")) + setenv("RINGRACERSWADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1); /* Hand off to main application code */ status = SDL_main (gArgc, gArgv); diff --git a/src/sdl/i_main.c b/src/sdl/i_main.cpp similarity index 79% rename from src/sdl/i_main.c rename to src/sdl/i_main.cpp index bc1200788..90b1a6a96 100644 --- a/src/sdl/i_main.c +++ b/src/sdl/i_main.cpp @@ -23,6 +23,10 @@ #include "../m_misc.h"/* path shit */ #include "../i_system.h" +#include +#include +#include + #if defined (__GNUC__) || defined (__unix__) #include #endif @@ -31,7 +35,9 @@ #include #endif +extern "C" { #include "time.h" // For log timestamps +} #ifdef HAVE_SDL @@ -66,11 +72,9 @@ char logfilename[1024]; #endif #if defined (_WIN32) -#include -#endif - -#if defined (_WIN32) +extern "C" { #include "../win32/win_dbg.h" +} typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID); #endif @@ -151,20 +155,20 @@ static void InitLogging(void) if (M_IsPathAbsolute(reldir)) { left = snprintf(logfilename, sizeof logfilename, - "%s"PATHSEP, reldir); + "%s" PATHSEP, reldir); } else #ifdef DEFAULTDIR if (logdir) { left = snprintf(logfilename, sizeof logfilename, - "%s"PATHSEP DEFAULTDIR PATHSEP"%s"PATHSEP, logdir, reldir); + "%s" PATHSEP DEFAULTDIR PATHSEP "%s" PATHSEP, logdir, reldir); } else #endif/*DEFAULTDIR*/ { left = snprintf(logfilename, sizeof logfilename, - "."PATHSEP"%s"PATHSEP, reldir); + "." PATHSEP "%s" PATHSEP, reldir); } strftime(&logfilename[left], sizeof logfilename - left, @@ -179,7 +183,7 @@ static void InitLogging(void) logstream = fopen(logfilename, "w"); #ifdef DEFAULTDIR if (logdir) - link = va("%s/"DEFAULTDIR"/latest-log.txt", logdir); + link = va("%s/" DEFAULTDIR "/latest-log.txt", logdir); else #endif/*DEFAULTDIR*/ link = "latest-log.txt"; @@ -194,6 +198,20 @@ static void InitLogging(void) } #endif +#ifdef _WIN32 +static void init_exchndl() +{ + HMODULE exchndl_module = LoadLibraryA("exchndl.dll"); + if (exchndl_module != NULL) + { + using PFN_ExcHndlInit = void(*)(void); + PFN_ExcHndlInit pfnExcHndlInit = reinterpret_cast( + GetProcAddress(exchndl_module, "ExcHndlInit")); + if (pfnExcHndlInit != NULL) + (pfnExcHndlInit)(); + } +} +#endif #ifdef _WIN32 static void @@ -208,6 +226,33 @@ ChDirToExe (void) } #endif +static void walk_exception_stack(std::string& accum, bool nested) { + if (nested) + accum.append("\n Caused by: Unknown exception"); + else + accum.append("Uncaught exception: Unknown exception"); +} + +static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) { + if (nested) + accum.append("\n Caused by: "); + else + accum.append("Uncaught exception: "); + + accum.append("("); + accum.append(typeid(ex).name()); + accum.append(") "); + accum.append(ex.what()); + + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + walk_exception_stack(accum, ex, true); + } catch (...) { + walk_exception_stack(accum, true); + } +} + /** \brief The main function @@ -259,7 +304,7 @@ int main(int argc, char **argv) ) #endif { - ExcHndlInit(); + init_exchndl(); } } #ifndef __MINGW32__ @@ -268,6 +313,8 @@ int main(int argc, char **argv) MakeCodeWritable(); #endif + try { + // startup SRB2 CONS_Printf("Setting up Dr. Robotnik's Ring Racers...\n"); D_SRB2Main(); @@ -279,6 +326,16 @@ int main(int argc, char **argv) // never return D_SRB2Loop(); + } catch (const std::exception& ex) { + std::string exception; + walk_exception_stack(exception, ex, false); + I_Error("%s", exception.c_str()); + } catch (...) { + std::string exception; + walk_exception_stack(exception, false); + I_Error("%s", exception.c_str()); + } + #ifdef BUGTRAP // This is safe even if BT didn't start. ShutdownBugTrap(); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index b390b6d1e..515dc3e95 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -989,7 +989,7 @@ INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev) SDL_Joystick *joystick = NULL; joystick = SDL_GameControllerGetJoystick(dev); - + if (joystick) { return SDL_JoystickInstanceID(joystick); @@ -2242,9 +2242,9 @@ static const char *locateWad(void) const char *envstr; const char *WadPath; - I_OutputMsg("SRB2WADDIR"); - // does SRB2WADDIR exist? - if (((envstr = I_GetEnv("SRB2WADDIR")) != NULL) && isWadPathOk(envstr)) + I_OutputMsg("RINGRACERSWADDIR"); + // does RINGRACERSWADDIR exist? + if (((envstr = I_GetEnv("RINGRACERSWADDIR")) != NULL) && isWadPathOk(envstr)) return envstr; #ifndef NOCWD diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 5cea8c65a..bdb522efb 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -572,10 +572,13 @@ static INT32 SDLJoyAxis(const Sint16 axis, UINT8 pid) static void Impl_HandleWindowEvent(SDL_WindowEvent evt) { +#define FOCUSUNION (mousefocus | (kbfocus << 1)) static SDL_bool firsttimeonmouse = SDL_TRUE; static SDL_bool mousefocus = SDL_TRUE; static SDL_bool kbfocus = SDL_TRUE; + const unsigned int oldfocus = FOCUSUNION; + switch (evt.event) { case SDL_WINDOWEVENT_ENTER: @@ -599,6 +602,11 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) window_y = evt.data2; } + if (FOCUSUNION == oldfocus) // No state change + { + return; + } + if (mousefocus && kbfocus) { // Tell game we got focus back, resume music if necessary @@ -639,7 +647,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) SDLdoUngrabMouse(); } } - +#undef FOCUSUNION } static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp new file mode 100644 index 000000000..3052ee587 --- /dev/null +++ b/src/sdl/new_sound.cpp @@ -0,0 +1,665 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include + +#include "../audio/chunk_load.hpp" +#include "../audio/gain.hpp" +#include "../audio/mixer.hpp" +#include "../audio/music_player.hpp" +#include "../audio/sound_chunk.hpp" +#include "../audio/sound_effect_player.hpp" +#include "../cxxutil.hpp" +#include "../io/streams.hpp" + +#include "../doomdef.h" +#include "../i_sound.h" +#include "../s_sound.h" +#include "../sounds.h" +#include "../w_wad.h" +#include "../z_zone.h" + +using std::make_shared; +using std::make_unique; +using std::shared_ptr; +using std::unique_ptr; +using std::vector; + +using srb2::audio::Gain; +using srb2::audio::Mixer; +using srb2::audio::MusicPlayer; +using srb2::audio::Sample; +using srb2::audio::SoundChunk; +using srb2::audio::SoundEffectPlayer; +using srb2::audio::Source; +using namespace srb2; +using namespace srb2::io; + +// extern in i_sound.h +UINT8 sound_started = false; + +static unique_ptr> master; +static shared_ptr> mixer_sound_effects; +static shared_ptr> mixer_music; +static shared_ptr music_player; +static shared_ptr> gain_sound_effects; +static shared_ptr> gain_music; + +static vector> sound_effect_channels; + +static void (*music_fade_callback)(); + +void* I_GetSfx(sfxinfo_t* sfx) { + if (sfx->lumpnum == LUMPERROR) + sfx->lumpnum = S_GetSfxLumpNum(sfx); + sfx->length = W_LumpLength(sfx->lumpnum); + + std::byte* lump = static_cast(W_CacheLumpNum(sfx->lumpnum, PU_SOUND)); + auto _ = srb2::finally([lump]() { Z_Free(lump); }); + + tcb::span data_span(lump, sfx->length); + std::optional chunk = srb2::audio::try_load_chunk(data_span); + + if (!chunk) + return nullptr; + + SoundChunk* heap_chunk = new SoundChunk {std::move(*chunk)}; + + return heap_chunk; +} + +void I_FreeSfx(sfxinfo_t* sfx) { + if (sfx->data) { + SoundChunk* chunk = static_cast(sfx->data); + auto _ = srb2::finally([chunk]() { delete chunk; }); + + // Stop any channels playing this chunk + for (auto& player : sound_effect_channels) { + if (player->is_playing_chunk(chunk)) { + player->reset(); + } + } + } + sfx->data = nullptr; + sfx->lumpnum = LUMPERROR; +} + +namespace { + +class SdlAudioLockHandle { +public: + SdlAudioLockHandle() { SDL_LockAudio(); } + ~SdlAudioLockHandle() { SDL_UnlockAudio(); } +}; + +void audio_callback(void* userdata, Uint8* buffer, int len) { + // The SDL Audio lock is implied to be held during callback. + + try { + Sample<2>* float_buffer = reinterpret_cast*>(buffer); + size_t float_len = len / 8; + + for (size_t i = 0; i < float_len; i++) { + float_buffer[i] = Sample<2> {0.f, 0.f}; + } + + if (!master) + return; + + master->generate(tcb::span {float_buffer, float_len}); + + for (size_t i = 0; i < float_len; i++) { + float_buffer[i] = { + std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f), + std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f), + }; + } + } catch (...) { + } + + return; +} + +void initialize_sound() { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError()); + return; + } + + SDL_AudioSpec desired; + desired.format = AUDIO_F32SYS; + desired.channels = 2; + desired.samples = 1024; + desired.freq = 44100; + desired.callback = audio_callback; + + if (SDL_OpenAudio(&desired, NULL) < 0) { + CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return; + } + + SDL_PauseAudio(SDL_FALSE); + + { + SdlAudioLockHandle _; + + master = make_unique>(); + mixer_sound_effects = make_shared>(); + mixer_music = make_shared>(); + music_player = make_shared(); + gain_sound_effects = make_shared>(); + gain_music = make_shared>(); + gain_sound_effects->bind(mixer_sound_effects); + gain_music->bind(mixer_music); + master->add_source(gain_sound_effects); + master->add_source(gain_music); + mixer_music->add_source(music_player); + for (size_t i = 0; i < static_cast(cv_numChannels.value); i++) { + shared_ptr player = make_shared(); + sound_effect_channels.push_back(player); + mixer_sound_effects->add_source(player); + } + } + + sound_started = true; +} + +} // namespace + +void I_StartupSound(void) { + if (!sound_started) + initialize_sound(); +} + +void I_ShutdownSound(void) { + SdlAudioLockHandle _; + + for (auto& channel : sound_effect_channels) { + *channel = audio::SoundEffectPlayer(); + } +} + +void I_UpdateSound(void) { + // The SDL audio lock is re-entrant, so it is safe to lock twice + // for the "fade to stop music" callback later. + SdlAudioLockHandle _; + + if (music_fade_callback && !music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback()); + } + return; +} + +// +// SFX I/O +// + +INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel) { + (void) pitch; + (void) priority; + + SdlAudioLockHandle _; + + if (channel >= 0 && static_cast(channel) >= sound_effect_channels.size()) + return -1; + + shared_ptr player_channel; + if (channel < 0) { + // find a free sfx channel + for (size_t i = 0; i < sound_effect_channels.size(); i++) { + if (sound_effect_channels[i]->finished()) { + player_channel = sound_effect_channels[i]; + channel = i; + break; + } + } + } else { + player_channel = sound_effect_channels[channel]; + } + + if (!player_channel) + return -1; + + SoundChunk* chunk = static_cast(S_sfx[id].data); + if (chunk == nullptr) + return -1; + + float vol_float = static_cast(vol) / 255.f; + float sep_float = static_cast(sep) / 127.f - 1.f; + + player_channel->start(chunk, vol_float, sep_float); + + return channel; +} + +void I_StopSound(INT32 handle) { + SdlAudioLockHandle _; + + if (sound_effect_channels.empty()) + return; + + if (handle < 0) + return; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return; + + sound_effect_channels[index]->reset(); +} + +boolean I_SoundIsPlaying(INT32 handle) { + SdlAudioLockHandle _; + + // Handle is channel index + if (sound_effect_channels.empty()) + return 0; + + if (handle < 0) + return 0; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return 0; + + return sound_effect_channels[index]->finished() ? 0 : 1; +} + +void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) { + (void) pitch; + + SdlAudioLockHandle _; + + if (sound_effect_channels.empty()) + return; + + if (handle < 0) + return; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return; + + shared_ptr& channel = sound_effect_channels[index]; + if (!channel->finished()) { + float vol_float = static_cast(vol) / 255.f; + float sep_float = static_cast(sep) / 127.f - 1.f; + channel->update(vol_float, sep_float); + } +} + +void I_SetSfxVolume(int volume) { + SdlAudioLockHandle _; + float vol = static_cast(volume) / 100.f; + + if (gain_sound_effects) { + gain_sound_effects->gain(vol * vol * vol); + } +} + +/// ------------------------ +// MUSIC SYSTEM +/// ------------------------ + +void I_InitMusic(void) { + if (!sound_started) + initialize_sound(); + + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +void I_ShutdownMusic(void) { + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +/// ------------------------ +// MUSIC PROPERTIES +/// ------------------------ + +musictype_t I_SongType(void) { + if (!music_player) + return MU_NONE; + + SdlAudioLockHandle _; + + std::optional music_type = music_player->music_type(); + + if (music_type == std::nullopt) { + return MU_NONE; + } + + switch (*music_type) { + case audio::MusicType::kOgg: + return MU_OGG; + case audio::MusicType::kGme: + return MU_GME; + case audio::MusicType::kMod: + return MU_MOD; + default: + return MU_NONE; + } +} + +boolean I_SongPlaying(void) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + return music_player->music_type().has_value(); +} + +boolean I_SongPaused(void) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + return !music_player->playing(); +} + +/// ------------------------ +// MUSIC EFFECTS +/// ------------------------ + +boolean I_SetSongSpeed(float speed) { + (void) speed; + return false; +} + +/// ------------------------ +// MUSIC SEEKING +/// ------------------------ + +UINT32 I_GetSongLength(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional duration = music_player->duration_seconds(); + + if (!duration) + return 0; + + return static_cast(std::round(*duration * 1000.f)); +} + +boolean I_SetSongLoopPoint(UINT32 looppoint) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + if (music_player->music_type() == audio::MusicType::kOgg) { + music_player->loop_point_seconds(looppoint / 1000.f); + return true; + } + + return false; +} + +UINT32 I_GetSongLoopPoint(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional loop_point_seconds = music_player->loop_point_seconds(); + + if (!loop_point_seconds) + return 0; + + return static_cast(std::round(*loop_point_seconds * 1000.f)); +} + +boolean I_SetSongPosition(UINT32 position) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + music_player->seek(position / 1000.f); + return true; +} + +UINT32 I_GetSongPosition(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional position_seconds = music_player->position_seconds(); + + if (!position_seconds) + return 0; + + return static_cast(std::round(*position_seconds * 1000.f)); +} + +void I_UpdateSongLagThreshold(void) { +} + +void I_UpdateSongLagConditions(void) { +} + +/// ------------------------ +// MUSIC PLAYBACK +/// ------------------------ + +namespace { +void print_walk_ex_stack(const std::exception& ex) { + CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what()); + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + print_walk_ex_stack(ex); + } +} + +void print_ex(const std::exception& ex) { + CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what()); + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + print_walk_ex_stack(ex); + } +} +} // namespace + +boolean I_LoadSong(char* data, size_t len) { + if (!music_player) + return false; + + tcb::span data_span(reinterpret_cast(data), len); + audio::MusicPlayer new_player; + try { + new_player = audio::MusicPlayer {data_span}; + } catch (const std::exception& ex) { + print_ex(ex); + return false; + } + + if (music_fade_callback && music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback)(); + } + + SdlAudioLockHandle _; + + try { + *music_player = std::move(new_player); + } catch (const std::exception& ex) { + print_ex(ex); + return false; + } + + return true; +} + +void I_UnloadSong(void) { + if (!music_player) + return; + + if (music_fade_callback && music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback)(); + } + + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +boolean I_PlaySong(boolean looping) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + music_player->play(looping); + + return true; +} + +void I_StopSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop(); +} + +void I_PauseSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->pause(); +} + +void I_ResumeSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->unpause(); +} + +void I_SetMusicVolume(int volume) { + float vol = static_cast(volume) / 100.f; + + if (gain_music) { + gain_music->gain(vol * vol * vol); + } +} + +boolean I_SetSongTrack(int track) { + (void) track; + return false; +} + +/// ------------------------ +// MUSIC FADING +/// ------------------------ + +void I_SetInternalMusicVolume(UINT8 volume) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + float gain = volume / 100.f; + music_player->internal_gain(gain); +} + +void I_StopFadingSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop_fade(); +} + +boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + float source_gain = source_volume / 100.f; + float target_gain = target_volume / 100.f; + float seconds = ms / 1000.f; + + music_player->fade_from_to(source_gain, target_gain, seconds); + + if (music_fade_callback) + music_fade_callback(); + music_fade_callback = callback; + + return true; +} + +boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + float target_gain = target_volume / 100.f; + float seconds = ms / 1000.f; + + music_player->fade_to(target_gain, seconds); + + if (music_fade_callback) + music_fade_callback(); + music_fade_callback = callback; + + return true; +} + +static void stop_song_cb(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop(); +} + +boolean I_FadeOutStopSong(UINT32 ms) { + return I_FadeSong(0.f, ms, stop_song_cb); +} + +boolean I_FadeInPlaySong(UINT32 ms, boolean looping) { + if (I_PlaySong(looping)) + return I_FadeSongFromVolume(100, 0, ms, nullptr); + else + return false; +} diff --git a/src/st_stuff.c b/src/st_stuff.c index 5c2def212..3f7e793eb 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -597,7 +597,7 @@ void ST_runTitleCard(void) // SRB2KART // side Zig-Zag positions... - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // Handle name info... if (bossinfo.enemyname) @@ -792,7 +792,7 @@ void ST_drawTitleCard(void) if (lt_ticker < TTANIMSTART) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // WARNING! // https://twitter.com/matthewseiji/status/1485003284196716544 @@ -802,7 +802,7 @@ void ST_drawTitleCard(void) #define HITIME 15 patch_t *localwarn = (encoremode ? twarn2 : twarn); INT32 transp = (lt_ticker+HITIME) % (LOTIME+HITIME); - boolean encorehack = (encoremode && lt_ticker <= PRELEVELTIME+4); + boolean encorehack = ((levelfadecol == 0) && lt_ticker <= PRELEVELTIME+4); if ((localwarn->width > 0) && (lt_ticker + (HITIME-transp) <= lt_endtime)) { @@ -1253,15 +1253,15 @@ void ST_Drawer(void) switch (demo.savemode) { case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "(B) or (X): Save replay"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "(B) or (X): Save replay"); break; case DSM_WILLAUTOSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved. (Look Backward: Change title)"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved. (Look Backward: Change title)"); break; case DSM_WILLSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved."); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved."); break; case DSM_TITLEENTRY: diff --git a/src/typedef.h b/src/typedef.h index 23336dbf5..1ca433cb9 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -111,6 +111,7 @@ TYPEDEF (textpage_t); TYPEDEF (textprompt_t); TYPEDEF (mappoint_t); TYPEDEF (customoption_t); +TYPEDEF (gametype_t); TYPEDEF (mapheader_t); TYPEDEF (tolinfo_t); TYPEDEF (cupheader_t); diff --git a/src/v_video.c b/src/v_video.c index d65b34cea..e633b4e3f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -585,16 +585,16 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du } } - if (options & V_SLIDEIN) + if ((options & V_SLIDEIN)) { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true || G_IsTitleCardAvailable() == false) { - if (leveltime <= 3) + if (leveltime <= 16) timer = 0; else - timer = leveltime-3; + timer = leveltime-16; } if (timer < length) diff --git a/src/w_wad.c b/src/w_wad.c index cef5615cc..ad630a6fb 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1309,12 +1309,12 @@ lumpnum_t W_CheckNumForLongName(const char *name) // Look for valid map data through all added files in descendant order. // Get a map marker for WADs, and a standalone WAD file lump inside PK3s. -lumpnum_t W_CheckNumForMap(const char *name) +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst) { lumpnum_t check = INT16_MAX; UINT32 uhash, hash = quickncasehash(name, LUMPNUMCACHENAME); INT32 i; - UINT16 firstfile = (partadd_earliestfile == UINT16_MAX) ? 0 : partadd_earliestfile; + UINT16 firstfile = (checktofirst || (partadd_earliestfile == UINT16_MAX)) ? 0 : partadd_earliestfile; // Check the lumpnumcache first. Loop backwards so that we check // most recent entries first diff --git a/src/w_wad.h b/src/w_wad.h index 24c0ac74b..31ca26e69 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -171,7 +171,7 @@ UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump); -lumpnum_t W_CheckNumForMap(const char *name); +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst); lumpnum_t W_CheckNumForName(const char *name); lumpnum_t W_CheckNumForLongName(const char *name); lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR diff --git a/src/y_inter.c b/src/y_inter.c index 8cb21fe04..902966ca4 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -98,7 +98,6 @@ static INT32 endtic = -1; static INT32 sorttic = -1; intertype_t intertype = int_none; -intertype_t intermissiontypes[NUMGAMETYPES]; static huddrawlist_h luahuddrawlist_intermission; @@ -210,7 +209,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) else { // set up the levelstring - if (bossinfo.boss == true && bossinfo.enemyname) + if (bossinfo.valid == true && bossinfo.enemyname) { snprintf(data.levelstring, sizeof data.levelstring, @@ -335,7 +334,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) // void Y_IntermissionDrawer(void) { - INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = V_YELLOWMAP; // fallback + INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = highlightflags; if (intertype == int_none || rendermode == render_none) return; @@ -358,11 +357,6 @@ void Y_IntermissionDrawer(void) if (!r_splitscreen) whiteplayer = demo.playback ? displayplayers[0] : consoleplayer; - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((intertype == int_race) ? V_SKYMAP : V_REDMAP); - if (sorttic != -1 && intertic > sorttic) { INT32 count = (intertic - sorttic); @@ -375,7 +369,7 @@ void Y_IntermissionDrawer(void) x += (((16 - count) * vid.width) / (8 * vid.dupx)); } - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { #define NUMFORNEWCOLUMN 8 INT32 y = 41, gutter = ((data.numplayers > NUMFORNEWCOLUMN) ? 0 : (BASEVIDWIDTH/2)); @@ -398,7 +392,7 @@ void Y_IntermissionDrawer(void) { switch (intertype) { - case int_battle: + case int_score: timeheader = "SCORE"; break; default: @@ -533,7 +527,7 @@ void Y_IntermissionDrawer(void) V_DrawRightAlignedThinString(x+152+gutter, y-1, (data.numplayers > NUMFORNEWCOLUMN ? V_6WIDTHSPACE : 0), "NO CONTEST."); else { - if (intertype == int_race || intertype == int_battletime) + if (intertype == int_time) { snprintf(strtime, sizeof strtime, "%i'%02i\"%02i", G_TicsToMinutes(data.val[i], true), G_TicsToSeconds(data.val[i]), G_TicsToCentiseconds(data.val[i])); @@ -575,7 +569,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking) + if (timer && grandprixinfo.gp == false && !modeattacking) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -669,7 +663,7 @@ void Y_Ticker(void) if (intertic < TICRATE || intertic & 1 || endtic != -1) return; - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) @@ -751,29 +745,28 @@ void Y_Ticker(void) // void Y_DetermineIntermissionType(void) { - // set to int_none initially - intertype = int_none; - - if (gametype == GT_RACE) - intertype = int_race; - else if (gametype == GT_BATTLE) + // no intermission for GP events + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) { - if (grandprixinfo.gp == true && bossinfo.boss == false) - intertype = int_none; - else - { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - intertype = (nump < 2 ? int_battletime : int_battle); - } + intertype = int_none; + return; + } + + // set initially + intertype = gametypes[gametype]->intermission; + + // special cases + if (intertype == int_scoreortimeattack) + { + UINT8 i = 0, nump = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + nump++; + } + intertype = (nump < 2 ? int_time : int_score); } - else //if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; } // @@ -830,9 +823,6 @@ void Y_StartIntermission(void) sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); } - if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; - // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. //if (dedicated) return; @@ -841,23 +831,18 @@ void Y_StartIntermission(void) if (prevmap >= nummapheaders || !mapheaderinfo[prevmap]) I_Error("Y_StartIntermission: Internal map ID %d not found (nummapheaders = %d)", prevmap, nummapheaders); + if (!(gametyperules & GTR_CIRCUIT) && (timer > 1)) + S_ChangeMusicInternal("racent", true); // loop it + switch (intertype) { - case int_battle: - case int_battletime: + case int_score: { - if (timer > 1) - S_ChangeMusicInternal("racent", true); // loop it - // Calculate who won - if (intertype == int_battle) - { - Y_CalculateMatchData(0, Y_CompareScore); - break; - } + Y_CalculateMatchData(0, Y_CompareScore); + break; } - // FALLTHRU - case int_race: + case int_time: { // Calculate who won Y_CalculateMatchData(0, Y_CompareTime); @@ -1224,12 +1209,8 @@ void Y_VoteDrawer(void) if (timer) { - INT32 hilicol, tickdown = (timer+1)/TICRATE; - if (gametype == GT_RACE) - hilicol = V_SKYMAP; - else //if (gametype == GT_BATTLE) - hilicol = V_REDMAP; - V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, + INT32 tickdown = (timer+1)/TICRATE; + V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP, va("Vote ends in %d", tickdown)); } } @@ -1569,9 +1550,9 @@ void Y_StartVote(void) levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; // set up the gtc and gts - levelinfo[i].gtc = G_GetGametypeColor(votelevels[i][1]); + levelinfo[i].gtc = 73; // yellowmap[0] -- TODO rewrite vote screen if (i == 2 && votelevels[i][1] != votelevels[0][1]) - levelinfo[i].gts = Gametype_Names[votelevels[i][1]]; + levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else levelinfo[i].gts = NULL; } diff --git a/src/y_inter.h b/src/y_inter.h index f7a0960e8..4b73bf560 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -33,13 +33,12 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level); typedef enum { int_none, - int_race, // Race - int_battle, // Battle (score-based) - int_battletime, // Battle (time-based) + int_time, // Always time + int_score, // Always score + int_scoreortimeattack, // Score unless 1P } intertype_t; extern intertype_t intertype; -extern intertype_t intermissiontypes[NUMGAMETYPES]; #ifdef __cplusplus } // extern "C" diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index df264bdb5..c31748cc0 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -9,601 +9,17 @@ else() set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON) endif() - if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME SDL2 - VERSION 2.24.2 - URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_TEST OFF" - "SDL2_DISABLE_SDL2MAIN ON" - "SDL2_DISABLE_INSTALL ON" - ) +include("cpm-sdl2.cmake") + include("cpm-zlib.cmake") + include("cpm-png.cmake") + include("cpm-curl.cmake") + include("cpm-libgme.cmake") endif() -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME SDL2_mixer - VERSION 2.6.2 - URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL2MIXER_INSTALL OFF" - "SDL2MIXER_DEPS_SHARED OFF" - "SDL2MIXER_SAMPLES OFF" - "SDL2MIXER_VENDORED ON" - "SDL2MIXER_FLAC ON" - "SDL2MIXER_FLAC_LIBFLAC OFF" - "SDL2MIXER_FLAC_DRFLAC ON" - "SDL2MIXER_MOD OFF" - "SDL2MIXER_MP3 ON" - "SDL2MIXER_MP3_DRMP3 ON" - "SDL2MIXER_MIDI ON" - "SDL2MIXER_OPUS OFF" - "SDL2MIXER_VORBIS STB" - "SDL2MIXER_WAVE ON" - ) -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME ZLIB - VERSION 1.2.13 - URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip" - EXCLUDE_FROM_ALL - DOWNLOAD_ONLY YES - ) - if(ZLIB_ADDED) - set(ZLIB_SRCS - crc32.h - deflate.h - gzguts.h - inffast.h - inffixed.h - inflate.h - inftrees.h - trees.h - zutil.h - - adler32.c - compress.c - crc32.c - deflate.c - gzclose.c - gzlib.c - gzread.c - gzwrite.c - inflate.c - infback.c - inftrees.c - inffast.c - trees.c - uncompr.c - zutil.c - ) - list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/") - - configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY) - configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY) - configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY) - - add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS}) - set_target_properties(ZLIB PROPERTIES - VERSION 1.2.13 - OUTPUT_NAME "z" - ) - target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}") - target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include") - if(MSVC) - target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) - endif() - add_library(ZLIB::ZLIB ALIAS ZLIB) - endif() -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME png - VERSION 1.6.38 - URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip" - # png cmake build is broken on msys/mingw32 - DOWNLOAD_ONLY YES - ) - - if(png_ADDED) - # Since png's cmake build is broken, we're going to create a target manually - set( - PNG_SOURCES - - png.h - pngconf.h - - pngpriv.h - pngdebug.h - pnginfo.h - pngstruct.h - - png.c - pngerror.c - pngget.c - pngmem.c - pngpread.c - pngread.c - pngrio.c - pngrtran.c - pngrutil.c - pngset.c - pngtrans.c - pngwio.c - pngwrite.c - pngwtran.c - pngwutil.c - ) - list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/") - - add_custom_command( - OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h" - COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include" - DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" - VERBATIM - ) - add_custom_command( - OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h" - COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h" - DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" - VERBATIM - ) - list( - APPEND PNG_SOURCES - "${png_BINARY_DIR}/include/png.h" - "${png_BINARY_DIR}/include/pngconf.h" - "${png_BINARY_DIR}/include/pnglibconf.h" - ) - add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES}) - - # Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason - target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0) - - # The png includes need to be available to consumers - target_include_directories(png PUBLIC "${png_BINARY_DIR}/include") - - # ... and these also need to be present only for png build - target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}") - target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}") - target_include_directories(png PRIVATE "${png_BINARY_DIR}") - - target_link_libraries(png PRIVATE ZLIB::ZLIB) - add_library(PNG::PNG ALIAS png) - endif() -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - set( - internal_curl_options - - "BUILD_CURL_EXE OFF" - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "CURL_DISABLE_TESTS ON" - "HTTP_ONLY ON" - "CURL_DISABLE_CRYPTO_AUTH ON" - "CURL_DISABLE_NTLM ON" - "ENABLE_MANUAL OFF" - "ENABLE_THREADED_RESOLVER OFF" - "CURL_USE_LIBPSL OFF" - "CURL_USE_LIBSSH2 OFF" - "USE_LIBIDN2 OFF" - "CURL_ENABLE_EXPORT_TARGET OFF" - ) - if(${CMAKE_SYSTEM} MATCHES Windows) - list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") - list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON") - endif() - if(${CMAKE_SYSTEM} MATCHES Darwin) - list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") - list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON") - endif() - if(${CMAKE_SYSTEM} MATCHES Linux) - list(APPEND internal_curl_options "CURL_USE_OPENSSL ON") - endif() - - CPMAddPackage( - NAME curl - VERSION 7.86.0 - URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip" - EXCLUDE_FROM_ALL ON - OPTIONS ${internal_curl_options} - ) -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME openmpt - VERSION 0.4.30 - URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip" - DOWNLOAD_ONLY ON - ) - - if(openmpt_ADDED) - set( - openmpt_SOURCES - - # minimp3 - # -DMPT_WITH_MINIMP3 - include/minimp3/minimp3.c - - common/mptStringParse.cpp - common/mptLibrary.cpp - common/Logging.cpp - common/Profiler.cpp - common/version.cpp - common/mptCPU.cpp - common/ComponentManager.cpp - common/mptOS.cpp - common/serialization_utils.cpp - common/mptStringFormat.cpp - common/FileReader.cpp - common/mptWine.cpp - common/mptPathString.cpp - common/mptAlloc.cpp - common/mptUUID.cpp - common/mptTime.cpp - common/mptString.cpp - common/mptFileIO.cpp - common/mptStringBuffer.cpp - common/mptRandom.cpp - common/mptIO.cpp - common/misc_util.cpp - - common/mptCRC.h - common/mptLibrary.h - common/mptIO.h - common/version.h - common/stdafx.h - common/ComponentManager.h - common/Endianness.h - common/mptStringFormat.h - common/mptMutex.h - common/mptUUID.h - common/mptExceptionText.h - common/BuildSettings.h - common/mptAlloc.h - common/mptTime.h - common/FileReaderFwd.h - common/Logging.h - common/mptException.h - common/mptWine.h - common/mptStringBuffer.h - common/misc_util.h - common/mptBaseMacros.h - common/mptMemory.h - common/mptFileIO.h - common/serialization_utils.h - common/mptSpan.h - common/mptThread.h - common/FlagSet.h - common/mptString.h - common/mptStringParse.h - common/mptBaseUtils.h - common/mptRandom.h - common/CompilerDetect.h - common/FileReader.h - common/mptAssert.h - common/mptPathString.h - common/Profiler.h - common/mptOS.h - common/mptBaseTypes.h - common/mptCPU.h - common/mptBufferIO.h - common/versionNumber.h - - soundlib/WAVTools.cpp - soundlib/ITTools.cpp - soundlib/AudioCriticalSection.cpp - soundlib/Load_stm.cpp - soundlib/MixerLoops.cpp - soundlib/Load_dbm.cpp - soundlib/ModChannel.cpp - soundlib/Load_gdm.cpp - soundlib/Snd_fx.cpp - soundlib/Load_mid.cpp - soundlib/mod_specifications.cpp - soundlib/Snd_flt.cpp - soundlib/Load_psm.cpp - soundlib/Load_far.cpp - soundlib/patternContainer.cpp - soundlib/Load_med.cpp - soundlib/Load_dmf.cpp - soundlib/Paula.cpp - soundlib/modcommand.cpp - soundlib/Message.cpp - soundlib/SoundFilePlayConfig.cpp - soundlib/Load_uax.cpp - soundlib/plugins/PlugInterface.cpp - soundlib/plugins/LFOPlugin.cpp - soundlib/plugins/PluginManager.cpp - soundlib/plugins/DigiBoosterEcho.cpp - soundlib/plugins/dmo/DMOPlugin.cpp - soundlib/plugins/dmo/Flanger.cpp - soundlib/plugins/dmo/Distortion.cpp - soundlib/plugins/dmo/ParamEq.cpp - soundlib/plugins/dmo/Gargle.cpp - soundlib/plugins/dmo/I3DL2Reverb.cpp - soundlib/plugins/dmo/Compressor.cpp - soundlib/plugins/dmo/WavesReverb.cpp - soundlib/plugins/dmo/Echo.cpp - soundlib/plugins/dmo/Chorus.cpp - soundlib/Load_ams.cpp - soundlib/tuningbase.cpp - soundlib/ContainerUMX.cpp - soundlib/Load_ptm.cpp - soundlib/ContainerXPK.cpp - soundlib/SampleFormatMP3.cpp - soundlib/tuning.cpp - soundlib/Sndfile.cpp - soundlib/ContainerMMCMP.cpp - soundlib/Load_amf.cpp - soundlib/Load_669.cpp - soundlib/modsmp_ctrl.cpp - soundlib/Load_mtm.cpp - soundlib/OggStream.cpp - soundlib/Load_plm.cpp - soundlib/Tables.cpp - soundlib/Load_c67.cpp - soundlib/Load_mod.cpp - soundlib/Load_sfx.cpp - soundlib/Sndmix.cpp - soundlib/load_j2b.cpp - soundlib/ModSequence.cpp - soundlib/SampleFormatFLAC.cpp - soundlib/ModInstrument.cpp - soundlib/Load_mo3.cpp - soundlib/ModSample.cpp - soundlib/Dlsbank.cpp - soundlib/Load_itp.cpp - soundlib/UpgradeModule.cpp - soundlib/MIDIMacros.cpp - soundlib/ContainerPP20.cpp - soundlib/RowVisitor.cpp - soundlib/Load_imf.cpp - soundlib/SampleFormatVorbis.cpp - soundlib/Load_dsm.cpp - soundlib/Load_mt2.cpp - soundlib/MixerSettings.cpp - soundlib/S3MTools.cpp - soundlib/Load_xm.cpp - soundlib/MIDIEvents.cpp - soundlib/pattern.cpp - soundlib/Load_digi.cpp - soundlib/Load_s3m.cpp - soundlib/tuningCollection.cpp - soundlib/SampleIO.cpp - soundlib/Dither.cpp - soundlib/Load_mdl.cpp - soundlib/OPL.cpp - soundlib/WindowedFIR.cpp - soundlib/SampleFormats.cpp - soundlib/Load_wav.cpp - soundlib/Load_it.cpp - soundlib/UMXTools.cpp - soundlib/Load_stp.cpp - soundlib/Load_okt.cpp - soundlib/Load_ult.cpp - soundlib/MixFuncTable.cpp - soundlib/SampleFormatOpus.cpp - soundlib/Fastmix.cpp - soundlib/Tagging.cpp - soundlib/ITCompression.cpp - soundlib/Load_dtm.cpp - soundlib/MPEGFrame.cpp - soundlib/XMTools.cpp - soundlib/SampleFormatMediaFoundation.cpp - soundlib/InstrumentExtensions.cpp - - soundlib/MixerInterface.h - soundlib/SoundFilePlayConfig.h - soundlib/ModSample.h - soundlib/MIDIEvents.h - soundlib/ModSampleCopy.h - soundlib/patternContainer.h - soundlib/ChunkReader.h - soundlib/ITCompression.h - soundlib/Dither.h - soundlib/S3MTools.h - soundlib/MPEGFrame.h - soundlib/WAVTools.h - soundlib/mod_specifications.h - soundlib/ITTools.h - soundlib/RowVisitor.h - soundlib/plugins/PluginMixBuffer.h - soundlib/plugins/PluginStructs.h - soundlib/plugins/LFOPlugin.h - soundlib/plugins/PlugInterface.h - soundlib/plugins/DigiBoosterEcho.h - soundlib/plugins/OpCodes.h - soundlib/plugins/dmo/Echo.h - soundlib/plugins/dmo/I3DL2Reverb.h - soundlib/plugins/dmo/WavesReverb.h - soundlib/plugins/dmo/ParamEq.h - soundlib/plugins/dmo/Gargle.h - soundlib/plugins/dmo/DMOPlugin.h - soundlib/plugins/dmo/Chorus.h - soundlib/plugins/dmo/Compressor.h - soundlib/plugins/dmo/Distortion.h - soundlib/plugins/dmo/Flanger.h - soundlib/plugins/PluginManager.h - soundlib/SampleIO.h - soundlib/Container.h - soundlib/ModSequence.h - soundlib/UMXTools.h - soundlib/Message.h - soundlib/modcommand.h - soundlib/XMTools.h - soundlib/Snd_defs.h - soundlib/MixFuncTable.h - soundlib/pattern.h - soundlib/modsmp_ctrl.h - soundlib/Tagging.h - soundlib/tuningcollection.h - soundlib/Mixer.h - soundlib/FloatMixer.h - soundlib/AudioCriticalSection.h - soundlib/Tables.h - soundlib/tuningbase.h - soundlib/WindowedFIR.h - soundlib/Sndfile.h - soundlib/Paula.h - soundlib/ModInstrument.h - soundlib/Dlsbank.h - soundlib/IntMixer.h - soundlib/OPL.h - soundlib/Resampler.h - soundlib/ModChannel.h - soundlib/MixerSettings.h - soundlib/AudioReadTarget.h - soundlib/MixerLoops.h - soundlib/tuning.h - soundlib/MIDIMacros.h - soundlib/OggStream.h - soundlib/Loaders.h - soundlib/BitReader.h - soundlib/opal.h - - sounddsp/AGC.cpp - sounddsp/EQ.cpp - sounddsp/DSP.cpp - sounddsp/Reverb.cpp - sounddsp/Reverb.h - sounddsp/EQ.h - sounddsp/DSP.h - sounddsp/AGC.h - - libopenmpt/libopenmpt_c.cpp - libopenmpt/libopenmpt_cxx.cpp - libopenmpt/libopenmpt_impl.cpp - libopenmpt/libopenmpt_ext_impl.cpp - ) - list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/") - - # -DLIBOPENMPT_BUILD - configure_file("openmpt_svn_version.h" "svn_version.h") - add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h) - if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang) - target_compile_options(openmpt PRIVATE "-g0") - endif() - if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC) - target_link_libraries(openmpt PRIVATE Rpcrt4) - endif() - target_compile_features(openmpt PRIVATE cxx_std_11) - target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD) - - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}") - target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") - - # I wish this wasn't necessary, but it is - target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}") - endif() -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME libgme - VERSION 0.6.3 - URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "ENABLE_UBSAN OFF" - "GME_YM2612_EMU MAME" - ) - target_compile_features(gme PRIVATE cxx_std_11) - target_link_libraries(gme PRIVATE ZLIB::ZLIB) -endif() - -CPMAddPackage( - NAME RapidJSON - VERSION 1.1.0 - URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz" - EXCLUDE_FROM_ALL ON - DOWNLOAD_ONLY ON -) -if(RapidJSON_ADDED) - add_library(RapidJSON INTERFACE) - add_library(RapidJSON::RapidJSON ALIAS RapidJSON) - target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include") -endif() - -CPMAddPackage( - NAME DiscordRPC - VERSION 3.4.0 - URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip" - EXCLUDE_FROM_ALL ON - DOWNLOAD_ONLY ON -) - -if(DiscordRPC_ADDED) - set(DiscordRPC_SOURCES - include/discord_rpc.h - include/discord_register.h - - src/discord_rpc.cpp - src/rpc_connection.h - src/rpc_connection.cpp - src/serialization.h - src/serialization.cpp - src/connection.h - src/backoff.h - src/msg_queue.h - ) - list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/") - - # Discord RPC is always statically linked because it's tiny. - add_library(discord-rpc STATIC ${DiscordRPC_SOURCES}) - add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc) - - target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include") - target_compile_features(discord-rpc PUBLIC cxx_std_11) - target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON) - - # Platform-specific connection and register impls - if(WIN32) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp" - "${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp" - ) - target_link_libraries(discord-rpc PRIVATE psapi advapi32) - endif() - - if(UNIX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp" - ) - - if(APPLE) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m" - ) - target_link_libraries(discord-rpc PUBLIC "-framework AppKit") - endif() - - if(UNIX AND NOT APPLE) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp" - ) - endif() - endif() -endif() +include("cpm-rapidjson.cmake") +include("cpm-discordrpc.cmake") +include("cpm-xmp-lite.cmake") if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") CPMAddPackage( @@ -686,3 +102,4 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") endif() add_subdirectory(tcbrindle_span) +add_subdirectory(stb_vorbis) diff --git a/thirdparty/cpm-curl.cmake b/thirdparty/cpm-curl.cmake new file mode 100644 index 000000000..3d8c6e61d --- /dev/null +++ b/thirdparty/cpm-curl.cmake @@ -0,0 +1,35 @@ +set( + internal_curl_options + + "BUILD_CURL_EXE OFF" + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "CURL_DISABLE_TESTS ON" + "HTTP_ONLY ON" + "CURL_DISABLE_CRYPTO_AUTH ON" + "CURL_DISABLE_NTLM ON" + "ENABLE_MANUAL OFF" + "ENABLE_THREADED_RESOLVER OFF" + "CURL_USE_LIBPSL OFF" + "CURL_USE_LIBSSH2 OFF" + "USE_LIBIDN2 OFF" + "CURL_ENABLE_EXPORT_TARGET OFF" +) +if(${CMAKE_SYSTEM} MATCHES Windows) + list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") + list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON") +endif() +if(${CMAKE_SYSTEM} MATCHES Darwin) + list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") + list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON") +endif() +if(${CMAKE_SYSTEM} MATCHES Linux) + list(APPEND internal_curl_options "CURL_USE_OPENSSL ON") +endif() + +CPMAddPackage( + NAME curl + VERSION 7.86.0 + URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip" + EXCLUDE_FROM_ALL ON + OPTIONS ${internal_curl_options} +) diff --git a/thirdparty/cpm-discordrpc.cmake b/thirdparty/cpm-discordrpc.cmake new file mode 100644 index 000000000..078ebaab5 --- /dev/null +++ b/thirdparty/cpm-discordrpc.cmake @@ -0,0 +1,63 @@ +CPMAddPackage( + NAME DiscordRPC + VERSION 3.4.0 + URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(DiscordRPC_ADDED) + set(DiscordRPC_SOURCES + include/discord_rpc.h + include/discord_register.h + + src/discord_rpc.cpp + src/rpc_connection.h + src/rpc_connection.cpp + src/serialization.h + src/serialization.cpp + src/connection.h + src/backoff.h + src/msg_queue.h + ) + list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/") + + # Discord RPC is always statically linked because it's tiny. + add_library(discord-rpc STATIC ${DiscordRPC_SOURCES}) + add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc) + + target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include") + target_compile_features(discord-rpc PUBLIC cxx_std_11) + target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON) + + # Platform-specific connection and register impls + if(WIN32) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp" + "${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp" + ) + target_link_libraries(discord-rpc PRIVATE psapi advapi32) + endif() + + if(UNIX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp" + ) + + if(APPLE) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m" + ) + target_link_libraries(discord-rpc PUBLIC "-framework AppKit") + endif() + + if(UNIX AND NOT APPLE) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp" + ) + endif() + endif() +endif() diff --git a/thirdparty/cpm-libgme.cmake b/thirdparty/cpm-libgme.cmake new file mode 100644 index 000000000..1786ad0ec --- /dev/null +++ b/thirdparty/cpm-libgme.cmake @@ -0,0 +1,15 @@ +CPMAddPackage( + NAME libgme + VERSION 0.6.3 + URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip" + EXCLUDE_FROM_ALL ON + OPTIONS + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "ENABLE_UBSAN OFF" + "GME_YM2612_EMU MAME" +) + +if(libgme_ADDED) + target_compile_features(gme PRIVATE cxx_std_11) + target_link_libraries(gme PRIVATE ZLIB::ZLIB) +endif() diff --git a/thirdparty/cpm-png.cmake b/thirdparty/cpm-png.cmake new file mode 100644 index 000000000..716ff27b8 --- /dev/null +++ b/thirdparty/cpm-png.cmake @@ -0,0 +1,73 @@ +CPMAddPackage( + NAME png + VERSION 1.6.38 + URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip" + # png cmake build is broken on msys/mingw32 + DOWNLOAD_ONLY YES +) + +if(png_ADDED) + # Since png's cmake build is broken, we're going to create a target manually + set( + PNG_SOURCES + + png.h + pngconf.h + + pngpriv.h + pngdebug.h + pnginfo.h + pngstruct.h + + png.c + pngerror.c + pngget.c + pngmem.c + pngpread.c + pngread.c + pngrio.c + pngrtran.c + pngrutil.c + pngset.c + pngtrans.c + pngwio.c + pngwrite.c + pngwtran.c + pngwutil.c + ) + list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/") + + add_custom_command( + OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h" + COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include" + DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" + VERBATIM + ) + add_custom_command( + OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h" + COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h" + DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" + VERBATIM + ) + list( + APPEND PNG_SOURCES + "${png_BINARY_DIR}/include/png.h" + "${png_BINARY_DIR}/include/pngconf.h" + "${png_BINARY_DIR}/include/pnglibconf.h" + ) + add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES}) + + # Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason + target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0) + + # The png includes need to be available to consumers + target_include_directories(png PUBLIC "${png_BINARY_DIR}/include") + + # ... and these also need to be present only for png build + target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}") + target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}") + target_include_directories(png PRIVATE "${png_BINARY_DIR}") + + target_link_libraries(png PRIVATE ZLIB::ZLIB) + add_library(PNG::PNG ALIAS png) +endif() diff --git a/thirdparty/cpm-rapidjson.cmake b/thirdparty/cpm-rapidjson.cmake new file mode 100644 index 000000000..2836625ed --- /dev/null +++ b/thirdparty/cpm-rapidjson.cmake @@ -0,0 +1,13 @@ +CPMAddPackage( + NAME RapidJSON + VERSION 1.1.0 + URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(RapidJSON_ADDED) + add_library(RapidJSON INTERFACE) + add_library(RapidJSON::RapidJSON ALIAS RapidJSON) + target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include") +endif() diff --git a/thirdparty/cpm-sdl2.cmake b/thirdparty/cpm-sdl2.cmake new file mode 100644 index 000000000..58cf9afc2 --- /dev/null +++ b/thirdparty/cpm-sdl2.cmake @@ -0,0 +1,13 @@ +CPMAddPackage( + NAME SDL2 + VERSION 2.24.2 + URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip" + EXCLUDE_FROM_ALL ON + OPTIONS + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_TEST OFF" + "SDL2_DISABLE_SDL2MAIN ON" + "SDL2_DISABLE_INSTALL ON" +) diff --git a/thirdparty/cpm-xmp-lite.cmake b/thirdparty/cpm-xmp-lite.cmake new file mode 100644 index 000000000..21a721e0a --- /dev/null +++ b/thirdparty/cpm-xmp-lite.cmake @@ -0,0 +1,60 @@ +CPMAddPackage( + NAME xmp-lite + VERSION 4.5.0 + URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(xmp-lite_ADDED) + set(xmp_sources + virtual.c + format.c + period.c + player.c + read_event.c + misc.c + dataio.c + lfo.c + scan.c + control.c + filter.c + effects.c + mixer.c + mix_all.c + load_helpers.c + load.c + hio.c + smix.c + memio.c + win32.c + + loaders/common.c + loaders/itsex.c + loaders/sample.c + loaders/xm_load.c + loaders/mod_load.c + loaders/s3m_load.c + loaders/it_load.c + ) + list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/") + + add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources}) + + target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS) + if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC") + if(WIN32) + # BUILDING_STATIC has to be public to work around a bug in xmp.h + # which adds __declspec(dllimport) even when statically linking + target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC) + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC) + endif() + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL) + endif() + target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src") + target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite") + + add_library(xmp-lite::xmp-lite ALIAS xmp-lite) +endif() diff --git a/thirdparty/cpm-zlib.cmake b/thirdparty/cpm-zlib.cmake new file mode 100644 index 000000000..aee090083 --- /dev/null +++ b/thirdparty/cpm-zlib.cmake @@ -0,0 +1,54 @@ +CPMAddPackage( + NAME ZLIB + VERSION 1.2.13 + URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip" + EXCLUDE_FROM_ALL + DOWNLOAD_ONLY YES +) + +if(ZLIB_ADDED) + set(ZLIB_SRCS + crc32.h + deflate.h + gzguts.h + inffast.h + inffixed.h + inflate.h + inftrees.h + trees.h + zutil.h + + adler32.c + compress.c + crc32.c + deflate.c + gzclose.c + gzlib.c + gzread.c + gzwrite.c + inflate.c + infback.c + inftrees.c + inffast.c + trees.c + uncompr.c + zutil.c + ) + list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/") + + configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY) + configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY) + configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY) + + add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS}) + set_target_properties(ZLIB PROPERTIES + VERSION 1.2.13 + OUTPUT_NAME "z" + ) + target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}") + target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include") + if(MSVC) + target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) + endif() + add_library(ZLIB::ZLIB ALIAS ZLIB) +endif() diff --git a/thirdparty/openmpt_svn_version.h b/thirdparty/openmpt_svn_version.h deleted file mode 100644 index a45ed9f22..000000000 --- a/thirdparty/openmpt_svn_version.h +++ /dev/null @@ -1,10 +0,0 @@ - -#pragma once -#define OPENMPT_VERSION_SVNVERSION "17963" -#define OPENMPT_VERSION_REVISION 17963 -#define OPENMPT_VERSION_DIRTY 0 -#define OPENMPT_VERSION_MIXEDREVISIONS 0 -#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.4.32" -#define OPENMPT_VERSION_DATE "2022-09-25T14:19:05.052596Z" -#define OPENMPT_VERSION_IS_PACKAGE 1 - diff --git a/thirdparty/stb_vorbis/CMakeLists.txt b/thirdparty/stb_vorbis/CMakeLists.txt new file mode 100644 index 000000000..3cc4bc5c3 --- /dev/null +++ b/thirdparty/stb_vorbis/CMakeLists.txt @@ -0,0 +1,4 @@ +# Update from https://github.com/nothings/stb +# This doesn't use CPM because stb_vorbis.c has a weird header setup +add_library(stb_vorbis STATIC stb_vorbis.c include/stb_vorbis.h) +target_include_directories(stb_vorbis PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/thirdparty/stb_vorbis/include/stb_vorbis.h b/thirdparty/stb_vorbis/include/stb_vorbis.h new file mode 100644 index 000000000..ef0b4d369 --- /dev/null +++ b/thirdparty/stb_vorbis/include/stb_vorbis.h @@ -0,0 +1,2 @@ +#define STB_VORBIS_HEADER_ONLY +#include "../stb_vorbis.c" diff --git a/thirdparty/stb_vorbis/stb_vorbis.c b/thirdparty/stb_vorbis/stb_vorbis.c new file mode 100644 index 000000000..3e5c2504c --- /dev/null +++ b/thirdparty/stb_vorbis/stb_vorbis.c @@ -0,0 +1,5584 @@ +// Ogg Vorbis audio decoder - v1.22 - public domain +// http://nothings.org/stb_vorbis/ +// +// Original version written by Sean Barrett in 2007. +// +// Originally sponsored by RAD Game Tools. Seeking implementation +// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, +// Elias Software, Aras Pranckevicius, and Sean Barrett. +// +// LICENSE +// +// See end of file for license information. +// +// Limitations: +// +// - floor 0 not supported (used in old ogg vorbis files pre-2004) +// - lossless sample-truncation at beginning ignored +// - cannot concatenate multiple vorbis streams +// - sample positions are 32-bit, limiting seekable 192Khz +// files to around 6 hours (Ogg supports 64-bit) +// +// Feature contributors: +// Dougall Johnson (sample-exact seeking) +// +// Bugfix/warning contributors: +// Terje Mathisen Niklas Frykholm Andy Hill +// Casey Muratori John Bolton Gargaj +// Laurent Gomila Marc LeBlanc Ronny Chevalier +// Bernhard Wodo Evan Balster github:alxprd +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot +// Phillip Bennefall Rohit Thiago Goulart +// github:manxorist Saga Musix github:infatum +// Timur Gagiev Maxwell Koo Peter Waller +// github:audinowho Dougall Johnson David Reid +// github:Clownacy Pedro J. Estebanez Remi Verschelde +// AnthoFoxo github:morlat Gabriel Ravier +// +// Partial history: +// 1.22 - 2021-07-11 - various small fixes +// 1.21 - 2021-07-02 - fix bug for files with no comments +// 1.20 - 2020-07-11 - several small fixes +// 1.19 - 2020-02-05 - warnings +// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. +// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) +// 1.16 - 2019-03-04 - fix warnings +// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files +// 1.11 - 2017-07-23 - fix MinGW compilation +// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) +// some crash fixes when out of memory or with corrupt files +// fix some inappropriately signed shifts +// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant +// 1.04 - 2014-08-27 - fix missing const-correct case in API +// 1.03 - 2014-08-07 - warning fixes +// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// (API change) report sample rate for decode-full-file funcs +// +// See end of file for full version history. + + +////////////////////////////////////////////////////////////////////////////// +// +// HEADER BEGINS HERE +// + +#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define STB_VORBIS_INCLUDE_STB_VORBIS_H + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) +#define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////// THREAD SAFETY + +// Individual stb_vorbis* handles are not thread-safe; you cannot decode from +// them from multiple threads at the same time. However, you can have multiple +// stb_vorbis* handles and decode from them independently in multiple thrads. + + +/////////// MEMORY ALLOCATION + +// normally stb_vorbis uses malloc() to allocate memory at startup, +// and alloca() to allocate temporary memory during a frame on the +// stack. (Memory consumption will depend on the amount of setup +// data in the file and how you set the compile flags for speed +// vs. size. In my test files the maximal-size usage is ~150KB.) +// +// You can modify the wrapper functions in the source (setup_malloc, +// setup_temp_malloc, temp_malloc) to change this behavior, or you +// can use a simpler allocation model: you pass in a buffer from +// which stb_vorbis will allocate _all_ its memory (including the +// temp memory). "open" may fail with a VORBIS_outofmem if you +// do not pass in enough data; there is no way to determine how +// much you do need except to succeed (at which point you can +// query get_info to find the exact amount required. yes I know +// this is lame). +// +// If you pass in a non-NULL buffer of the type below, allocation +// will occur from it as described above. Otherwise just pass NULL +// to use malloc()/alloca() + +typedef struct +{ + char *alloc_buffer; + int alloc_buffer_length_in_bytes; +} stb_vorbis_alloc; + + +/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES + +typedef struct stb_vorbis stb_vorbis; + +typedef struct +{ + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int setup_temp_memory_required; + unsigned int temp_memory_required; + + int max_frame_size; +} stb_vorbis_info; + +typedef struct +{ + char *vendor; + + int comment_list_length; + char **comment_list; +} stb_vorbis_comment; + +// get general information about the file +extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); + +// get ogg comments +extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); + +// get the last error detected (clears it, too) +extern int stb_vorbis_get_error(stb_vorbis *f); + +// close an ogg vorbis file and free all memory in use +extern void stb_vorbis_close(stb_vorbis *f); + +// this function returns the offset (in samples) from the beginning of the +// file that will be returned by the next decode, if it is known, or -1 +// otherwise. after a flush_pushdata() call, this may take a while before +// it becomes valid again. +// NOT WORKING YET after a seek with PULLDATA API +extern int stb_vorbis_get_sample_offset(stb_vorbis *f); + +// returns the current seek point within the file, or offset from the beginning +// of the memory buffer. In pushdata mode it returns 0. +extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); + +/////////// PUSHDATA API + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +// this API allows you to get blocks of data from any source and hand +// them to stb_vorbis. you have to buffer them; stb_vorbis will tell +// you how much it used, and you have to give it the rest next time; +// and stb_vorbis may not have enough data to work with and you will +// need to give it the same data again PLUS more. Note that the Vorbis +// specification does not bound the size of an individual frame. + +extern stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char * datablock, int datablock_length_in_bytes, + int *datablock_memory_consumed_in_bytes, + int *error, + const stb_vorbis_alloc *alloc_buffer); +// create a vorbis decoder by passing in the initial data block containing +// the ogg&vorbis headers (you don't need to do parse them, just provide +// the first N bytes of the file--you're told if it's not enough, see below) +// on success, returns an stb_vorbis *, does not set error, returns the amount of +// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; +// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed +// if returns NULL and *error is VORBIS_need_more_data, then the input block was +// incomplete and you need to pass in a larger block from the start of the file + +extern int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, + const unsigned char *datablock, int datablock_length_in_bytes, + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ); +// decode a frame of audio sample data if possible from the passed-in data block +// +// return value: number of bytes we used from datablock +// +// possible cases: +// 0 bytes used, 0 samples output (need more data) +// N bytes used, 0 samples output (resynching the stream, keep going) +// N bytes used, M samples output (one frame of data) +// note that after opening a file, you will ALWAYS get one N-bytes,0-sample +// frame, because Vorbis always "discards" the first frame. +// +// Note that on resynch, stb_vorbis will rarely consume all of the buffer, +// instead only datablock_length_in_bytes-3 or less. This is because it wants +// to avoid missing parts of a page header if they cross a datablock boundary, +// without writing state-machiney code to record a partial detection. +// +// The number of channels returned are stored in *channels (which can be +// NULL--it is always the same as the number of channels reported by +// get_info). *output will contain an array of float* buffers, one per +// channel. In other words, (*output)[0][0] contains the first sample from +// the first channel, and (*output)[1][0] contains the first sample from +// the second channel. +// +// *output points into stb_vorbis's internal output buffer storage; these +// buffers are owned by stb_vorbis and application code should not free +// them or modify their contents. They are transient and will be overwritten +// once you ask for more data to get decoded, so be sure to grab any data +// you need before then. + +extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +// inform stb_vorbis that your next datablock will not be contiguous with +// previous ones (e.g. you've seeked in the data); future attempts to decode +// frames will cause stb_vorbis to resynchronize (as noted above), and +// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it +// will begin decoding the _next_ frame. +// +// if you want to seek using pushdata, you need to seek in your file, then +// call stb_vorbis_flush_pushdata(), then start calling decoding, then once +// decoding is returning you data, call stb_vorbis_get_sample_offset, and +// if you don't like the result, seek your file again and repeat. +#endif + + +////////// PULLING INPUT API + +#ifndef STB_VORBIS_NO_PULLDATA_API +// This API assumes stb_vorbis is allowed to pull data from a source-- +// either a block of memory containing the _entire_ vorbis stream, or a +// FILE * that you or it create, or possibly some other reading mechanism +// if you go modify the source to replace the FILE * case with some kind +// of callback to your code. (But if you don't support seeking, you may +// just want to go ahead and use pushdata.) + +#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +#endif +#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#endif +// decode an entire file and output the data interleaved into a malloc()ed +// buffer stored in *output. The return value is the number of samples +// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. +// When you're done with it, just free() the pointer returned in *output. + +extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an ogg vorbis stream in memory (note +// this must be the entire stream!). on failure, returns NULL and sets *error + +#ifndef STB_VORBIS_NO_STDIO +extern stb_vorbis * stb_vorbis_open_filename(const char *filename, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from a filename via fopen(). on failure, +// returns NULL and sets *error (possibly to VORBIS_file_open_failure). + +extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell). on failure, returns NULL and sets *error. +// note that stb_vorbis must "own" this stream; if you seek it in between +// calls to stb_vorbis, it will become confused. Moreover, if you attempt to +// perform stb_vorbis_seek_*() operations on this file, it will assume it +// owns the _entire_ rest of the file after the start point. Use the next +// function, stb_vorbis_open_file_section(), to limit it. + +extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell); the stream will be of length 'len' bytes. +// on failure, returns NULL and sets *error. note that stb_vorbis must "own" +// this stream; if you seek it in between calls to stb_vorbis, it will become +// confused. +#endif + +extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +// these functions seek in the Vorbis file to (approximately) 'sample_number'. +// after calling seek_frame(), the next call to get_frame_*() will include +// the specified sample. after calling stb_vorbis_seek(), the next call to +// stb_vorbis_get_samples_* will start with the specified sample. If you +// do not need to seek to EXACTLY the target sample when using get_samples_*, +// you can also use seek_frame(). + +extern int stb_vorbis_seek_start(stb_vorbis *f); +// this function is equivalent to stb_vorbis_seek(f,0) + +extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +// these functions return the total length of the vorbis stream + +extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +// decode the next frame and return the number of samples. the number of +// channels returned are stored in *channels (which can be NULL--it is always +// the same as the number of channels reported by get_info). *output will +// contain an array of float* buffers, one per channel. These outputs will +// be overwritten on the next call to stb_vorbis_get_frame_*. +// +// You generally should not intermix calls to stb_vorbis_get_frame_*() +// and stb_vorbis_get_samples_*(), since the latter calls the former. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +#endif +// decode the next frame and return the number of *samples* per channel. +// Note that for interleaved data, you pass in the number of shorts (the +// size of your array), but the return value is the number of samples per +// channel, not the total number of samples. +// +// The data is coerced to the number of channels you request according to the +// channel coercion rules (see below). You must pass in the size of your +// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. +// The maximum buffer size needed can be gotten from get_info(); however, +// the Vorbis I specification implies an absolute maximum of 4096 samples +// per channel. + +// Channel coercion rules: +// Let M be the number of channels requested, and N the number of channels present, +// and Cn be the nth channel; let stereo L be the sum of all L and center channels, +// and stereo R be the sum of all R and center channels (channel assignment from the +// vorbis spec). +// M N output +// 1 k sum(Ck) for all k +// 2 * stereo L, stereo R +// k l k > l, the first l channels, then 0s +// k l k <= l, the first k channels +// Note that this is not _good_ surround etc. mixing at all! It's just so +// you get something useful. + +extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. +// Returns the number of samples stored per channel; it may be less than requested +// at the end of the file. If there are no more samples in the file, returns 0. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +#endif +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. Applies the coercion rules above +// to produce 'channels' channels. Returns the number of samples stored per channel; +// it may be less than requested at the end of the file. If there are no more +// samples in the file, returns 0. + +#endif + +//////// ERROR CODES + +enum STBVorbisError +{ + VORBIS__no_error, + + VORBIS_need_more_data=1, // not a real error + + VORBIS_invalid_api_mixing, // can't mix API modes + VORBIS_outofmem, // not enough memory + VORBIS_feature_not_supported, // uses floor 0 + VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small + VORBIS_file_open_failure, // fopen() failed + VORBIS_seek_without_length, // can't seek in unknown-length file + + VORBIS_unexpected_eof=10, // file is truncated? + VORBIS_seek_invalid, // seek past EOF + + // decoding errors (corrupt/invalid stream) -- you probably + // don't care about the exact details of these + + // vorbis errors: + VORBIS_invalid_setup=20, + VORBIS_invalid_stream, + + // ogg errors: + VORBIS_missing_capture_pattern=30, + VORBIS_invalid_stream_structure_version, + VORBIS_continued_packet_flag_invalid, + VORBIS_incorrect_stream_serial_number, + VORBIS_invalid_first_page, + VORBIS_bad_packet_type, + VORBIS_cant_find_last_page, + VORBIS_seek_failed, + VORBIS_ogg_skeleton_not_supported +}; + + +#ifdef __cplusplus +} +#endif + +#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H +// +// HEADER ENDS HERE +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef STB_VORBIS_HEADER_ONLY + +// global configuration settings (e.g. set these in the project/makefile), +// or just set them in this file at the top (although ideally the first few +// should be visible when the header file is compiled too, although it's not +// crucial) + +// STB_VORBIS_NO_PUSHDATA_API +// does not compile the code for the various stb_vorbis_*_pushdata() +// functions +// #define STB_VORBIS_NO_PUSHDATA_API + +// STB_VORBIS_NO_PULLDATA_API +// does not compile the code for the non-pushdata APIs +// #define STB_VORBIS_NO_PULLDATA_API + +// STB_VORBIS_NO_STDIO +// does not compile the code for the APIs that use FILE *s internally +// or externally (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_STDIO + +// STB_VORBIS_NO_INTEGER_CONVERSION +// does not compile the code for converting audio sample data from +// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_INTEGER_CONVERSION + +// STB_VORBIS_NO_FAST_SCALED_FLOAT +// does not use a fast float-to-int trick to accelerate float-to-int on +// most platforms which requires endianness be defined correctly. +//#define STB_VORBIS_NO_FAST_SCALED_FLOAT + + +// STB_VORBIS_MAX_CHANNELS [number] +// globally define this to the maximum number of channels you need. +// The spec does not put a restriction on channels except that +// the count is stored in a byte, so 255 is the hard limit. +// Reducing this saves about 16 bytes per value, so using 16 saves +// (255-16)*16 or around 4KB. Plus anything other memory usage +// I forgot to account for. Can probably go as low as 8 (7.1 audio), +// 6 (5.1 audio), or 2 (stereo only). +#ifndef STB_VORBIS_MAX_CHANNELS +#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? +#endif + +// STB_VORBIS_PUSHDATA_CRC_COUNT [number] +// after a flush_pushdata(), stb_vorbis begins scanning for the +// next valid page, without backtracking. when it finds something +// that looks like a page, it streams through it and verifies its +// CRC32. Should that validation fail, it keeps scanning. But it's +// possible that _while_ streaming through to check the CRC32 of +// one candidate page, it sees another candidate page. This #define +// determines how many "overlapping" candidate pages it can search +// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas +// garbage pages could be as big as 64KB, but probably average ~16KB. +// So don't hose ourselves by scanning an apparent 64KB page and +// missing a ton of real ones in the interim; so minimum of 2 +#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT +#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 +#endif + +// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] +// sets the log size of the huffman-acceleration table. Maximum +// supported value is 24. with larger numbers, more decodings are O(1), +// but the table size is larger so worse cache missing, so you'll have +// to probe (and try multiple ogg vorbis files) to find the sweet spot. +#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH +#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 +#endif + +// STB_VORBIS_FAST_BINARY_LENGTH [number] +// sets the log size of the binary-search acceleration table. this +// is used in similar fashion to the fast-huffman size to set initial +// parameters for the binary search + +// STB_VORBIS_FAST_HUFFMAN_INT +// The fast huffman tables are much more efficient if they can be +// stored as 16-bit results instead of 32-bit results. This restricts +// the codebooks to having only 65535 possible outcomes, though. +// (At least, accelerated by the huffman table.) +#ifndef STB_VORBIS_FAST_HUFFMAN_INT +#define STB_VORBIS_FAST_HUFFMAN_SHORT +#endif + +// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH +// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls +// back on binary searching for the correct one. This requires storing +// extra tables with the huffman codes in sorted order. Defining this +// symbol trades off space for speed by forcing a linear search in the +// non-fast case, except for "sparse" codebooks. +// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + +// STB_VORBIS_DIVIDES_IN_RESIDUE +// stb_vorbis precomputes the result of the scalar residue decoding +// that would otherwise require a divide per chunk. you can trade off +// space for time by defining this symbol. +// #define STB_VORBIS_DIVIDES_IN_RESIDUE + +// STB_VORBIS_DIVIDES_IN_CODEBOOK +// vorbis VQ codebooks can be encoded two ways: with every case explicitly +// stored, or with all elements being chosen from a small range of values, +// and all values possible in all elements. By default, stb_vorbis expands +// this latter kind out to look like the former kind for ease of decoding, +// because otherwise an integer divide-per-vector-element is required to +// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can +// trade off storage for speed. +//#define STB_VORBIS_DIVIDES_IN_CODEBOOK + +#ifdef STB_VORBIS_CODEBOOK_SHORTS +#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" +#endif + +// STB_VORBIS_DIVIDE_TABLE +// this replaces small integer divides in the floor decode loop with +// table lookups. made less than 1% difference, so disabled by default. + +// STB_VORBIS_NO_INLINE_DECODE +// disables the inlining of the scalar codebook fast-huffman decode. +// might save a little codespace; useful for debugging +// #define STB_VORBIS_NO_INLINE_DECODE + +// STB_VORBIS_NO_DEFER_FLOOR +// Normally we only decode the floor without synthesizing the actual +// full curve. We can instead synthesize the curve immediately. This +// requires more memory and is very likely slower, so I don't think +// you'd ever want to do it except for debugging. +// #define STB_VORBIS_NO_DEFER_FLOOR + + + + +////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_NO_PULLDATA_API + #define STB_VORBIS_NO_INTEGER_CONVERSION + #define STB_VORBIS_NO_STDIO +#endif + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) + #define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + + // only need endianness for fast-float-to-int, which we don't + // use for pushdata + + #ifndef STB_VORBIS_BIG_ENDIAN + #define STB_VORBIS_ENDIAN 0 + #else + #define STB_VORBIS_ENDIAN 1 + #endif + +#endif +#endif + + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifndef STB_VORBIS_NO_CRT + #include + #include + #include + #include + + // find definition of alloca if it's not in stdlib.h: + #if defined(_MSC_VER) || defined(__MINGW32__) + #include + #endif + #if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) + #include + #endif +#else // STB_VORBIS_NO_CRT + #define NULL 0 + #define malloc(s) 0 + #define free(s) ((void) 0) + #define realloc(s) 0 +#endif // STB_VORBIS_NO_CRT + +#include + +#ifdef __MINGW32__ + // eff you mingw: + // "fixed": + // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ + // "no that broke the build, reverted, who cares about C": + // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ + #ifdef __forceinline + #undef __forceinline + #endif + #define __forceinline + #ifndef alloca + #define alloca __builtin_alloca + #endif +#elif !defined(_MSC_VER) + #if __GNUC__ + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + +#if STB_VORBIS_MAX_CHANNELS > 256 +#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" +#endif + +#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 +#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" +#endif + + +#if 0 +#include +#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define CHECK(f) ((void) 0) +#endif + +#define MAX_BLOCKSIZE_LOG 13 // from specification +#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) + + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float codetype; + +#ifdef _MSC_VER +#define STBV_NOTUSED(v) (void)(v) +#else +#define STBV_NOTUSED(v) (void)sizeof(v) +#endif + +// @NOTE +// +// Some arrays below are tagged "//varies", which means it's actually +// a variable-sized piece of data, but rather than malloc I assume it's +// small enough it's better to just allocate it all together with the +// main thing +// +// Most of the variables are specified with the smallest size I could pack +// them into. It might give better performance to make them all full-sized +// integers. It should be safe to freely rearrange the structures or change +// the sizes larger--nothing relies on silently truncating etc., nor the +// order of variables. + +#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + uint8 *codeword_lengths; + float minimum_value; + float delta_value; + uint8 value_bits; + uint8 lookup_type; + uint8 sequence_p; + uint8 sparse; + uint32 lookup_values; + codetype *multiplicands; + uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #else + int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #endif + uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} Codebook; + +typedef struct +{ + uint8 order; + uint16 rate; + uint16 bark_map_size; + uint8 amplitude_bits; + uint8 amplitude_offset; + uint8 number_of_books; + uint8 book_list[16]; // varies +} Floor0; + +typedef struct +{ + uint8 partitions; + uint8 partition_class_list[32]; // varies + uint8 class_dimensions[16]; // varies + uint8 class_subclasses[16]; // varies + uint8 class_masterbooks[16]; // varies + int16 subclass_books[16][8]; // varies + uint16 Xlist[31*8+2]; // varies + uint8 sorted_order[31*8+2]; + uint8 neighbors[31*8+2][2]; + uint8 floor1_multiplier; + uint8 rangebits; + int values; +} Floor1; + +typedef union +{ + Floor0 floor0; + Floor1 floor1; +} Floor; + +typedef struct +{ + uint32 begin, end; + uint32 part_size; + uint8 classifications; + uint8 classbook; + uint8 **classdata; + int16 (*residue_books)[8]; +} Residue; + +typedef struct +{ + uint8 magnitude; + uint8 angle; + uint8 mux; +} MappingChannel; + +typedef struct +{ + uint16 coupling_steps; + MappingChannel *chan; + uint8 submaps; + uint8 submap_floor[15]; // varies + uint8 submap_residue[15]; // varies +} Mapping; + +typedef struct +{ + uint8 blockflag; + uint8 mapping; + uint16 windowtype; + uint16 transformtype; +} Mode; + +typedef struct +{ + uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + uint32 sample_loc; // granule pos encoded in page +} CRCscan; + +typedef struct +{ + uint32 page_start, page_end; + uint32 last_decoded_sample; +} ProbedPage; + +struct stb_vorbis +{ + // user-accessible info + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int temp_memory_required; + unsigned int setup_temp_memory_required; + + char *vendor; + int comment_list_length; + char **comment_list; + + // input config +#ifndef STB_VORBIS_NO_STDIO + FILE *f; + uint32 f_start; + int close_on_free; +#endif + + uint8 *stream; + uint8 *stream_start; + uint8 *stream_end; + + uint32 stream_len; + + uint8 push_mode; + + // the page to seek to when seeking to start, may be zero + uint32 first_audio_page_offset; + + // p_first is the page on which the first audio packet ends + // (but not necessarily the page on which it starts) + ProbedPage p_first, p_last; + + // memory management + stb_vorbis_alloc alloc; + int setup_offset; + int temp_offset; + + // run-time results + int eof; + enum STBVorbisError error; + + // user-useful data + + // header info + int blocksize[2]; + int blocksize_0, blocksize_1; + int codebook_count; + Codebook *codebooks; + int floor_count; + uint16 floor_types[64]; // varies + Floor *floor_config; + int residue_count; + uint16 residue_types[64]; // varies + Residue *residue_config; + int mapping_count; + Mapping *mapping; + int mode_count; + Mode mode_config[64]; // varies + + uint32 total_samples; + + // decode buffer + float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; + float *outputs [STB_VORBIS_MAX_CHANNELS]; + + float *previous_window[STB_VORBIS_MAX_CHANNELS]; + int previous_length; + + #ifndef STB_VORBIS_NO_DEFER_FLOOR + int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + uint32 current_loc; // sample location of next frame to decode + int current_loc_valid; + + // per-blocksize precomputed data + + // twiddle factors + float *A[2],*B[2],*C[2]; + float *window[2]; + uint16 *bit_reverse[2]; + + // current page/packet/segment streaming info + uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + uint8 segments[255]; + uint8 page_flag; + uint8 bytes_in_seg; + uint8 first_decode; + int next_seg; + int last_seg; // flag that we're on the last segment + int last_seg_which; // what was the segment number of the last seg? + uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + uint32 known_loc_for_packet; + int discard_samples_deferred; + uint32 samples_output; + + // push mode scanning + int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching +#ifndef STB_VORBIS_NO_PUSHDATA_API + CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; +#endif + + // sample-access + int channel_buffer_start; + int channel_buffer_end; +}; + +#if defined(STB_VORBIS_NO_PUSHDATA_API) + #define IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define IS_PUSH_MODE(f) TRUE +#else + #define IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis vorb; + +static int error(vorb *f, enum STBVorbisError e) +{ + f->error = e; + if (!f->eof && e != VORBIS_need_more_data) { + f->error=e; // breakpoint for debugging + } + return 0; +} + + +// these functions are used for allocating temporary memory +// while decoding. if you can afford the stack space, use +// alloca(); otherwise, provide a temp buffer and it will +// allocate out of those. + +#define array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) +#define temp_free(f,p) (void)0 +#define temp_alloc_save(f) ((f)->temp_offset) +#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) + +// given a sufficiently large block of memory, make an array of pointers to subblocks of it +static void *make_block_array(void *mem, int count, int size) +{ + int i; + void ** p = (void **) mem; + char *q = (char *) (p + count); + for (i=0; i < count; ++i) { + p[i] = q; + q += size; + } + return p; +} + +static void *setup_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + f->setup_memory_required += sz; + if (f->alloc.alloc_buffer) { + void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; + if (f->setup_offset + sz > f->temp_offset) return NULL; + f->setup_offset += sz; + return p; + } + return sz ? malloc(sz) : NULL; +} + +static void setup_free(vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + free(p); +} + +static void *setup_temp_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + if (f->alloc.alloc_buffer) { + if (f->temp_offset - sz < f->setup_offset) return NULL; + f->temp_offset -= sz; + return (char *) f->alloc.alloc_buffer + f->temp_offset; + } + return malloc(sz); +} + +static void setup_temp_free(vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+7)&~7; + return; + } + free(p); +} + +#define CRC32_POLY 0x04c11db7 // from spec + +static uint32 crc_table[256]; +static void crc32_init(void) +{ + int i,j; + uint32 s; + for(i=0; i < 256; i++) { + for (s=(uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); + crc_table[i] = s; + } +} + +static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) +{ + return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int bit_reverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +static float square(float x) +{ + return x*x; +} + +// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 +// as required by the specification. fast(?) implementation from stb.h +// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup +static int ilog(int32 n) +{ + static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; + + if (n < 0) return 0; // signed n returns 0 + + // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) + if (n < (1 << 14)) + if (n < (1 << 4)) return 0 + log2_4[n ]; + else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1 << 24)) + if (n < (1 << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846264f // from CRC +#endif + +// code length assigned to a value with no huffman encoding +#define NO_CODE 255 + +/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// +// +// these functions are only called at setup, and only a few times +// per file + +static float float32_unpack(uint32 x) +{ + // from the specification + uint32 mantissa = x & 0x1fffff; + uint32 sign = x & 0x80000000; + uint32 exp = (x & 0x7fe00000) >> 21; + double res = sign ? -(double)mantissa : (double)mantissa; + return (float) ldexp((float)res, (int)exp-788); +} + + +// zlib & jpeg huffman tables assume that the output symbols +// can either be arbitrarily arranged, or have monotonically +// increasing frequencies--they rely on the lengths being sorted; +// this makes for a very simple generation algorithm. +// vorbis allows a huffman table with non-sorted lengths. This +// requires a more sophisticated construction, since symbols in +// order do not map to huffman codes "in order". +static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) +{ + if (!c->sparse) { + c->codewords [symbol] = huff_code; + } else { + c->codewords [count] = huff_code; + c->codeword_lengths[count] = len; + values [count] = symbol; + } +} + +static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) +{ + int i,k,m=0; + uint32 available[32]; + + memset(available, 0, sizeof(available)); + // find the first entry + for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; + if (k == n) { assert(c->sorted_entries == 0); return TRUE; } + assert(len[k] < 32); // no error return required, code reading lens checks this + // add to the list + add_entry(c, 0, k, m++, len[k], values); + // add all available leaves + for (i=1; i <= len[k]; ++i) + available[i] = 1U << (32-i); + // note that the above code treats the first case specially, + // but it's really the same as the following code, so they + // could probably be combined (except the initial code is 0, + // and I use 0 in available[] to mean 'empty') + for (i=k+1; i < n; ++i) { + uint32 res; + int z = len[i], y; + if (z == NO_CODE) continue; + assert(z < 32); // no error return required, code reading lens checks this + // find lowest available leaf (should always be earliest, + // which is what the specification calls for) + // note that this property, and the fact we can never have + // more than one free leaf at a given level, isn't totally + // trivial to prove, but it seems true and the assert never + // fires, so! + while (z > 0 && !available[z]) --z; + if (z == 0) { return FALSE; } + res = available[z]; + available[z] = 0; + add_entry(c, bit_reverse(res), i, m++, len[i], values); + // propagate availability up the tree + if (z != len[i]) { + for (y=len[i]; y > z; --y) { + assert(available[y] == 0); + available[y] = res + (1 << (32-y)); + } + } + } + return TRUE; +} + +// accelerated huffman table allows fast O(1) match of all symbols +// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH +static void compute_accelerated_huffman(Codebook *c) +{ + int i, len; + for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) + c->fast_huffman[i] = -1; + + len = c->sparse ? c->sorted_entries : c->entries; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + if (len > 32767) len = 32767; // largest possible value we can encode! + #endif + for (i=0; i < len; ++i) { + if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { + uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < FAST_HUFFMAN_TABLE_SIZE) { + c->fast_huffman[z] = i; + z += 1 << c->codeword_lengths[i]; + } + } + } +} + +#ifdef _MSC_VER +#define STBV_CDECL __cdecl +#else +#define STBV_CDECL +#endif + +static int STBV_CDECL uint32_compare(const void *p, const void *q) +{ + uint32 x = * (uint32 *) p; + uint32 y = * (uint32 *) q; + return x < y ? -1 : x > y; +} + +static int include_in_sort(Codebook *c, uint8 len) +{ + if (c->sparse) { assert(len != NO_CODE); return TRUE; } + if (len == NO_CODE) return FALSE; + if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; + return FALSE; +} + +// if the fast table above doesn't work, we want to binary +// search them... need to reverse the bits +static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) +{ + int i, len; + // build a list of all the entries + // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. + // this is kind of a frivolous optimization--I don't see any performance improvement, + // but it's like 4 extra lines of code, so. + if (!c->sparse) { + int k = 0; + for (i=0; i < c->entries; ++i) + if (include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); + c->sorted_codewords[c->sorted_entries] = 0xffffffff; + + len = c->sparse ? c->sorted_entries : c->entries; + // now we need to indicate how they correspond; we could either + // #1: sort a different data structure that says who they correspond to + // #2: for each sorted entry, search the original list to find who corresponds + // #3: for each original entry, find the sorted entry + // #1 requires extra storage, #2 is slow, #3 can use binary search! + for (i=0; i < len; ++i) { + int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; + if (include_in_sort(c,huff_len)) { + uint32 code = bit_reverse(c->codewords[i]); + int x=0, n=c->sorted_entries; + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + assert(c->sorted_codewords[x] == code); + if (c->sparse) { + c->sorted_values[x] = values[i]; + c->codeword_lengths[x] = huff_len; + } else { + c->sorted_values[x] = i; + } + } + } +} + +// only run while parsing the header (3 times) +static int vorbis_validate(uint8 *data) +{ + static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; + return memcmp(data, vorbis, 6) == 0; +} + +// called from setup only, once per code book +// (formula implied by specification) +static int lookup1_values(int entries, int dim) +{ + int r = (int) floor(exp((float) log((float) entries) / dim)); + if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; + ++r; // floor() to avoid _ftol() when non-CRT + if (pow((float) r+1, dim) <= entries) + return -1; + if ((int) floor(pow((float) r, dim)) > entries) + return -1; + return r; +} + +// called twice per file +static void compute_twiddle_factors(int n, float *A, float *B, float *C) +{ + int n4 = n >> 2, n8 = n >> 3; + int k,k2; + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; + B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } +} + +static void compute_window(int n, float *window) +{ + int n2 = n >> 1, i; + for (i=0; i < n2; ++i) + window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void compute_bitreverse(int n, uint16 *rev) +{ + int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int init_blocksize(vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); + compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return error(f, VORBIS_outofmem); + compute_window(n, f->window[b]); + f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); + if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); + compute_bitreverse(n, f->bit_reverse[b]); + return TRUE; +} + +static void neighbors(uint16 *x, int n, int *plow, int *phigh) +{ + int low = -1; + int high = 65536; + int i; + for (i=0; i < n; ++i) { + if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } + if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } + } +} + +// this has been repurposed so y is now the original index instead of y +typedef struct +{ + uint16 x,id; +} stbv__floor_ordering; + +static int STBV_CDECL point_compare(const void *p, const void *q) +{ + stbv__floor_ordering *a = (stbv__floor_ordering *) p; + stbv__floor_ordering *b = (stbv__floor_ordering *) q; + return a->x < b->x ? -1 : a->x > b->x; +} + +// +/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// + + +#if defined(STB_VORBIS_NO_STDIO) + #define USE_MEMORY(z) TRUE +#else + #define USE_MEMORY(z) ((z)->stream) +#endif + +static uint8 get8(vorb *z) +{ + if (USE_MEMORY(z)) { + if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } + return *z->stream++; + } + + #ifndef STB_VORBIS_NO_STDIO + { + int c = fgetc(z->f); + if (c == EOF) { z->eof = TRUE; return 0; } + return c; + } + #endif +} + +static uint32 get32(vorb *f) +{ + uint32 x; + x = get8(f); + x += get8(f) << 8; + x += get8(f) << 16; + x += (uint32) get8(f) << 24; + return x; +} + +static int getn(vorb *z, uint8 *data, int n) +{ + if (USE_MEMORY(z)) { + if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } + memcpy(data, z->stream, n); + z->stream += n; + return 1; + } + + #ifndef STB_VORBIS_NO_STDIO + if (fread(data, n, 1, z->f) == 1) + return 1; + else { + z->eof = 1; + return 0; + } + #endif +} + +static void skip(vorb *z, int n) +{ + if (USE_MEMORY(z)) { + z->stream += n; + if (z->stream >= z->stream_end) z->eof = 1; + return; + } + #ifndef STB_VORBIS_NO_STDIO + { + long x = ftell(z->f); + fseek(z->f, x+n, SEEK_SET); + } + #endif +} + +static int set_file_offset(stb_vorbis *f, unsigned int loc) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + f->eof = 0; + if (USE_MEMORY(f)) { + if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + f->stream = f->stream_end; + f->eof = 1; + return 0; + } else { + f->stream = f->stream_start + loc; + return 1; + } + } + #ifndef STB_VORBIS_NO_STDIO + if (loc + f->f_start < loc || loc >= 0x80000000) { + loc = 0x7fffffff; + f->eof = 1; + } else { + loc += f->f_start; + } + if (!fseek(f->f, loc, SEEK_SET)) + return 1; + f->eof = 1; + fseek(f->f, f->f_start, SEEK_END); + return 0; + #endif +} + + +static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int capture_pattern(vorb *f) +{ + if (0x4f != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x53 != get8(f)) return FALSE; + return TRUE; +} + +#define PAGEFLAG_continued_packet 1 +#define PAGEFLAG_first_page 2 +#define PAGEFLAG_last_page 4 + +static int start_page_no_capturepattern(vorb *f) +{ + uint32 loc0,loc1,n; + if (f->first_decode && !IS_PUSH_MODE(f)) { + f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; + } + // stream structure version + if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = get8(f); + // absolute granule position + loc0 = get32(f); + loc1 = get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + get32(f); + //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = get32(f); + f->last_page = n; + // CRC32 + get32(f); + // page_segments + f->segment_count = get8(f); + if (!getn(f, f->segments, f->segment_count)) + return error(f, VORBIS_unexpected_eof); + // assume we _don't_ know any the sample position of any segments + f->end_seg_with_known_loc = -2; + if (loc0 != ~0U || loc1 != ~0U) { + int i; + // determine which packet is the last one that will complete + for (i=f->segment_count-1; i >= 0; --i) + if (f->segments[i] < 255) + break; + // 'i' is now the index of the _last_ segment of a packet that ends + if (i >= 0) { + f->end_seg_with_known_loc = i; + f->known_loc_for_packet = loc0; + } + } + if (f->first_decode) { + int i,len; + len = 0; + for (i=0; i < f->segment_count; ++i) + len += f->segments[i]; + len += 27 + f->segment_count; + f->p_first.page_end = f->p_first.page_start + len; + f->p_first.last_decoded_sample = loc0; + } + f->next_seg = 0; + return TRUE; +} + +static int start_page(vorb *f) +{ + if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); + return start_page_no_capturepattern(f); +} + +static int start_packet(vorb *f) +{ + while (f->next_seg == -1) { + if (!start_page(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) + return error(f, VORBIS_continued_packet_flag_invalid); + } + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + // f->next_seg is now valid + return TRUE; +} + +static int maybe_start_packet(vorb *f) +{ + if (f->next_seg == -1) { + int x = get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (!start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) { + // set up enough state that we can read this packet if we want, + // e.g. during recovery + f->last_seg = FALSE; + f->bytes_in_seg = 0; + return error(f, VORBIS_continued_packet_flag_invalid); + } + } + return start_packet(f); +} + +static int next_segment(vorb *f) +{ + int len; + if (f->last_seg) return 0; + if (f->next_seg == -1) { + f->last_seg_which = f->segment_count-1; // in case start_page fails + if (!start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); + } + len = f->segments[f->next_seg++]; + if (len < 255) { + f->last_seg = TRUE; + f->last_seg_which = f->next_seg-1; + } + if (f->next_seg >= f->segment_count) + f->next_seg = -1; + assert(f->bytes_in_seg == 0); + f->bytes_in_seg = len; + return len; +} + +#define EOP (-1) +#define INVALID_BITS (-1) + +static int get8_packet_raw(vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return EOP; + else if (!next_segment(f)) return EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return get8(f); +} + +static int get8_packet(vorb *f) +{ + int x = get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static int get32_packet(vorb *f) +{ + uint32 x; + x = get8_packet(f); + x += get8_packet(f) << 8; + x += get8_packet(f) << 16; + x += (uint32) get8_packet(f) << 24; + return x; +} + +static void flush_packet(vorb *f) +{ + while (get8_packet_raw(f) != EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static uint32 get_bits(vorb *f, int n) +{ + uint32 z; + + if (f->valid_bits < 0) return 0; + if (f->valid_bits < n) { + if (n > 24) { + // the accumulator technique below would not work correctly in this case + z = get_bits(f, 24); + z += get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = get8_packet_raw(f); + if (z == EOP) { + f->valid_bits = INVALID_BITS; + return 0; + } + f->acc += z << f->valid_bits; + f->valid_bits += 8; + } + } + + assert(f->valid_bits >= n); + z = f->acc & ((1 << n)-1); + f->acc >>= n; + f->valid_bits -= n; + return z; +} + +// @OPTIMIZE: primary accumulator for huffman +// expand the buffer to as many bits as possible without reading off end of packet +// it might be nice to allow f->valid_bits and f->acc to be stored in registers, +// e.g. cache them locally and decode locally +static __forceinline void prep_huffman(vorb *f) +{ + if (f->valid_bits <= 24) { + if (f->valid_bits == 0) f->acc = 0; + do { + int z; + if (f->last_seg && !f->bytes_in_seg) return; + z = get8_packet_raw(f); + if (z == EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + VORBIS_packet_id = 1, + VORBIS_packet_comment = 3, + VORBIS_packet_setup = 5 +}; + +static int codebook_decode_scalar_raw(vorb *f, Codebook *c) +{ + int i; + prep_huffman(f); + + if (c->codewords == NULL && c->sorted_codewords == NULL) + return -1; + + // cases to use binary search: sorted_codewords && !c->codewords + // sorted_codewords && c->entries > 8 + if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { + // binary search + uint32 code = bit_reverse(f->acc); + int x=0, n=c->sorted_entries, len; + + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + // x is now the sorted index + if (!c->sparse) x = c->sorted_values[x]; + // x is now sorted index if sparse, or symbol otherwise + len = c->codeword_lengths[x]; + if (f->valid_bits >= len) { + f->acc >>= len; + f->valid_bits -= len; + return x; + } + + f->valid_bits = 0; + return -1; + } + + // if small, linear search + assert(!c->sparse); + for (i=0; i < c->entries; ++i) { + if (c->codeword_lengths[i] == NO_CODE) continue; + if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (f->valid_bits >= c->codeword_lengths[i]) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + return i; + } + f->valid_bits = 0; + return -1; + } + } + + error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + prep_huffman(f); \ + var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ + var = c->fast_huffman[var]; \ + if (var >= 0) { \ + int n = c->codeword_lengths[var]; \ + f->acc >>= n; \ + f->valid_bits -= n; \ + if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ + } else { \ + var = codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int codebook_decode_scalar(vorb *f, Codebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + prep_huffman(f); + // fast huffman table lookup + i = f->acc & FAST_HUFFMAN_TABLE_MASK; + i = c->fast_huffman[i]; + if (i >= 0) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } + return i; + } + return codebook_decode_scalar_raw(f,c); +} + +#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); + +#endif + +#define DECODE(var,f,c) \ + DECODE_RAW(var,f,c) \ + if (c->sparse) var = c->sorted_values[var]; + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) DECODE(var,f,c) +#endif + + + + + + +// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_BASE(c) (0) + +static int codebook_decode_start(vorb *f, Codebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int codebook_decode(vorb *f, Codebook *c, float *output, int len) +{ + int i,z = codebook_decode_start(f,c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + float last = CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i] += val; + if (c->sequence_p) last = val + c->minimum_value; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + if (c->sequence_p) { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) +{ + int i,z = codebook_decode_start(f,c); + float last = CODEBOOK_ELEMENT_BASE(c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) +{ + int c_inter = *c_inter_p; + int p_inter = *p_inter_p; + int i,z, effective = c->dimensions; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = CODEBOOK_ELEMENT_BASE(c); + DECODE_VQ(z,f,c); + #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + assert(!c->sparse || z < c->sorted_entries); + #endif + if (z < 0) { + if (!f->bytes_in_seg) + if (f->last_seg) return FALSE; + return error(f, VORBIS_invalid_stream); + } + + // if this will take us off the end of the buffers, stop short! + // we check by computing the length of the virtual interleaved + // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), + // and the length we'll be using (effective) + if (c_inter + p_inter*ch + effective > len * ch) { + effective = len*ch - (p_inter*ch - c_inter); + } + + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < effective; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + } else + #endif + { + z *= c->dimensions; + if (c->sequence_p) { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + last = val; + } + } else { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + } + } + } + + total_decode -= effective; + } + *c_inter_p = c_inter; + *p_inter_p = p_inter; + return TRUE; +} + +static int predict_point(int x, int x0, int x1, int y0, int y1) +{ + int dy = y1 - y0; + int adx = x1 - x0; + // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? + int err = abs(dy) * (x - x0); + int off = err / adx; + return dy < 0 ? y0 - off : y0 + off; +} + +// the following table is block-copied from the specification +static float inverse_db_table[256] = +{ + 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, + 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, + 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, + 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, + 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, + 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, + 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, + 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, + 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, + 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, + 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, + 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, + 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, + 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, + 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, + 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, + 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, + 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, + 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, + 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, + 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, + 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, + 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, + 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, + 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, + 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, + 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, + 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, + 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, + 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, + 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, + 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, + 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, + 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, + 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, + 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, + 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, + 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, + 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, + 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, + 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, + 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, + 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, + 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, + 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, + 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, + 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, + 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, + 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, + 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, + 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, + 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, + 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, + 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, + 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, + 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, + 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, + 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, + 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, + 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, + 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, + 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, + 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, + 0.82788260f, 0.88168307f, 0.9389798f, 1.0f +}; + + +// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, +// note that you must produce bit-identical output to decode correctly; +// this specific sequence of operations is specified in the spec (it's +// drawing integer-quantized frequency-space lines that the encoder +// expects to be exactly the same) +// ... also, isn't the whole point of Bresenham's algorithm to NOT +// have to divide in the setup? sigh. +#ifndef STB_VORBIS_NO_DEFER_FLOOR +#define LINE_OP(a,b) a *= b +#else +#define LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define DIVTAB_NUMER 32 +#define DIVTAB_DENOM 64 +int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +{ + int dy = y1 - y0; + int adx = x1 - x0; + int ady = abs(dy); + int base; + int x=x0,y=y0; + int err = 0; + int sy; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { + if (dy < 0) { + base = -integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = integer_divide_table[ady][adx]; + sy = base+1; + } + } else { + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; + } +#else + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; +#endif + ady -= abs(base) * adx; + if (x1 > n) x1 = n; + if (x < x1) { + LINE_OP(output[x], inverse_db_table[y&255]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + LINE_OP(output[x], inverse_db_table[y&255]); + } + } +} + +static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) +{ + int k; + if (rtype == 0) { + int step = n / book->dimensions; + for (k=0; k < step; ++k) + if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!codebook_decode(f, book, target+offset, n-k)) + return FALSE; + k += book->dimensions; + offset += book->dimensions; + } + } + return TRUE; +} + +// n is 1/2 of the blocksize -- +// specification: "Correct per-vector decode length is [n]/2" +static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) +{ + int i,j,pass; + Residue *r = f->residue_config + rn; + int rtype = f->residue_types[rn]; + int c = r->classbook; + int classwords = f->codebooks[c].dimensions; + unsigned int actual_size = rtype == 2 ? n*2 : n; + unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); + unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + int temp_alloc_point = temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + CHECK(f); + + for (i=0; i < ch; ++i) + if (!do_not_decode[i]) + memset(residue_buffers[i], 0, sizeof(float) * n); + + if (rtype == 2 && ch != 1) { + for (j=0; j < ch; ++j) + if (!do_not_decode[j]) + break; + if (j == ch) + goto done; + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set = 0; + if (ch == 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = (z & 1), p_inter = z>>1; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #endif + } else { + z += r->part_size; + c_inter = z & 1; + p_inter = z >> 1; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else if (ch > 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = z % ch, p_inter = z/ch; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = z % ch; + p_inter = z / ch; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + } + goto done; + } + CHECK(f); + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set=0; + while (pcount < part_read) { + if (pass == 0) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + Codebook *c = f->codebooks+r->classbook; + int temp; + DECODE(temp,f,c); + if (temp == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[j][class_set] = r->classdata[temp]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[j][i+pcount] = temp % r->classifications; + temp /= r->classifications; + } + #endif + } + } + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[j][class_set][i]; + #else + int c = classifications[j][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + float *target = residue_buffers[j]; + int offset = r->begin + pcount * r->part_size; + int n = r->part_size; + Codebook *book = f->codebooks + b; + if (!residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + temp_free(f,part_classdata); + #else + temp_free(f,classifications); + #endif + temp_alloc_restore(f,temp_alloc_point); +} + + +#if 0 +// slow way for debugging +void inverse_mdct_slow(float *buffer, int n) +{ + int i,j; + int n2 = n >> 1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + // formula from paper: + //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + // formula from wikipedia + //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + // these are equivalent, except the formula from the paper inverts the multiplier! + // however, what actually works is NO MULTIPLIER!?! + //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + buffer[i] = acc; + } + free(x); +} +#elif 0 +// same as above, but just barely able to run in real time on modern machines +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + float mcos[16384]; + int i,j; + int n2 = n >> 1, nmask = (n << 2) -1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < 4*n; ++i) + mcos[i] = (float) cos(M_PI / 2 * i / n); + + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; + buffer[i] = acc; + } + free(x); +} +#elif 0 +// transform to use a slow dct-iv; this is STILL basically trivial, +// but only requires half as many ops +void dct_iv_slow(float *buffer, int n) +{ + float mcos[16384]; + float x[2048]; + int i,j; + int n2 = n >> 1, nmask = (n << 3) - 1; + memcpy(x, buffer, sizeof(*x) * n); + for (i=0; i < 8*n; ++i) + mcos[i] = (float) cos(M_PI / 4 * i / n); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n; ++j) + acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; + buffer[i] = acc; + } +} + +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; + float temp[4096]; + + memcpy(temp, buffer, n2 * sizeof(float)); + dct_iv_slow(temp, n2); // returns -c'-d, a-b' + + for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' + for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' + for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d +} +#endif + +#ifndef LIBVORBIS_MDCT +#define LIBVORBIS_MDCT 0 +#endif + +#if LIBVORBIS_MDCT +// directly call the vorbis MDCT using an interface documented +// by Jeff Roberts... useful for performance comparison +typedef struct +{ + int n; + int log2n; + + float *trig; + int *bitrev; + + float scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup, int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_backward(mdct_lookup *init, float *in, float *out); + +mdct_lookup M1,M2; + +void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + mdct_lookup *M; + if (M1.n == n) M = &M1; + else if (M2.n == n) M = &M2; + else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } + else { + if (M2.n) __asm int 3; + mdct_init(&M2, n); + M = &M2; + } + + mdct_backward(M, buffer, buffer); +} +#endif + + +// the following were split out into separate functions while optimizing; +// they could be pushed back up but eh. __forceinline showed no change; +// they're probably already being inlined. +static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) +{ + float *ee0 = e + i_off; + float *ee2 = ee0 + k_off; + int i; + + assert((n & 3) == 0); + for (i=(n>>2); i > 0; --i) { + float k00_20, k01_21; + k00_20 = ee0[ 0] - ee2[ 0]; + k01_21 = ee0[-1] - ee2[-1]; + ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-2] - ee2[-2]; + k01_21 = ee0[-3] - ee2[-3]; + ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-4] - ee2[-4]; + k01_21 = ee0[-5] - ee2[-5]; + ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-6] - ee2[-6]; + k01_21 = ee0[-7] - ee2[-7]; + ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + ee0 -= 8; + ee2 -= 8; + } +} + +static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) +{ + int i; + float k00_20, k01_21; + + float *e0 = e + d0; + float *e2 = e0 + k_off; + + for (i=lim >> 2; i > 0; --i) { + k00_20 = e0[-0] - e2[-0]; + k01_21 = e0[-1] - e2[-1]; + e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; + e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; + e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-2] - e2[-2]; + k01_21 = e0[-3] - e2[-3]; + e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; + e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; + e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-4] - e2[-4]; + k01_21 = e0[-5] - e2[-5]; + e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; + e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; + e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-6] - e2[-6]; + k01_21 = e0[-7] - e2[-7]; + e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; + e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; + e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; + + e0 -= 8; + e2 -= 8; + + A += k1; + } +} + +static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) +{ + int i; + float A0 = A[0]; + float A1 = A[0+1]; + float A2 = A[0+a_off]; + float A3 = A[0+a_off+1]; + float A4 = A[0+a_off*2+0]; + float A5 = A[0+a_off*2+1]; + float A6 = A[0+a_off*3+0]; + float A7 = A[0+a_off*3+1]; + + float k00,k11; + + float *ee0 = e +i_off; + float *ee2 = ee0+k_off; + + for (i=n; i > 0; --i) { + k00 = ee0[ 0] - ee2[ 0]; + k11 = ee0[-1] - ee2[-1]; + ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = (k00) * A0 - (k11) * A1; + ee2[-1] = (k11) * A0 + (k00) * A1; + + k00 = ee0[-2] - ee2[-2]; + k11 = ee0[-3] - ee2[-3]; + ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = (k00) * A2 - (k11) * A3; + ee2[-3] = (k11) * A2 + (k00) * A3; + + k00 = ee0[-4] - ee2[-4]; + k11 = ee0[-5] - ee2[-5]; + ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = (k00) * A4 - (k11) * A5; + ee2[-5] = (k11) * A4 + (k00) * A5; + + k00 = ee0[-6] - ee2[-6]; + k11 = ee0[-7] - ee2[-7]; + ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = (k00) * A6 - (k11) * A7; + ee2[-7] = (k11) * A6 + (k00) * A7; + + ee0 -= k0; + ee2 -= k0; + } +} + +static __forceinline void iter_54(float *z) +{ + float k00,k11,k22,k33; + float y0,y1,y2,y3; + + k00 = z[ 0] - z[-4]; + y0 = z[ 0] + z[-4]; + y2 = z[-2] + z[-6]; + k22 = z[-2] - z[-6]; + + z[-0] = y0 + y2; // z0 + z4 + z2 + z6 + z[-2] = y0 - y2; // z0 + z4 - z2 - z6 + + // done with y0,y2 + + k33 = z[-3] - z[-7]; + + z[-4] = k00 + k33; // z0 - z4 + z3 - z7 + z[-6] = k00 - k33; // z0 - z4 - z3 + z7 + + // done with k33 + + k11 = z[-1] - z[-5]; + y1 = z[-1] + z[-5]; + y3 = z[-3] + z[-7]; + + z[-1] = y1 + y3; // z1 + z5 + z3 + z7 + z[-3] = y1 - y3; // z1 + z5 - z3 - z7 + z[-5] = k11 - k22; // z1 - z5 + z2 - z6 + z[-7] = k11 + k22; // z1 - z5 - z2 + z6 +} + +static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) +{ + int a_off = base_n >> 3; + float A2 = A[0+a_off]; + float *z = e + i_off; + float *base = z - 16 * n; + + while (z > base) { + float k00,k11; + float l00,l11; + + k00 = z[-0] - z[ -8]; + k11 = z[-1] - z[ -9]; + l00 = z[-2] - z[-10]; + l11 = z[-3] - z[-11]; + z[ -0] = z[-0] + z[ -8]; + z[ -1] = z[-1] + z[ -9]; + z[ -2] = z[-2] + z[-10]; + z[ -3] = z[-3] + z[-11]; + z[ -8] = k00; + z[ -9] = k11; + z[-10] = (l00+l11) * A2; + z[-11] = (l11-l00) * A2; + + k00 = z[ -4] - z[-12]; + k11 = z[ -5] - z[-13]; + l00 = z[ -6] - z[-14]; + l11 = z[ -7] - z[-15]; + z[ -4] = z[ -4] + z[-12]; + z[ -5] = z[ -5] + z[-13]; + z[ -6] = z[ -6] + z[-14]; + z[ -7] = z[ -7] + z[-15]; + z[-12] = k11; + z[-13] = -k00; + z[-14] = (l11-l00) * A2; + z[-15] = (l00+l11) * -A2; + + iter_54(z); + iter_54(z-8); + z -= 16; + } +} + +static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int ld; + // @OPTIMIZE: reduce register pressure by using fewer variables? + int save_point = temp_alloc_save(f); + float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); + float *u=NULL,*v=NULL; + // twiddle factors + float *A = f->A[blocktype]; + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. + + // kernel from paper + + + // merged: + // copy and reflect spectral data + // step 0 + + // note that it turns out that the items added together during + // this step are, in fact, being added to themselves (as reflected + // by step 0). inexplicable inefficiency! this became obvious + // once I combined the passes. + + // so there's a missing 'times 2' here (for adding X to itself). + // this propagates through linearly to the end, where the numbers + // are 1/2 too small, and need to be compensated for. + + { + float *d,*e, *AA, *e_stop; + d = &buf2[n2-2]; + AA = A; + e = &buffer[0]; + e_stop = &buffer[n2]; + while (e != e_stop) { + d[1] = (e[0] * AA[0] - e[2]*AA[1]); + d[0] = (e[0] * AA[1] + e[2]*AA[0]); + d -= 2; + AA += 2; + e += 4; + } + + e = &buffer[n2-3]; + while (d >= buf2) { + d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); + d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); + d -= 2; + AA += 2; + e -= 4; + } + } + + // now we use symbolic names for these, so that we can + // possibly swap their meaning as we change which operations + // are in place + + u = buffer; + v = buf2; + + // step 2 (paper output is w, now u) + // this could be in place, but the data ends up in the wrong + // place... _somebody_'s got to swap it, so this is nominated + { + float *AA = &A[n2-8]; + float *d0,*d1, *e0, *e1; + + e0 = &v[n4]; + e1 = &v[0]; + + d0 = &u[n4]; + d1 = &u[0]; + + while (AA >= A) { + float v40_20, v41_21; + + v41_21 = e0[1] - e1[1]; + v40_20 = e0[0] - e1[0]; + d0[1] = e0[1] + e1[1]; + d0[0] = e0[0] + e1[0]; + d1[1] = v41_21*AA[4] - v40_20*AA[5]; + d1[0] = v40_20*AA[4] + v41_21*AA[5]; + + v41_21 = e0[3] - e1[3]; + v40_20 = e0[2] - e1[2]; + d0[3] = e0[3] + e1[3]; + d0[2] = e0[2] + e1[2]; + d1[3] = v41_21*AA[0] - v40_20*AA[1]; + d1[2] = v40_20*AA[0] + v41_21*AA[1]; + + AA -= 8; + + d0 += 4; + d1 += 4; + e0 += 4; + e1 += 4; + } + } + + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + + // optimized step 3: + + // the original step3 loop can be nested r inside s or s inside r; + // it's written originally as s inside r, but this is dumb when r + // iterates many times, and s few. So I have two copies of it and + // switch between them halfway. + + // this is iteration 0 of step 3 + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); + + l=2; + for (; l < (ld-3)>>1; ++l) { + int k0 = n >> (l+2), k0_2 = k0>>1; + int lim = 1 << (l+1); + int i; + for (i=0; i < lim; ++i) + imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); + } + + for (; l < ld-6; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; + int rlim = n >> (l+6), r; + int lim = 1 << (l+1); + int i_off; + float *A0 = A; + i_off = n2-1; + for (r=rlim; r > 0; --r) { + imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); + A0 += k1*4; + i_off -= 8; + } + } + + // iterations with count: + // ld-6,-5,-4 all interleaved together + // the big win comes from getting rid of needless flops + // due to the constants on pass 5 & 4 being all 1 and 0; + // combining them to be simultaneous to improve cache made little difference + imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); + + // output is u + + // step 4, 5, and 6 + // cannot be in-place because of step 5 + { + uint16 *bitrev = f->bit_reverse[blocktype]; + // weirdly, I'd have thought reading sequentially and writing + // erratically would have been better than vice-versa, but in + // fact that's not what my testing showed. (That is, with + // j = bitreverse(i), do you read i and write j, or read j and write i.) + + float *d0 = &v[n4-4]; + float *d1 = &v[n2-4]; + while (d0 >= v) { + int k4; + + k4 = bitrev[0]; + d1[3] = u[k4+0]; + d1[2] = u[k4+1]; + d0[3] = u[k4+2]; + d0[2] = u[k4+3]; + + k4 = bitrev[1]; + d1[1] = u[k4+0]; + d1[0] = u[k4+1]; + d0[1] = u[k4+2]; + d0[0] = u[k4+3]; + + d0 -= 4; + d1 -= 4; + bitrev += 2; + } + } + // (paper output is u, now v) + + + // data must be in buf2 + assert(v == buf2); + + // step 7 (paper output is v, now v) + // this is now in place + { + float *C = f->C[blocktype]; + float *d, *e; + + d = v; + e = v + n2 - 4; + + while (d < e) { + float a02,a11,b0,b1,b2,b3; + + a02 = d[0] - e[2]; + a11 = d[1] + e[3]; + + b0 = C[1]*a02 + C[0]*a11; + b1 = C[1]*a11 - C[0]*a02; + + b2 = d[0] + e[ 2]; + b3 = d[1] - e[ 3]; + + d[0] = b2 + b0; + d[1] = b3 + b1; + e[2] = b2 - b0; + e[3] = b1 - b3; + + a02 = d[2] - e[0]; + a11 = d[3] + e[1]; + + b0 = C[3]*a02 + C[2]*a11; + b1 = C[3]*a11 - C[2]*a02; + + b2 = d[2] + e[ 0]; + b3 = d[3] - e[ 1]; + + d[2] = b2 + b0; + d[3] = b3 + b1; + e[0] = b2 - b0; + e[1] = b1 - b3; + + C += 4; + d += 4; + e -= 4; + } + } + + // data must be in buf2 + + + // step 8+decode (paper output is X, now buffer) + // this generates pairs of data a la 8 and pushes them directly through + // the decode kernel (pushing rather than pulling) to avoid having + // to make another pass later + + // this cannot POSSIBLY be in place, so we refer to the buffers directly + + { + float *d0,*d1,*d2,*d3; + + float *B = f->B[blocktype] + n2 - 8; + float *e = buf2 + n2 - 8; + d0 = &buffer[0]; + d1 = &buffer[n2-4]; + d2 = &buffer[n2]; + d3 = &buffer[n-4]; + while (e >= v) { + float p0,p1,p2,p3; + + p3 = e[6]*B[7] - e[7]*B[6]; + p2 = -e[6]*B[6] - e[7]*B[7]; + + d0[0] = p3; + d1[3] = - p3; + d2[0] = p2; + d3[3] = p2; + + p1 = e[4]*B[5] - e[5]*B[4]; + p0 = -e[4]*B[4] - e[5]*B[5]; + + d0[1] = p1; + d1[2] = - p1; + d2[1] = p0; + d3[2] = p0; + + p3 = e[2]*B[3] - e[3]*B[2]; + p2 = -e[2]*B[2] - e[3]*B[3]; + + d0[2] = p3; + d1[1] = - p3; + d2[2] = p2; + d3[1] = p2; + + p1 = e[0]*B[1] - e[1]*B[0]; + p0 = -e[0]*B[0] - e[1]*B[1]; + + d0[3] = p1; + d1[0] = - p1; + d2[3] = p0; + d3[0] = p0; + + B -= 8; + e -= 8; + d0 += 4; + d2 += 4; + d1 -= 4; + d3 -= 4; + } + } + + temp_free(f,buf2); + temp_alloc_restore(f,save_point); +} + +#if 0 +// this is the original version of the above code, if you want to optimize it from scratch +void inverse_mdct_naive(float *buffer, int n) +{ + float s; + float A[1 << 12], B[1 << 12], C[1 << 11]; + int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int n3_4 = n - n4, ld; + // how can they claim this only uses N words?! + // oh, because they're only used sparsely, whoops + float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; + // set up twiddle factors + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2); + B[k2+1] = (float) sin((k2+1)*M_PI/n/2); + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // Note there are bugs in that pseudocode, presumably due to them attempting + // to rename the arrays nicely rather than representing the way their actual + // implementation bounces buffers back and forth. As a result, even in the + // "some formulars corrected" version, a direct implementation fails. These + // are noted below as "paper bug". + + // copy and reflect spectral data + for (k=0; k < n2; ++k) u[k] = buffer[k]; + for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; + // kernel from paper + // step 1 + for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { + v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; + v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; + } + // step 2 + for (k=k4=0; k < n8; k+=1, k4+=4) { + w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; + w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; + w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; + w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; + } + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + for (l=0; l < ld-3; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3); + int rlim = n >> (l+4), r4, r; + int s2lim = 1 << (l+2), s2; + for (r=r4=0; r < rlim; r4+=4,++r) { + for (s2=0; s2 < s2lim; s2+=2) { + u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; + u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; + u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] + - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; + u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; + } + } + if (l+1 < ld-3) { + // paper bug: ping-ponging of u&w here is omitted + memcpy(w, u, sizeof(u)); + } + } + + // step 4 + for (i=0; i < n8; ++i) { + int j = bit_reverse(i) >> (32-ld+3); + assert(j < n8); + if (i == j) { + // paper bug: original code probably swapped in place; if copying, + // need to directly copy in this case + int i8 = i << 3; + v[i8+1] = u[i8+1]; + v[i8+3] = u[i8+3]; + v[i8+5] = u[i8+5]; + v[i8+7] = u[i8+7]; + } else if (i < j) { + int i8 = i << 3, j8 = j << 3; + v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; + v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; + v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; + v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; + } + } + // step 5 + for (k=0; k < n2; ++k) { + w[k] = v[k*2+1]; + } + // step 6 + for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { + u[n-1-k2] = w[k4]; + u[n-2-k2] = w[k4+1]; + u[n3_4 - 1 - k2] = w[k4+2]; + u[n3_4 - 2 - k2] = w[k4+3]; + } + // step 7 + for (k=k2=0; k < n8; ++k, k2 += 2) { + v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + } + // step 8 + for (k=k2=0; k < n4; ++k,k2 += 2) { + X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; + X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; + } + + // decode kernel to output + // determined the following value experimentally + // (by first figuring out what made inverse_mdct_slow work); then matching that here + // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) + s = 0.5; // theoretically would be n4 + + // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, + // so it needs to use the "old" B values to behave correctly, or else + // set s to 1.0 ]]] + for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; + for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; + for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; +} +#endif + +static float *get_window(vorb *f, int len) +{ + len <<= 1; + if (len == f->blocksize_0) return f->window[0]; + if (len == f->blocksize_1) return f->window[1]; + return NULL; +} + +#ifndef STB_VORBIS_NO_DEFER_FLOOR +typedef int16 YTYPE; +#else +typedef int YTYPE; +#endif +static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) +{ + int n2 = n >> 1; + int s = map->chan[i].mux, floor; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + int j,q; + int lx = 0, ly = finalY[0] * g->floor1_multiplier; + for (q=1; q < g->values; ++q) { + j = g->sorted_order[q]; + #ifndef STB_VORBIS_NO_DEFER_FLOOR + STBV_NOTUSED(step2_flag); + if (finalY[j] >= 0) + #else + if (step2_flag[j]) + #endif + { + int hy = finalY[j] * g->floor1_multiplier; + int hx = g->Xlist[j]; + if (lx != hx) + draw_line(target, lx,ly, hx,hy, n2); + CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + LINE_OP(target[j], inverse_db_table[ly]); + CHECK(f); + } + } + return TRUE; +} + +// The meaning of "left" and "right" +// +// For a given frame: +// we compute samples from 0..n +// window_center is n/2 +// we'll window and mix the samples from left_start to left_end with data from the previous frame +// all of the samples from left_end to right_start can be output without mixing; however, +// this interval is 0-length except when transitioning between short and long frames +// all of the samples from right_start to right_end need to be mixed with the next frame, +// which we don't have, so those get saved in a buffer +// frame N's right_end-right_start, the number of samples to mix with the next frame, +// has to be the same as frame N+1's left_end-left_start (which they are by +// construction) + +static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + Mode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!maybe_start_packet(f)) + return FALSE; + // check packet type + if (get_bits(f,1) != 0) { + if (IS_PUSH_MODE(f)) + return error(f,VORBIS_bad_packet_type); + while (EOP != get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = get_bits(f, ilog(f->mode_count-1)); + if (i == EOP) return FALSE; + if (i >= f->mode_count) return FALSE; + *mode = i; + m = f->mode_config + i; + if (m->blockflag) { + n = f->blocksize_1; + prev = get_bits(f,1); + next = get_bits(f,1); + } else { + prev = next = 0; + n = f->blocksize_0; + } + +// WINDOWING + + window_center = n >> 1; + if (m->blockflag && !prev) { + *p_left_start = (n - f->blocksize_0) >> 2; + *p_left_end = (n + f->blocksize_0) >> 2; + } else { + *p_left_start = 0; + *p_left_end = window_center; + } + if (m->blockflag && !next) { + *p_right_start = (n*3 - f->blocksize_0) >> 2; + *p_right_end = (n*3 + f->blocksize_0) >> 2; + } else { + *p_right_start = window_center; + *p_right_end = n; + } + + return TRUE; +} + +static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + Mapping *map; + int i,j,k,n,n2; + int zero_channel[256]; + int really_zero_channel[256]; + +// WINDOWING + + STBV_NOTUSED(left_end); + n = f->blocksize[m->blockflag]; + map = &f->mapping[m->mapping]; + +// FLOORS + n2 = n >> 1; + + CHECK(f); + + for (i=0; i < f->channels; ++i) { + int s = map->chan[i].mux, floor; + zero_channel[i] = FALSE; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + if (get_bits(f, 1)) { + short *finalY; + uint8 step2_flag[256]; + static int range_list[4] = { 256, 128, 86, 64 }; + int range = range_list[g->floor1_multiplier-1]; + int offset = 2; + finalY = f->finalY[i]; + finalY[0] = get_bits(f, ilog(range)-1); + finalY[1] = get_bits(f, ilog(range)-1); + for (j=0; j < g->partitions; ++j) { + int pclass = g->partition_class_list[j]; + int cdim = g->class_dimensions[pclass]; + int cbits = g->class_subclasses[pclass]; + int csub = (1 << cbits)-1; + int cval = 0; + if (cbits) { + Codebook *c = f->codebooks + g->class_masterbooks[pclass]; + DECODE(cval,f,c); + } + for (k=0; k < cdim; ++k) { + int book = g->subclass_books[pclass][cval & csub]; + cval = cval >> cbits; + if (book >= 0) { + int temp; + Codebook *c = f->codebooks + book; + DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec + step2_flag[0] = step2_flag[1] = 1; + for (j=2; j < g->values; ++j) { + int low, high, pred, highroom, lowroom, room, val; + low = g->neighbors[j][0]; + high = g->neighbors[j][1]; + //neighbors(g->Xlist, j, &low, &high); + pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); + val = finalY[j]; + highroom = range - pred; + lowroom = pred; + if (highroom < lowroom) + room = highroom * 2; + else + room = lowroom * 2; + if (val) { + step2_flag[low] = step2_flag[high] = 1; + step2_flag[j] = 1; + if (val >= room) + if (highroom > lowroom) + finalY[j] = val - lowroom + pred; + else + finalY[j] = pred - val + highroom - 1; + else + if (val & 1) + finalY[j] = pred - ((val+1)>>1); + else + finalY[j] = pred + (val>>1); + } else { + step2_flag[j] = 0; + finalY[j] = pred; + } + } + +#ifdef STB_VORBIS_NO_DEFER_FLOOR + do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); +#else + // defer final floor computation until _after_ residue + for (j=0; j < g->values; ++j) { + if (!step2_flag[j]) + finalY[j] = -1; + } +#endif + } else { + error: + zero_channel[i] = TRUE; + } + // So we just defer everything else to later + + // at this point we've decoded the floor into buffer + } + } + CHECK(f); + // at this point we've decoded all floors + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + // re-enable coupled channels if necessary + memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); + for (i=0; i < map->coupling_steps; ++i) + if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { + zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; + } + + CHECK(f); +// RESIDUE DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + uint8 do_not_decode[256]; + int ch = 0; + for (j=0; j < f->channels; ++j) { + if (map->chan[j].mux == i) { + if (zero_channel[j]) { + do_not_decode[ch] = TRUE; + residue_buffers[ch] = NULL; + } else { + do_not_decode[ch] = FALSE; + residue_buffers[ch] = f->channel_buffers[j]; + } + ++ch; + } + } + r = map->submap_residue[i]; + decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + CHECK(f); + +// INVERSE COUPLING + for (i = map->coupling_steps-1; i >= 0; --i) { + int n2 = n >> 1; + float *m = f->channel_buffers[map->chan[i].magnitude]; + float *a = f->channel_buffers[map->chan[i].angle ]; + for (j=0; j < n2; ++j) { + float a2,m2; + if (m[j] > 0) + if (a[j] > 0) + m2 = m[j], a2 = m[j] - a[j]; + else + a2 = m[j], m2 = m[j] + a[j]; + else + if (a[j] > 0) + m2 = m[j], a2 = m[j] + a[j]; + else + a2 = m[j], m2 = m[j] - a[j]; + m[j] = m2; + a[j] = a2; + } + } + CHECK(f); + + // finish decoding the floors +#ifndef STB_VORBIS_NO_DEFER_FLOOR + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); + } + } +#else + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + for (j=0; j < n2; ++j) + f->channel_buffers[i][j] *= f->floor_buffers[i][j]; + } + } +#endif + +// INVERSE MDCT + CHECK(f); + for (i=0; i < f->channels; ++i) + inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + flush_packet(f); + + if (f->first_decode) { + // assume we start so first non-discarded sample is sample 0 + // this isn't to spec, but spec would require us to read ahead + // and decode the size of all current frames--could be done, + // but presumably it's not a commonly used feature + f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around) + // we might have to discard samples "from" the next frame too, + // if we're lapping a large block then a small at the start? + f->discard_samples_deferred = n - right_end; + f->current_loc_valid = TRUE; + f->first_decode = FALSE; + } else if (f->discard_samples_deferred) { + if (f->discard_samples_deferred >= right_start - left_start) { + f->discard_samples_deferred -= (right_start - left_start); + left_start = right_start; + *p_left = left_start; + } else { + left_start += f->discard_samples_deferred; + *p_left = left_start; + f->discard_samples_deferred = 0; + } + } else if (f->previous_length == 0 && f->current_loc_valid) { + // we're recovering from a seek... that means we're going to discard + // the samples from this packet even though we know our position from + // the last page header, so we need to update the position based on + // the discarded samples here + // but wait, the code below is going to add this in itself even + // on a discard, so we don't need to do it here... + } + + // check if we have ogg information about the sample # for this packet + if (f->last_seg_which == f->end_seg_with_known_loc) { + // if we have a valid current loc, and this is final: + if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { + uint32 current_end = f->known_loc_for_packet; + // then let's infer the size of the (probably) short final frame + if (current_end < f->current_loc + (right_end-left_start)) { + if (current_end < f->current_loc) { + // negative truncation, that's impossible! + *len = 0; + } else { + *len = current_end - f->current_loc; + } + *len += left_start; // this doesn't seem right, but has no ill effect on my test files + if (*len > right_end) *len = right_end; // this should never happen + f->current_loc += *len; + return TRUE; + } + } + // otherwise, just set our sample loc + // guess that the ogg granule pos refers to the _middle_ of the + // last frame? + // set f->current_loc to the position of left_start + f->current_loc = f->known_loc_for_packet - (n2-left_start); + f->current_loc_valid = TRUE; + } + if (f->current_loc_valid) + f->current_loc += (right_start - left_start); + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + *len = right_end; // ignore samples after the window goes to 0 + CHECK(f); + + return TRUE; +} + +static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) +{ + int prev,i,j; + // we use right&left (the start of the right- and left-window sin()-regions) + // to determine how much to return, rather than inferring from the rules + // (same result, clearer code); 'left' indicates where our sin() window + // starts, therefore where the previous window's right edge starts, and + // therefore where to start mixing from the previous buffer. 'right' + // indicates where our sin() ending-window starts, therefore that's where + // we start saving, and where our returned-data ends. + + // mixin from previous window + if (f->previous_length) { + int i,j, n = f->previous_length; + float *w = get_window(f, n); + if (w == NULL) return 0; + for (i=0; i < f->channels; ++i) { + for (j=0; j < n; ++j) + f->channel_buffers[i][left+j] = + f->channel_buffers[i][left+j]*w[ j] + + f->previous_window[i][ j]*w[n-1-j]; + } + } + + prev = f->previous_length; + + // last half of this data becomes previous window + f->previous_length = len - right; + + // @OPTIMIZE: could avoid this copy by double-buffering the + // output (flipping previous_window with channel_buffers), but + // then previous_window would have to be 2x as large, and + // channel_buffers couldn't be temp mem (although they're NOT + // currently temp mem, they could be (unless we want to level + // performance by spreading out the computation)) + for (i=0; i < f->channels; ++i) + for (j=0; right+j < len; ++j) + f->previous_window[i][j] = f->channel_buffers[i][right+j]; + + if (!prev) + // there was no previous packet, so this data isn't valid... + // this isn't entirely true, only the would-have-overlapped data + // isn't valid, but this seems to be what the spec requires + return 0; + + // truncate a short frame + if (len < right) right = len; + + f->samples_output += right-left; + + return right - left; +} + +static int vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = vorbis_decode_packet(f, &len, &left, &right); + if (res) + vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int is_whole_packet_present(stb_vorbis *f) +{ + // make sure that we have the packet available before continuing... + // this requires a full ogg parse, but we know we can fetch from f->stream + + // instead of coding this out explicitly, we could save the current read state, + // read the next packet with get8() until end-of-packet, check f->eof, then + // reset the state? but that would be slower, esp. since we'd have over 256 bytes + // of state to restore (primarily the page segment table) + + int s = f->next_seg, first = TRUE; + uint8 *p = f->stream; + + if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag + for (; s < f->segment_count; ++s) { + p += f->segments[s]; + if (f->segments[s] < 255) // stop at first short segment + break; + } + // either this continues, or it ends it... + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); + if (p[4] != 0) return error(f, VORBIS_invalid_stream); + if (first) { // the first segment must NOT have 'continued_packet', later ones MUST + if (f->previous_length) + if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + // if no previous length, we're resynching, so we can come in on a continued-packet, + // which we'll just drop + } else { + if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + } + n = p[26]; // segment counts + q = p+27; // q points to segment table + p = q + n; // advance past header + // make sure we've read the segment table + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + for (s=0; s < n; ++s) { + p += q[s]; + if (q[s] < 255) + break; + } + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int start_decoder(vorb *f) +{ + uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + f->first_decode = TRUE; + + if (!start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) { + // check for the Ogg skeleton fishead identifying header to refine our error + if (f->segments[0] == 64 && + getn(f, header, 6) && + header[0] == 'f' && + header[1] == 'i' && + header[2] == 's' && + header[3] == 'h' && + header[4] == 'e' && + header[5] == 'a' && + get8(f) == 'd' && + get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); + else + return error(f, VORBIS_invalid_first_page); + } + + // read packet + // check packet header + if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); + if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); + // vorbis_version + if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); + f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); + f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); + get32(f); // bitrate_maximum + get32(f); // bitrate_nominal + get32(f); // bitrate_minimum + x = get8(f); + { + int log0,log1; + log0 = x & 15; + log1 = x >> 4; + f->blocksize_0 = 1 << log0; + f->blocksize_1 = 1 << log1; + if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); + if (log0 > log1) return error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = get8(f); + if (!(x & 1)) return error(f, VORBIS_invalid_first_page); + + // second packet! + if (!start_page(f)) return FALSE; + + if (!start_packet(f)) return FALSE; + + if (!next_segment(f)) return FALSE; + + if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + //file vendor + len = get32_packet(f); + f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->vendor == NULL) return error(f, VORBIS_outofmem); + for(i=0; i < len; ++i) { + f->vendor[i] = get8_packet(f); + } + f->vendor[len] = (char)'\0'; + //user comments + f->comment_list_length = get32_packet(f); + f->comment_list = NULL; + if (f->comment_list_length > 0) + { + f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length)); + if (f->comment_list == NULL) return error(f, VORBIS_outofmem); + } + + for(i=0; i < f->comment_list_length; ++i) { + len = get32_packet(f); + f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); + + for(j=0; j < len; ++j) { + f->comment_list[i][j] = get8_packet(f); + } + f->comment_list[i][len] = (char)'\0'; + } + + // framing_flag + x = get8_packet(f); + if (!(x & 1)) return error(f, VORBIS_invalid_setup); + + + skip(f, f->bytes_in_seg); + f->bytes_in_seg = 0; + + do { + len = next_segment(f); + skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (IS_PUSH_MODE(f)) { + if (!is_whole_packet_present(f)) { + // convert error in ogg header to write type + if (f->error == VORBIS_invalid_stream) + f->error = VORBIS_invalid_setup; + return FALSE; + } + } + #endif + + crc32_init(); // always init it, to avoid multithread race conditions + + if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = get_bits(f,8) + 1; + f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + uint32 *values; + int ordered, sorted_count; + int total=0; + uint8 *lengths; + Codebook *c = f->codebooks+i; + CHECK(f); + x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); + c->dimensions = (get_bits(f, 8)<<8) + x; + x = get_bits(f, 8); + y = get_bits(f, 8); + c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; + ordered = get_bits(f,1); + c->sparse = ordered ? 0 : get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (uint8 *) setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + + if (!lengths) return error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = get_bits(f, ilog(limit)); + if (current_length >= 32) return error(f, VORBIS_invalid_setup); + if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + memset(lengths + current_entry, current_length, n); + current_entry += n; + ++current_length; + } + } else { + for (j=0; j < c->entries; ++j) { + int present = c->sparse ? get_bits(f,1) : 1; + if (present) { + lengths[j] = get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return error(f, VORBIS_invalid_setup); + } else { + lengths[j] = NO_CODE; + } + } + } + + if (c->sparse && total >= c->entries >> 2) { + // convert sparse items to non-sparse! + if (c->entries > (int) f->setup_temp_memory_required) + f->setup_temp_memory_required = c->entries; + + c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + lengths = c->codeword_lengths; + c->sparse = 0; + } + + // compute the size of the sorted tables + if (c->sparse) { + sorted_count = total; + } else { + sorted_count = 0; + #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + for (j=0; j < c->entries; ++j) + if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) + ++sorted_count; + #endif + } + + c->sorted_entries = sorted_count; + values = NULL; + + CHECK(f); + if (!c->sparse) { + c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return error(f, VORBIS_outofmem); + } + size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; + if (size > f->setup_temp_memory_required) + f->setup_temp_memory_required = size; + } + + if (!compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) setup_temp_free(f, values, 0); + return error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + // allocate an extra slot at the front so that c->sorted_values[-1] is defined + // so that we can catch that case without an extra if + c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + compute_accelerated_huffman(c); + + CHECK(f); + c->lookup_type = get_bits(f, 4); + if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + uint16 *mults; + c->minimum_value = float32_unpack(get_bits(f, 32)); + c->delta_value = float32_unpack(get_bits(f, 32)); + c->value_bits = get_bits(f, 4)+1; + c->sequence_p = get_bits(f,1); + if (c->lookup_type == 1) { + int values = lookup1_values(c->entries, c->dimensions); + if (values < 0) return error(f, VORBIS_invalid_setup); + c->lookup_values = (uint32) values; + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = get_bits(f, c->value_bits); + if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + mults[j] = q; + } + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int len, sparse = c->sparse; + float last=0; + // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop + if (sparse) { + if (c->sorted_entries == 0) goto skip; + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + len = sparse ? c->sorted_entries : c->entries; + for (j=0; j < len; ++j) { + unsigned int z = sparse ? c->sorted_values[j] : j; + unsigned int div=1; + for (k=0; k < c->dimensions; ++k) { + int off = (z / div) % c->lookup_values; + float val = mults[off]*c->delta_value + c->minimum_value + last; + c->multiplicands[j*c->dimensions + k] = val; + if (c->sequence_p) + last = val; + if (k+1 < c->dimensions) { + if (div > UINT_MAX / (unsigned int) c->lookup_values) { + setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + CHECK(f); + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + for (j=0; j < (int) c->lookup_values; ++j) { + float val = mults[j] * c->delta_value + c->minimum_value + last; + c->multiplicands[j] = val; + if (c->sequence_p) + last = val; + } + } +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + skip:; +#endif + setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + CHECK(f); + } + CHECK(f); + } + + // time domain transfers (notused) + + x = get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + uint32 z = get_bits(f, 16); + if (z != 0) return error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = get_bits(f, 6)+1; + f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = get_bits(f, 16); + if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + Floor0 *g = &f->floor_config[i].floor0; + g->order = get_bits(f,8); + g->rate = get_bits(f,16); + g->bark_map_size = get_bits(f,16); + g->amplitude_bits = get_bits(f,6); + g->amplitude_offset = get_bits(f,8); + g->number_of_books = get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = get_bits(f,8); + return error(f, VORBIS_feature_not_supported); + } else { + stbv__floor_ordering p[31*8+2]; + Floor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = get_bits(f, 4); + if (g->partition_class_list[j] > max_class) + max_class = g->partition_class_list[j]; + } + for (j=0; j <= max_class; ++j) { + g->class_dimensions[j] = get_bits(f, 3)+1; + g->class_subclasses[j] = get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = (int16)get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = get_bits(f,2)+1; + g->rangebits = get_bits(f,4); + g->Xlist[0] = 0; + g->Xlist[1] = 1 << g->rangebits; + g->values = 2; + for (j=0; j < g->partitions; ++j) { + int c = g->partition_class_list[j]; + for (k=0; k < g->class_dimensions[c]; ++k) { + g->Xlist[g->values] = get_bits(f, g->rangebits); + ++g->values; + } + } + // precompute the sorting + for (j=0; j < g->values; ++j) { + p[j].x = g->Xlist[j]; + p[j].id = j; + } + qsort(p, g->values, sizeof(p[0]), point_compare); + for (j=0; j < g->values-1; ++j) + if (p[j].x == p[j+1].x) + return error(f, VORBIS_invalid_setup); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (uint8) p[j].id; + // precompute the neighbors + for (j=2; j < g->values; ++j) { + int low = 0,hi = 0; + neighbors(g->Xlist, j, &low,&hi); + g->neighbors[j][0] = low; + g->neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // Residue + f->residue_count = get_bits(f, 6)+1; + f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return error(f, VORBIS_outofmem); + memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); + for (i=0; i < f->residue_count; ++i) { + uint8 residue_cascade[64]; + Residue *r = f->residue_config+i; + f->residue_types[i] = get_bits(f, 16); + if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); + r->begin = get_bits(f, 24); + r->end = get_bits(f, 24); + if (r->end < r->begin) return error(f, VORBIS_invalid_setup); + r->part_size = get_bits(f,24)+1; + r->classifications = get_bits(f,6)+1; + r->classbook = get_bits(f,8); + if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + uint8 high_bits=0; + uint8 low_bits=get_bits(f,3); + if (get_bits(f,1)) + high_bits = get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < r->classifications; ++j) { + for (k=0; k < 8; ++k) { + if (residue_cascade[j] & (1 << k)) { + r->residue_books[j][k] = get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } else { + r->residue_books[j][k] = -1; + } + } + } + // precompute the classifications[] array to avoid inner-loop mod/divide + // call it 'classdata' since we already have r->classifications + r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return error(f, VORBIS_outofmem); + memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + for (j=0; j < f->codebooks[r->classbook].entries; ++j) { + int classwords = f->codebooks[r->classbook].dimensions; + int temp = j; + r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = get_bits(f,6)+1; + f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + Mapping *m = f->mapping + i; + int mapping_type = get_bits(f,16); + if (mapping_type != 0) return error(f, VORBIS_invalid_setup); + m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return error(f, VORBIS_outofmem); + if (get_bits(f,1)) + m->submaps = get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (get_bits(f,1)) { + m->coupling_steps = get_bits(f,8)+1; + if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); + m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); + } + } else + // @SPECIFICATION: this case is missing from the spec + for (j=0; j < f->channels; ++j) + m->chan[j].mux = 0; + + for (j=0; j < m->submaps; ++j) { + get_bits(f,8); // discard + m->submap_floor[j] = get_bits(f,8); + m->submap_residue[j] = get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + Mode *m = f->mode_config+i; + m->blockflag = get_bits(f,1); + m->windowtype = get_bits(f,16); + m->transformtype = get_bits(f,16); + m->mapping = get_bits(f,8); + if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); + } + + flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); + #endif + } + + if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; + f->blocksize[0] = f->blocksize_0; + f->blocksize[1] = f->blocksize_1; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (integer_divide_table[1][1]==0) + for (i=0; i < DIVTAB_NUMER; ++i) + for (j=1; j < DIVTAB_DENOM; ++j) + integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + Residue *r = f->residue_config + i; + unsigned int actual_size = f->blocksize_1 / 2; + unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; + unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + if (part_read > max_part_read) + max_part_read = part_read; + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); + #else + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); + #endif + + // maximum reasonable partition size is f->blocksize_1 + + f->temp_memory_required = classify_mem; + if (imdct_mem > f->temp_memory_required) + f->temp_memory_required = imdct_mem; + } + + + if (f->alloc.alloc_buffer) { + assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); + // check if there's enough temp memory so we don't error later + if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) + return error(f, VORBIS_outofmem); + } + + // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page + // without PAGEFLAG_continued_packet, so this either points to the first page, or + // the page after the end of the headers. It might be cleaner to point to a page + // in the middle of the headers, when that's the page where the first audio packet + // starts, but we'd have to also correctly skip the end of any continued packet in + // stb_vorbis_seek_start. + if (f->next_seg == -1) { + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + } else { + f->first_audio_page_offset = 0; + } + + return TRUE; +} + +static void vorbis_deinit(stb_vorbis *p) +{ + int i,j; + + setup_free(p, p->vendor); + for (i=0; i < p->comment_list_length; ++i) { + setup_free(p, p->comment_list[i]); + } + setup_free(p, p->comment_list); + + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + Residue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + setup_free(p, r->classdata[j]); + setup_free(p, r->classdata); + } + setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + Codebook *c = p->codebooks + i; + setup_free(p, c->codeword_lengths); + setup_free(p, c->multiplicands); + setup_free(p, c->codewords); + setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + setup_free(p, p->codebooks); + } + setup_free(p, p->floor_config); + setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + setup_free(p, p->mapping[i].chan); + setup_free(p, p->mapping); + } + CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + setup_free(p, p->channel_buffers[i]); + setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + setup_free(p, p->floor_buffers[i]); + #endif + setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + setup_free(p, p->A[i]); + setup_free(p, p->B[i]); + setup_free(p, p->C[i]); + setup_free(p, p->window[i]); + setup_free(p, p->bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + vorbis_deinit(p); + setup_free(p,p); +} + +static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) +{ + memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start + if (z) { + p->alloc = *z; + p->alloc.alloc_buffer_length_in_bytes &= ~7; + p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; + } + p->eof = 0; + p->error = VORBIS__no_error; + p->stream = NULL; + p->codebooks = NULL; + p->page_crc_tests = -1; + #ifndef STB_VORBIS_NO_STDIO + p->close_on_free = FALSE; + p->f = NULL; + #endif +} + +int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) +{ + stb_vorbis_info d; + d.channels = f->channels; + d.sample_rate = f->sample_rate; + d.setup_memory_required = f->setup_memory_required; + d.setup_temp_memory_required = f->setup_temp_memory_required; + d.temp_memory_required = f->temp_memory_required; + d.max_frame_size = f->blocksize_1 >> 1; + return d; +} + +stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) +{ + stb_vorbis_comment d; + d.vendor = f->vendor; + d.comment_list_length = f->comment_list_length; + d.comment_list = f->comment_list; + return d; +} + +int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +void stb_vorbis_flush_pushdata(stb_vorbis *f) +{ + f->previous_length = 0; + f->page_crc_tests = 0; + f->discard_samples_deferred = 0; + f->current_loc_valid = FALSE; + f->first_decode = FALSE; + f->samples_output = 0; + f->channel_buffer_start = 0; + f->channel_buffer_end = 0; +} + +static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) +{ + int i,n; + for (i=0; i < f->page_crc_tests; ++i) + f->scan[i].bytes_done = 0; + + // if we have room for more scans, search for them first, because + // they may cause us to stop early if their header is incomplete + if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { + if (data_len < 4) return 0; + data_len -= 3; // need to look for 4-byte sequence, so don't miss + // one that straddles a boundary + for (i=0; i < data_len; ++i) { + if (data[i] == 0x4f) { + if (0==memcmp(data+i, ogg_page_header, 4)) { + int j,len; + uint32 crc; + // make sure we have the whole page header + if (i+26 >= data_len || i+27+data[i+26] >= data_len) { + // only read up to this page start, so hopefully we'll + // have the whole page header start next time + data_len = i; + break; + } + // ok, we have it all; compute the length of the page + len = 27 + data[i+26]; + for (j=0; j < data[i+26]; ++j) + len += data[i+27+j]; + // scan everything up to the embedded crc (which we must 0) + crc = 0; + for (j=0; j < 22; ++j) + crc = crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = crc32_update(crc, 0); + // len is the total number of bytes we need to scan + n = f->page_crc_tests++; + f->scan[n].bytes_left = len-j; + f->scan[n].crc_so_far = crc; + f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); + // if the last frame on a page is continued to the next, then + // we can't recover the sample_loc immediately + if (data[i+27+data[i+26]-1] == 255) + f->scan[n].sample_loc = ~0; + else + f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); + f->scan[n].bytes_done = i+j; + if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) + break; + // keep going if we still have room for more + } + } + } + } + + for (i=0; i < f->page_crc_tests;) { + uint32 crc; + int j; + int n = f->scan[i].bytes_done; + int m = f->scan[i].bytes_left; + if (m > data_len - n) m = data_len - n; + // m is the bytes to scan in the current chunk + crc = f->scan[i].crc_so_far; + for (j=0; j < m; ++j) + crc = crc32_update(crc, data[n+j]); + f->scan[i].bytes_left -= m; + f->scan[i].crc_so_far = crc; + if (f->scan[i].bytes_left == 0) { + // does it match? + if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { + // Houston, we have page + data_len = n+m; // consumption amount is wherever that scan ended + f->page_crc_tests = -1; // drop out of page scan mode + f->previous_length = 0; // decode-but-don't-output one frame + f->next_seg = -1; // start a new page + f->current_loc = f->scan[i].sample_loc; // set the current sample location + // to the amount we'd have decoded had we decoded this page + f->current_loc_valid = f->current_loc != ~0U; + return data_len; + } + // delete entry + f->scan[i] = f->scan[--f->page_crc_tests]; + } else { + ++i; + } + } + + return data_len; +} + +// return value: number of bytes we used +int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const uint8 *data, int data_len, // the memory available for decoding + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ) +{ + int i; + int len,right,left; + + if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); + } + + f->stream = (uint8 *) data; + f->stream_end = (uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!is_whole_packet_present(f)) { + *samples = 0; + return 0; + } + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + // save the actual error we encountered + enum STBVorbisError error = f->error; + if (error == VORBIS_bad_packet_type) { + // flush and resynch + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + if (error == VORBIS_continued_packet_flag_invalid) { + if (f->previous_length == 0) { + // we may be resynching, in which case it's ok to hit one + // of these; just discard the packet + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + } + // if we get an error while parsing, what to do? + // well, it DEFINITELY won't work to continue from where we are! + stb_vorbis_flush_pushdata(f); + // restore the error that actually made us bail + f->error = error; + *samples = 0; + return 1; + } + + // success! + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + if (channels) *channels = f->channels; + *samples = len; + *output = f->outputs; + return (int) (f->stream - data); +} + +stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char *data, int data_len, // the memory available for decoding + int *data_used, // only defined if result is not NULL + int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + data_len; + p.push_mode = TRUE; + if (!start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + vorbis_deinit(&p); + return NULL; + } + f = vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); + #ifndef STB_VORBIS_NO_STDIO + return (unsigned int) (ftell(f->f) - f->f_start); + #endif +} + +#ifndef STB_VORBIS_NO_PULLDATA_API +// +// DATA-PULLING API +// + +static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = get8(f); + if (n == 0x4f) { // page header candidate + unsigned int retry_loc = stb_vorbis_get_file_offset(f); + int i; + // check if we're off the end of a file_section stream + if (retry_loc - 25 > f->stream_len) + return 0; + // check the rest of the header + for (i=1; i < 4; ++i) + if (get8(f) != ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + uint8 header[27]; + uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = get8(f); + if (f->eof) return 0; + if (header[4] != 0) goto invalid; + goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24); + for (i=22; i < 26; ++i) + header[i] = 0; + crc = 0; + for (i=0; i < 27; ++i) + crc = crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = get8(f); + crc = crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = crc32_update(crc, get8(f)); + // finished parsing probable page + if (crc == goal) { + // we could now check that it's either got the last + // page flag set, OR it's followed by the capture + // pattern, but I guess TECHNICALLY you could have + // a file with garbage between each ogg page and recover + // from it automatically? So even though that paranoia + // might decrease the chance of an invalid decode by + // another 2^32, not worth it since it would hose those + // invalid-but-useful files? + if (end) + *end = stb_vorbis_get_file_offset(f); + if (last) { + if (header[5] & 0x04) + *last = 1; + else + *last = 0; + } + set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + set_file_offset(f, retry_loc); + } + } +} + + +#define SAMPLE_unknown 0xffffffff + +// seeking is implemented with a binary search, which narrows down the range to +// 64K, before using a linear search (because finding the synchronization +// pattern can be expensive, and the chance we'd find the end page again is +// relatively high for small ranges) +// +// two initial interpolation-style probes are used at the start of the search +// to try to bound either side of the binary search sensibly, while still +// working in O(log n) time if they fail. + +static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) +{ + uint8 header[27], lacing[255]; + int i,len; + + // record where the page starts + z->page_start = stb_vorbis_get_file_offset(f); + + // parse the header + getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + getn(f, lacing, header[26]); + + // determine the length of the payload + len = 0; + for (i=0; i < header[26]; ++i) + len += lacing[i]; + + // this implies where the page ends + z->page_end = z->page_start + 27 + header[26] + len; + + // read the last-decoded sample out of the data + z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); + + // restore file state to where we were + set_file_offset(f, z->page_start); + return 1; +} + +// rarely used function to seek back to the preceding page while finding the +// start of a packet +static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) +{ + unsigned int previous_safe, end; + + // now we want to seek back 64K from the limit + if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) + previous_safe = limit_offset - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + + while (vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + set_file_offset(f, end); + } + + return 0; +} + +// implements the search logic for finding a page and starting decoding. if +// the function succeeds, current_loc_valid will be true and current_loc will +// be less than or equal to the provided sample number (the closer the +// better). +static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) +{ + ProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + uint32 delta, stream_length, padding, last_sample_limit; + double offset = 0.0, bytes_per_sample = 0.0; + int probe = 0; + + // find the last page and validate the target sample + stream_length = stb_vorbis_stream_length_in_samples(f); + if (stream_length == 0) return error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); + + // this is the maximum difference between the window-center (which is the + // actual granule position value), and the right-start (which the spec + // indicates should be the granule position (give or take one)). + padding = ((f->blocksize_1 - f->blocksize_0) >> 2); + if (sample_number < padding) + last_sample_limit = 0; + else + last_sample_limit = sample_number - padding; + + left = f->p_first; + while (left.last_decoded_sample == ~0U) { + // (untested) the first page does not have a 'last_decoded_sample' + set_file_offset(f, left.page_end); + if (!get_seek_page_info(f, &left)) goto error; + } + + right = f->p_last; + assert(right.last_decoded_sample != ~0U); + + // starting from the start is handled differently + if (last_sample_limit <= left.last_decoded_sample) { + if (stb_vorbis_seek_start(f)) { + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + } + return 0; + } + + while (left.page_end != right.page_start) { + assert(left.page_end < right.page_start); + // search range in bytes + delta = right.page_start - left.page_end; + if (delta <= 65536) { + // there's only 64K left to search - handle it linearly + set_file_offset(f, left.page_end); + } else { + if (probe < 2) { + if (probe == 0) { + // first probe (interpolate) + double data_bytes = right.page_end - left.page_start; + bytes_per_sample = data_bytes / right.last_decoded_sample; + offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); + } else { + // second probe (try to bound the other side) + double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; + if (error >= 0 && error < 8000) error = 8000; + if (error < 0 && error > -8000) error = -8000; + offset += error * 2; + } + + // ensure the offset is valid + if (offset < left.page_end) + offset = left.page_end; + if (offset > right.page_start - 65536) + offset = right.page_start - 65536; + + set_file_offset(f, (unsigned int) offset); + } else { + // binary search for large ranges (offset by 32K to ensure + // we don't hit the right page) + set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + set_file_offset(f, mid.page_end); + assert(mid.page_start < right.page_start); + } + + // if we've just found the last page again then we're in a tricky file, + // and we're close enough (if it wasn't an interpolation probe). + if (mid.page_start == right.page_start) { + if (probe >= 2 || delta <= 65536) + break; + } else { + if (last_sample_limit < mid.last_decoded_sample) + right = mid; + else + left = mid; + } + + ++probe; + } + + // seek back to start of the last packet + page_start = left.page_start; + set_file_offset(f, page_start); + if (!start_page(f)) return error(f, VORBIS_seek_failed); + end_pos = f->end_seg_with_known_loc; + assert(end_pos >= 0); + + for (;;) { + for (i = end_pos; i > 0; --i) + if (f->segments[i-1] != 255) + break; + + start_seg_with_known_loc = i; + + if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!start_page(f)) goto error; + end_pos = f->segment_count - 1; + } + + // prepare to start decoding + f->current_loc_valid = FALSE; + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + f->previous_length = 0; + f->next_seg = start_seg_with_known_loc; + + for (i = 0; i < start_seg_with_known_loc; i++) + skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return error(f, VORBIS_seek_failed); +} + +// the same as vorbis_decode_initial, but without advancing +static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + int bits_read, bytes_read; + + if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) + return 0; + + // either 1 or 2 bytes were read, figure out which so we can rewind + bits_read = 1 + ilog(f->mode_count-1); + if (f->mode_config[*mode].blockflag) + bits_read += 2; + bytes_read = (bits_read + 7) / 8; + + f->bytes_in_seg += bytes_read; + f->packet_bytes -= bytes_read; + skip(f, -bytes_read); + if (f->next_seg == -1) + f->next_seg = f->segment_count - 1; + else + f->next_seg--; + f->valid_bits = 0; + + return 1; +} + +int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + uint32 max_frame_samples; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!seek_to_sample_coarse(f, sample_number)) + return 0; + + assert(f->current_loc_valid); + assert(f->current_loc <= sample_number); + + // linear search for the relevant packet + max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; + while (f->current_loc < sample_number) { + int left_start, left_end, right_start, right_end, mode, frame_samples; + if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return error(f, VORBIS_seek_failed); + // calculate the number of samples returned by the next frame + frame_samples = right_start - left_start; + if (f->current_loc + frame_samples > sample_number) { + return 1; // the next frame will contain the sample + } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { + // there's a chance the frame after this could contain the sample + vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + maybe_start_packet(f); + flush_packet(f); + } + } + // the next frame should start with the sample + if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); + return 1; +} + +int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) +{ + if (!stb_vorbis_seek_frame(f, sample_number)) + return 0; + + if (sample_number != f->current_loc) { + int n; + uint32 frame_start = f->current_loc; + stb_vorbis_get_frame_float(f, &n, NULL); + assert(sample_number > frame_start); + assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); + f->channel_buffer_start += (sample_number - frame_start); + } + + return 1; +} + +int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } + set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return vorbis_pump_first_frame(f); +} + +unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + uint32 lo,hi; + char header[6]; + + // first, store the current decode position so we can restore it + restore_offset = stb_vorbis_get_file_offset(f); + + // now we want to seek back 64K from the end (the last page must + // be at most a little less than 64K, but let's allow a little slop) + if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) + previous_safe = f->stream_len - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + // previous_safe is now our candidate 'earliest known place that seeking + // to will lead to the final page' + + if (!vorbis_find_page(f, &end, &last)) { + // if we can't find a page, we're hosed! + f->error = VORBIS_cant_find_last_page; + f->total_samples = 0xffffffff; + goto done; + } + + // check if there are more pages + last_page_loc = stb_vorbis_get_file_offset(f); + + // stop when the last_page flag is set, not when we reach eof; + // this allows us to stop short of a 'file_section' end without + // explicitly checking the length of the section + while (!last) { + set_file_offset(f, end); + if (!vorbis_find_page(f, &end, &last)) { + // the last page we found didn't have the 'last page' flag + // set. whoops! + break; + } + //previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging + last_page_loc = stb_vorbis_get_file_offset(f); + } + + set_file_offset(f, last_page_loc); + + // parse the header + getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = get32(f); + hi = get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = SAMPLE_unknown; + goto done; + } + if (hi) + lo = 0xfffffffe; // saturate + f->total_samples = lo; + + f->p_last.page_start = last_page_loc; + f->p_last.page_end = end; + f->p_last.last_decoded_sample = lo; + + done: + set_file_offset(f, restore_offset); + } + return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; +} + +float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + f->channel_buffer_start = left; + f->channel_buffer_end = left+len; + + if (channels) *channels = f->channels; + if (output) *output = f->outputs; + return len; +} + +#ifndef STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.f = file; + p.f_start = (uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) +{ + unsigned int len, start; + start = (unsigned int) ftell(file); + fseek(file, 0, SEEK_END); + len = (unsigned int) (ftell(file) - start); + fseek(file, start, SEEK_SET); + return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); +} + +stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) +{ + FILE *f; +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + if (0 != fopen_s(&f, filename, "rb")) + f = NULL; +#else + f = fopen(filename, "rb"); +#endif + if (f) + return stb_vorbis_open_file(f, TRUE, error, alloc); + if (error) *error = VORBIS_file_open_failure; + return NULL; +} +#endif // STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + if (!data) { + if (error) *error = VORBIS_unexpected_eof; + return NULL; + } + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + len; + p.stream_start = (uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define PLAYBACK_MONO 1 +#define PLAYBACK_LEFT 2 +#define PLAYBACK_RIGHT 4 + +#define L (PLAYBACK_LEFT | PLAYBACK_MONO) +#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) +#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) + +static int8 channel_position[7][6] = +{ + { 0 }, + { C }, + { L, R }, + { L, C, R }, + { L, R, L, R }, + { L, C, R, L, R }, + { L, C, R, L, R, C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define FASTDEF(x) float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) + #define check_endianness() +#else + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define check_endianness() + #define FASTDEF(x) +#endif + +static void copy_samples(short *dest, float *src, int len) +{ + int i; + check_endianness(); + for (i=0; i < len; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + dest[i] = v; + } +} + +static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE; + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE) { + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + if (channel_position[num_c][j] & mask) { + for (i=0; i < n; ++i) + buffer[i] += data[j][d_offset+o+i]; + } + } + for (i=0; i < n; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE >> 1; + // o is the offset in the source data + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) { + // o2 is the offset in the output data + int o2 = o << 1; + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); + if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_RIGHT) { + for (i=0; i < n; ++i) { + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } + } + for (i=0; i < (n<<1); ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o2+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) +{ + int i; + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + for (i=0; i < limit; ++i) + copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); + for ( ; i < buf_c; ++i) + memset(buffer[i]+b_offset, 0, sizeof(short) * samples); + } +} + +int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) +{ + float **output = NULL; + int len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len > num_samples) len = num_samples; + if (len) + convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + compute_stereo_samples(buffer, data_c, data, d_offset, len); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + int j; + for (j=0; j < len; ++j) { + for (i=0; i < limit; ++i) { + FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + *buffer++ = v; + } + for ( ; i < buf_c; ++i) + *buffer++ = 0; + } + } +} + +int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) +{ + float **output; + int len; + if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); + len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len) { + if (len*num_c > num_shorts) len = num_shorts / num_c; + convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) +{ + float **outputs; + int len = num_shorts / channels; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); + buffer += k*channels; + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) +{ + float **outputs; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +#ifndef STB_VORBIS_NO_STDIO +int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // NO_STDIO + +int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // STB_VORBIS_NO_INTEGER_CONVERSION + +int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) +{ + float **outputs; + int len = num_floats / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int i,j; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + for (j=0; j < k; ++j) { + for (i=0; i < z; ++i) + *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; + for ( ; i < channels; ++i) + *buffer++ = 0; + } + n += k; + f->channel_buffer_start += k; + if (n == len) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} + +int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < num_samples) { + int i; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= num_samples) k = num_samples - n; + if (k) { + for (i=0; i < z; ++i) + memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); + for ( ; i < channels; ++i) + memset(buffer[i]+n, 0, sizeof(float) * k); + } + n += k; + f->channel_buffer_start += k; + if (n == num_samples) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} +#endif // STB_VORBIS_NO_PULLDATA_API + +/* Version history + 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 + found with Mayhem by ForAllSecure + 1.16 - 2019-03-04 - fix warnings + 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found + 1.14 - 2018-02-11 - delete bogus dealloca usage + 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) + 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files + 1.11 - 2017-07-23 - fix MinGW compilation + 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory + 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version + 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; + avoid discarding last frame of audio data + 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API + some more crash fixes when out of memory or with corrupt files + 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) + some crash fixes when out of memory or with corrupt files + 1.05 - 2015-04-19 - don't define __forceinline if it's redundant + 1.04 - 2014-08-27 - fix missing const-correct case in API + 1.03 - 2014-08-07 - Warning fixes + 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows + 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float + 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel + (API change) report sample rate for decode-full-file funcs + 0.99996 - bracket #include for macintosh compilation by Laurent Gomila + 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem + 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence + 0.99993 - remove assert that fired on legal files with empty tables + 0.99992 - rewind-to-start + 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo + 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ + 0.9998 - add a full-decode function with a memory source + 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition + 0.9996 - query length of vorbis stream in samples/seconds + 0.9995 - bugfix to another optimization that only happened in certain files + 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors + 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation + 0.9992 - performance improvement of IMDCT; now performs close to reference implementation + 0.9991 - performance improvement of IMDCT + 0.999 - (should have been 0.9990) performance improvement of IMDCT + 0.998 - no-CRT support from Casey Muratori + 0.997 - bugfixes for bugs found by Terje Mathisen + 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen + 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen + 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen + 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen + 0.992 - fixes for MinGW warning + 0.991 - turn fast-float-conversion on by default + 0.990 - fix push-mode seek recovery if you seek into the headers + 0.98b - fix to bad release of 0.98 + 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode + 0.97 - builds under c++ (typecasting, don't use 'class' keyword) + 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code + 0.95 - clamping code for 16-bit functions + 0.94 - not publically released + 0.93 - fixed all-zero-floor case (was decoding garbage) + 0.92 - fixed a memory leak + 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION + 0.90 - first public release +*/ + +#endif // STB_VORBIS_HEADER_ONLY + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/