mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-20 15:02:37 +00:00
Merge branch 'master' into acs
This commit is contained in:
commit
1caf255f5c
140 changed files with 13719 additions and 2955 deletions
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
$<$<VERSION_LESS:$<C_COMPILER_VERSION>,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)
|
||||
|
|
|
|||
37
src/audio/CMakeLists.txt
Normal file
37
src/audio/CMakeLists.txt
Normal file
|
|
@ -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
|
||||
)
|
||||
206
src/audio/chunk_load.cpp
Normal file
206
src/audio/chunk_load.cpp
Normal file
|
|
@ -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 <stb_vorbis.h>
|
||||
|
||||
#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<SoundChunk>&& chunk)
|
||||
: chunk_(std::forward<std::unique_ptr<SoundChunk>>(chunk)) {}
|
||||
|
||||
virtual size_t generate(tcb::span<Sample<1>> buffer) override final {
|
||||
if (!chunk_)
|
||||
return 0;
|
||||
|
||||
size_t written = 0;
|
||||
for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) {
|
||||
buffer[written] = chunk_->samples[pos_];
|
||||
written++;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<SoundChunk> chunk_;
|
||||
size_t pos_ {0};
|
||||
};
|
||||
|
||||
template <class I>
|
||||
std::vector<Sample<1>> generate_to_vec(I& source, std::size_t estimate = 0) {
|
||||
std::vector<Sample<1>> generated;
|
||||
|
||||
size_t total = 0;
|
||||
size_t read = 0;
|
||||
generated.reserve(estimate);
|
||||
do {
|
||||
generated.resize(total + 4096);
|
||||
read = source.generate(tcb::span {generated.data() + total, 4096});
|
||||
total += read;
|
||||
} while (read != 0);
|
||||
generated.resize(total);
|
||||
return generated;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_dmx(tcb::span<std::byte> 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<Sample<1>> 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<SoundChunkSource> chunk_source =
|
||||
std::make_unique<SoundChunkSource>(std::make_unique<SoundChunk>(SoundChunk {std::move(samples)}));
|
||||
Resampler<1> resampler(std::move(chunk_source), rate / static_cast<float>(kSampleRate));
|
||||
|
||||
std::vector<Sample<1>> resampled;
|
||||
|
||||
size_t total = 0;
|
||||
size_t read = 0;
|
||||
resampled.reserve(samples_len * (static_cast<float>(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<SoundChunk> try_load_wav(tcb::span<std::byte> 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<WavPlayer>(std::move(wav)),
|
||||
sample_rate / static_cast<float>(kSampleRate));
|
||||
|
||||
SoundChunk chunk {generate_to_vec(resampler)};
|
||||
return chunk;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_ogg(tcb::span<std::byte> data) {
|
||||
std::shared_ptr<audio::OggPlayer<1>> player;
|
||||
try {
|
||||
io::SpanStream data_stream {data};
|
||||
audio::Ogg ogg = audio::load_ogg(data_stream);
|
||||
player = std::make_shared<audio::OggPlayer<1>>(std::move(ogg));
|
||||
} catch (...) {
|
||||
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<Sample<1>> resampled {generate_to_vec(resampler)};
|
||||
|
||||
SoundChunk chunk {std::move(resampled)};
|
||||
return chunk;
|
||||
}
|
||||
|
||||
optional<SoundChunk> try_load_gme(tcb::span<std::byte> data) {
|
||||
std::shared_ptr<audio::GmePlayer<1>> player;
|
||||
try {
|
||||
if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) {
|
||||
io::SpanStream stream {data};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
player = std::make_shared<GmePlayer<1>>(std::move(gme));
|
||||
} else {
|
||||
io::ZlibInputStream stream {io::SpanStream(data)};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
player = std::make_shared<GmePlayer<1>>(std::move(gme));
|
||||
}
|
||||
} catch (...) {
|
||||
return nullopt;
|
||||
}
|
||||
std::vector<Sample<1>> samples {generate_to_vec(*player)};
|
||||
SoundChunk chunk {std::move(samples)};
|
||||
return chunk;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data) {
|
||||
optional<SoundChunk> 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;
|
||||
}
|
||||
27
src/audio/chunk_load.hpp
Normal file
27
src/audio/chunk_load.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
#include <optional>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "sound_chunk.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
/// @brief Try to load a chunk from the given byte span.
|
||||
std::optional<SoundChunk> try_load_chunk(tcb::span<std::byte> data);
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_CHUNK_LOAD_HPP__
|
||||
26
src/audio/expand_mono.cpp
Normal file
26
src/audio/expand_mono.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
|
||||
using std::size_t;
|
||||
|
||||
using namespace srb2::audio;
|
||||
|
||||
ExpandMono::~ExpandMono() = default;
|
||||
|
||||
size_t ExpandMono::filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) {
|
||||
for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++) {
|
||||
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());
|
||||
}
|
||||
27
src/audio/expand_mono.hpp
Normal file
27
src/audio/expand_mono.hpp
Normal file
|
|
@ -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 <tcb/span.hpp>
|
||||
|
||||
#include "filter.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
class ExpandMono : public Filter<1, 2> {
|
||||
public:
|
||||
virtual ~ExpandMono();
|
||||
virtual std::size_t filter(tcb::span<Sample<1>> input_buffer, tcb::span<Sample<2>> buffer) override final;
|
||||
};
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_EXPAND_MONO_HPP__
|
||||
40
src/audio/filter.cpp
Normal file
40
src/audio/filter.cpp
Normal file
|
|
@ -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 IC, size_t OC>
|
||||
size_t Filter<IC, OC>::generate(tcb::span<Sample<OC>> buffer) {
|
||||
input_buffer_.clear();
|
||||
input_buffer_.resize(buffer.size());
|
||||
|
||||
input_->generate(input_buffer_);
|
||||
|
||||
return filter(input_buffer_, buffer);
|
||||
}
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
void Filter<IC, OC>::bind(const shared_ptr<Source<IC>>& input) {
|
||||
input_ = input;
|
||||
}
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
Filter<IC, OC>::~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>;
|
||||
46
src/audio/filter.hpp
Normal file
46
src/audio/filter.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t IC, size_t OC>
|
||||
class Filter : public Source<OC> {
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<OC>> buffer) override;
|
||||
|
||||
void bind(const std::shared_ptr<Source<IC>>& input);
|
||||
|
||||
virtual std::size_t filter(tcb::span<Sample<IC>> input_buffer, tcb::span<Sample<OC>> buffer) = 0;
|
||||
|
||||
virtual ~Filter();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Source<IC>> input_;
|
||||
std::vector<Sample<IC>> 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__
|
||||
43
src/audio/gain.cpp
Normal file
43
src/audio/gain.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
|
||||
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 C>
|
||||
size_t Gain<C>::filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) {
|
||||
size_t written = std::min(buffer.size(), input_buffer.size());
|
||||
for (size_t i = 0; i < written; i++) {
|
||||
buffer[i] = input_buffer[i];
|
||||
buffer[i] *= gain_;
|
||||
gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void Gain<C>::gain(float new_gain) {
|
||||
new_gain_ = std::clamp(new_gain, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Gain<C>::~Gain() = default;
|
||||
|
||||
template class srb2::audio::Gain<1>;
|
||||
template class srb2::audio::Gain<2>;
|
||||
33
src/audio/gain.hpp
Normal file
33
src/audio/gain.hpp
Normal file
|
|
@ -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 <tcb/span.hpp>
|
||||
|
||||
#include "filter.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class Gain : public Filter<C, C> {
|
||||
public:
|
||||
virtual std::size_t filter(tcb::span<Sample<C>> input_buffer, tcb::span<Sample<C>> buffer) override final;
|
||||
void gain(float new_gain);
|
||||
|
||||
virtual ~Gain();
|
||||
|
||||
private:
|
||||
float new_gain_ {1.f};
|
||||
float gain_ {1.f};
|
||||
};
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_GAIN_HPP__
|
||||
141
src/audio/gme.cpp
Normal file
141
src/audio/gme.cpp
Normal file
|
|
@ -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 <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<std::byte>&& data) : memory_data_(std::move(data)), instance_(nullptr) {
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Gme::Gme(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
|
||||
_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<short> 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<float> 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<float>(info->length) / 1000.f;
|
||||
}
|
||||
|
||||
std::optional<float> 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<long>::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<const void*>(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<std::byte>();
|
||||
|
||||
result = gme_start_track(instance_, 0);
|
||||
if (result)
|
||||
throw GmeException(result);
|
||||
}
|
||||
74
src/audio/gme.hpp
Normal file
74
src/audio/gme.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <gme/gme.h>
|
||||
#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<std::byte> 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<std::byte>&& data);
|
||||
explicit Gme(tcb::span<std::byte> data);
|
||||
|
||||
std::size_t get_samples(tcb::span<short> buffer);
|
||||
void seek(int sample);
|
||||
|
||||
std::optional<float> duration_seconds() const;
|
||||
std::optional<float> loop_point_seconds() const;
|
||||
float position_seconds() const;
|
||||
|
||||
~Gme();
|
||||
|
||||
private:
|
||||
void _init_with_data();
|
||||
};
|
||||
|
||||
template <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Gme load_gme(I& stream) {
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Gme {std::move(data)};
|
||||
}
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_GME_HPP__
|
||||
73
src/audio/gme_player.cpp
Normal file
73
src/audio/gme_player.cpp
Normal file
|
|
@ -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 <size_t C>
|
||||
GmePlayer<C>::GmePlayer(Gme&& gme) : gme_(std::forward<Gme>(gme)), buf_() {
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
GmePlayer<C>::GmePlayer(GmePlayer<C>&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
GmePlayer<C>& GmePlayer<C>::operator=(GmePlayer<C>&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
GmePlayer<C>::~GmePlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t GmePlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
buf_.clear();
|
||||
buf_.resize(buffer.size() * 2);
|
||||
|
||||
std::size_t read = gme_.get_samples(tcb::make_span(buf_));
|
||||
buf_.resize(read);
|
||||
std::size_t new_samples = std::min((read / 2), buffer.size());
|
||||
for (std::size_t i = 0; i < new_samples; i++) {
|
||||
if constexpr (C == 1) {
|
||||
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 <size_t C>
|
||||
void GmePlayer<C>::seek(float position_seconds) {
|
||||
gme_.seek(static_cast<std::size_t>(position_seconds * 44100.f));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void GmePlayer<C>::reset() {
|
||||
gme_.seek(0);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> GmePlayer<C>::duration_seconds() const {
|
||||
return gme_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> GmePlayer<C>::loop_point_seconds() const {
|
||||
return gme_.loop_point_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float GmePlayer<C>::position_seconds() const {
|
||||
return gme_.position_seconds();
|
||||
}
|
||||
|
||||
template class srb2::audio::GmePlayer<1>;
|
||||
template class srb2::audio::GmePlayer<2>;
|
||||
51
src/audio/gme_player.hpp
Normal file
51
src/audio/gme_player.hpp
Normal file
|
|
@ -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 <optional>
|
||||
|
||||
#include "gme.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class GmePlayer : public Source<C> {
|
||||
Gme gme_;
|
||||
std::vector<short> buf_;
|
||||
|
||||
public:
|
||||
GmePlayer(Gme&& gme);
|
||||
GmePlayer(const GmePlayer<C>&) = delete;
|
||||
GmePlayer(GmePlayer<C>&& gme) noexcept;
|
||||
|
||||
~GmePlayer();
|
||||
|
||||
GmePlayer& operator=(const GmePlayer<C>&) = delete;
|
||||
GmePlayer& operator=(GmePlayer<C>&& rhs) noexcept;
|
||||
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override;
|
||||
|
||||
void seek(float position_seconds);
|
||||
|
||||
std::optional<float> duration_seconds() const;
|
||||
std::optional<float> 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__
|
||||
62
src/audio/mixer.cpp
Normal file
62
src/audio/mixer.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
|
||||
using srb2::audio::Mixer;
|
||||
using srb2::audio::Sample;
|
||||
using srb2::audio::Source;
|
||||
|
||||
namespace {
|
||||
|
||||
template <size_t C>
|
||||
void default_init_sample_buffer(Sample<C>* buffer, size_t size) {
|
||||
std::for_each(buffer, buffer + size, [](auto& i) { i = Sample<C> {}; });
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void mix_sample_buffers(Sample<C>* dst, size_t size, Sample<C>* src, size_t src_size) {
|
||||
for (size_t i = 0; i < size && i < src_size; i++) {
|
||||
dst[i] += src[i];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <size_t C>
|
||||
size_t Mixer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
buffer_.resize(buffer.size());
|
||||
|
||||
default_init_sample_buffer<C>(buffer.data(), buffer.size());
|
||||
|
||||
for (auto& source : sources_) {
|
||||
size_t read = source->generate(buffer_);
|
||||
|
||||
mix_sample_buffers<C>(buffer.data(), buffer.size(), buffer_.data(), read);
|
||||
}
|
||||
|
||||
// because we initialized the out-buffer, we always generate size samples
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void Mixer<C>::add_source(const shared_ptr<Source<C>>& source) {
|
||||
sources_.push_back(source);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Mixer<C>::~Mixer() = default;
|
||||
|
||||
template class srb2::audio::Mixer<1>;
|
||||
template class srb2::audio::Mixer<2>;
|
||||
41
src/audio/mixer.hpp
Normal file
41
src/audio/mixer.hpp
Normal file
|
|
@ -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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class Mixer : public Source<C> {
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> buffer) override final;
|
||||
|
||||
virtual ~Mixer();
|
||||
|
||||
void add_source(const std::shared_ptr<Source<C>>& source);
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Source<C>>> sources_;
|
||||
std::vector<Sample<C>> buffer_;
|
||||
};
|
||||
|
||||
extern template class Mixer<1>;
|
||||
extern template class Mixer<2>;
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_MIXER_HPP__
|
||||
422
src/audio/music_player.cpp
Normal file
422
src/audio/music_player.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <gme/gme.h>
|
||||
#include <stb_vorbis.h>
|
||||
#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<std::byte> data) : Impl() { _load(data); }
|
||||
|
||||
size_t generate(tcb::span<Sample<2>> 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<double>(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<std::byte> 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<audio::OggPlayer<2>>(std::move(ogg));
|
||||
ogg_inst_->looping(looping_);
|
||||
resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f);
|
||||
} catch (const std::exception& ex) {
|
||||
// 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<GmePlayer<2>>(std::move(gme));
|
||||
} else {
|
||||
io::SpanStream stream {data};
|
||||
audio::Gme gme = audio::load_gme(stream);
|
||||
gme_inst_ = std::make_shared<GmePlayer<2>>(std::move(gme));
|
||||
}
|
||||
|
||||
resampler_ = Resampler<2>(gme_inst_, 1.f);
|
||||
} catch (const std::exception& ex) {
|
||||
// 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<XmpPlayer<2>>(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<audio::MusicType> 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<float> 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<float> 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<float> 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<uint64_t>(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<OggPlayer<2>> ogg_inst_;
|
||||
std::shared_ptr<GmePlayer<2>> gme_inst_;
|
||||
std::shared_ptr<XmpPlayer<2>> xmp_inst_;
|
||||
std::optional<Resampler<2>> 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::Impl>()) {
|
||||
}
|
||||
MusicPlayer::MusicPlayer(tcb::span<std::byte> data) : impl_(make_unique<MusicPlayer::Impl>(data)) {
|
||||
}
|
||||
MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default;
|
||||
MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default;
|
||||
|
||||
MusicPlayer::~MusicPlayer() = default;
|
||||
|
||||
void MusicPlayer::play(bool looping) {
|
||||
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<Sample<2>> buffer) {
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->generate(buffer);
|
||||
}
|
||||
|
||||
std::optional<audio::MusicType> MusicPlayer::music_type() const {
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->music_type();
|
||||
}
|
||||
|
||||
std::optional<float> MusicPlayer::duration_seconds() const {
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->duration_seconds();
|
||||
}
|
||||
|
||||
std::optional<float> MusicPlayer::loop_point_seconds() const {
|
||||
SRB2_ASSERT(impl_ != nullptr);
|
||||
|
||||
return impl_->loop_point_seconds();
|
||||
}
|
||||
|
||||
std::optional<float> 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);
|
||||
}
|
||||
69
src/audio/music_player.hpp
Normal file
69
src/audio/music_player.hpp
Normal file
|
|
@ -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 <memory>
|
||||
#include <optional>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#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<std::byte> 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<Sample<2>> 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<MusicType> music_type() const;
|
||||
std::optional<float> duration_seconds() const;
|
||||
std::optional<float> loop_point_seconds() const;
|
||||
std::optional<float> position_seconds() const;
|
||||
bool fading() const;
|
||||
|
||||
virtual ~MusicPlayer() final;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_MUSIC_PLAYER_HPP__
|
||||
194
src/audio/ogg.cpp
Normal file
194
src/audio/ogg.cpp
Normal file
|
|
@ -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 <limits>
|
||||
|
||||
#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<std::byte> data) : memory_data_(std::move(data)), instance_(nullptr) {
|
||||
_init_with_data();
|
||||
}
|
||||
|
||||
Ogg::Ogg(tcb::span<std::byte> data) : memory_data_(data.begin(), data.end()), instance_(nullptr) {
|
||||
_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<Sample<1>> buffer) {
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
size_t read = stb_vorbis_get_samples_float_interleaved(
|
||||
instance_, 1, reinterpret_cast<float*>(buffer.data()), buffer.size() * 1);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
std::size_t Ogg::get_samples(tcb::span<Sample<2>> buffer) {
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
|
||||
size_t read = stb_vorbis_get_samples_float_interleaved(
|
||||
instance_, 2, reinterpret_cast<float*>(buffer.data()), buffer.size() * 2);
|
||||
|
||||
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<std::string>(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<float>(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<int>::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<const unsigned char*>(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL);
|
||||
|
||||
if (vorbis_result != VORBIS__no_error)
|
||||
throw StbVorbisException(vorbis_result);
|
||||
}
|
||||
81
src/audio/ogg.hpp
Normal file
81
src/audio/ogg.hpp
Normal file
|
|
@ -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 <exception>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <stb_vorbis.h>
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#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<std::string> comments;
|
||||
};
|
||||
|
||||
class Ogg final {
|
||||
std::vector<std::byte> memory_data_;
|
||||
stb_vorbis* instance_;
|
||||
|
||||
public:
|
||||
Ogg() noexcept;
|
||||
|
||||
explicit Ogg(std::vector<std::byte> data);
|
||||
explicit Ogg(tcb::span<std::byte> 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<Sample<1>> buffer);
|
||||
std::size_t get_samples(tcb::span<Sample<2>> 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 <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Ogg load_ogg(I& stream) {
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Ogg {std::move(data)};
|
||||
}
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_OGG_HPP__
|
||||
141
src/audio/ogg_player.cpp
Normal file
141
src/audio/ogg_player.cpp
Normal file
|
|
@ -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 <cmath>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<std::size_t> find_loop_point(const Ogg& ogg) {
|
||||
OggComment comment = ogg.comment();
|
||||
std::size_t rate = ogg.sample_rate();
|
||||
for (auto& comment : comment.comments) {
|
||||
if (comment.find("LOOPPOINT=") == 0) {
|
||||
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<double>(loop_ms) / (rate / 1000.));
|
||||
|
||||
return loop_point;
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template <size_t C>
|
||||
OggPlayer<C>::OggPlayer(Ogg&& ogg) noexcept
|
||||
: playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward<Ogg>(ogg)) {
|
||||
loop_point_ = find_loop_point(ogg_);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
OggPlayer<C>::OggPlayer(OggPlayer&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
OggPlayer<C>& OggPlayer<C>::operator=(OggPlayer&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
OggPlayer<C>::~OggPlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t OggPlayer<C>::generate(tcb::span<Sample<C>> 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 <size_t C>
|
||||
void OggPlayer<C>::seek(float position_seconds) {
|
||||
ogg_.seek(static_cast<std::size_t>(position_seconds * sample_rate()));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void OggPlayer<C>::loop_point_seconds(float loop_point) {
|
||||
std::size_t rate = sample_rate();
|
||||
loop_point = static_cast<std::size_t>(std::round(loop_point * rate));
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void OggPlayer<C>::reset() {
|
||||
ogg_.seek(0);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::size_t OggPlayer<C>::sample_rate() const {
|
||||
return ogg_.sample_rate();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float OggPlayer<C>::duration_seconds() const {
|
||||
return ogg_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::optional<float> OggPlayer<C>::loop_point_seconds() const {
|
||||
if (!loop_point_)
|
||||
return std::nullopt;
|
||||
|
||||
return *loop_point_ / static_cast<float>(sample_rate());
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float OggPlayer<C>::position_seconds() const {
|
||||
return ogg_.position_seconds();
|
||||
}
|
||||
|
||||
template class srb2::audio::OggPlayer<1>;
|
||||
template class srb2::audio::OggPlayer<2>;
|
||||
72
src/audio/ogg_player.hpp
Normal file
72
src/audio/ogg_player.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <stb_vorbis.h>
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../io/streams.hpp"
|
||||
#include "ogg.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class OggPlayer final : public Source<C> {
|
||||
bool playing_;
|
||||
bool looping_;
|
||||
std::optional<std::size_t> 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<Sample<C>> 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<float> 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__
|
||||
81
src/audio/resample.cpp
Normal file
81
src/audio/resample.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::size_t;
|
||||
using std::vector;
|
||||
|
||||
using namespace srb2::audio;
|
||||
|
||||
template <size_t C>
|
||||
Resampler<C>::Resampler(std::shared_ptr<Source<C>>&& source, float ratio)
|
||||
: source_(std::forward<std::shared_ptr<Source<C>>>(source)), ratio_(ratio) {
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Resampler<C>::Resampler(Resampler<C>&& r) = default;
|
||||
|
||||
template <size_t C>
|
||||
Resampler<C>::~Resampler() = default;
|
||||
|
||||
template <size_t C>
|
||||
Resampler<C>& Resampler<C>::operator=(Resampler<C>&& r) = default;
|
||||
|
||||
template <size_t C>
|
||||
size_t Resampler<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
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<int>(buf_.size() - 1)) {
|
||||
pos_ -= buf_.size();
|
||||
last_ = buf_.size() == 0 ? Sample<C> {} : buf_.back();
|
||||
buf_.clear();
|
||||
buf_.resize(512);
|
||||
size_t source_read = source_->generate(buf_);
|
||||
buf_.resize(source_read);
|
||||
if (source_read == 0) {
|
||||
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>;
|
||||
63
src/audio/resample.hpp
Normal file
63
src/audio/resample.hpp
Normal file
|
|
@ -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 <cmath>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "sound_chunk.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class Resampler : public Source<C> {
|
||||
public:
|
||||
Resampler(std::shared_ptr<Source<C>>&& source_, float ratio);
|
||||
Resampler(const Resampler<C>& r) = delete;
|
||||
Resampler(Resampler<C>&& r);
|
||||
virtual ~Resampler();
|
||||
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> buffer);
|
||||
|
||||
Resampler& operator=(const Resampler<C>& r) = delete;
|
||||
Resampler& operator=(Resampler<C>&& r);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Source<C>> source_;
|
||||
float ratio_ {1.f};
|
||||
std::vector<Sample<C>> buf_;
|
||||
Sample<C> 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__
|
||||
78
src/audio/sample.hpp
Normal file
78
src/audio/sample.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
struct Sample {
|
||||
std::array<float, C> 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 <size_t C>
|
||||
constexpr Sample<C> operator+(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
constexpr Sample<C> operator-(const Sample<C>& lhs, const Sample<C>& rhs) noexcept {
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
constexpr Sample<C> operator*(const Sample<C>& lhs, float rhs) noexcept {
|
||||
Sample<C> out;
|
||||
for (std::size_t i = 0; i < C; i++) {
|
||||
out.amplitudes[i] = lhs.amplitudes[i] * rhs;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static constexpr float sample_to_float(T sample) noexcept;
|
||||
|
||||
template <>
|
||||
constexpr float sample_to_float<uint8_t>(uint8_t sample) noexcept {
|
||||
return (sample / 128.f) - 1.f;
|
||||
}
|
||||
|
||||
template <>
|
||||
constexpr float sample_to_float<int16_t>(int16_t sample) noexcept {
|
||||
return sample / 32768.f;
|
||||
}
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_SAMPLE_HPP__
|
||||
25
src/audio/sound_chunk.hpp
Normal file
25
src/audio/sound_chunk.hpp
Normal file
|
|
@ -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 <vector>
|
||||
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
struct SoundChunk {
|
||||
std::vector<Sample<1>> samples;
|
||||
};
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_SOUND_CHUNK_HPP__
|
||||
72
src/audio/sound_effect_player.cpp
Normal file
72
src/audio/sound_effect_player.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
|
||||
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<Sample<2>> 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;
|
||||
46
src/audio/sound_effect_player.hpp
Normal file
46
src/audio/sound_effect_player.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "sound_chunk.hpp"
|
||||
#include "source.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
class SoundEffectPlayer : public Source<2> {
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<2>> 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__
|
||||
36
src/audio/source.hpp
Normal file
36
src/audio/source.hpp
Normal file
|
|
@ -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 <array>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
template <size_t C>
|
||||
class Source {
|
||||
public:
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> 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__
|
||||
264
src/audio/wav.cpp
Normal file
264
src/audio/wav.cpp
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
|
||||
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 <typename Visitor>
|
||||
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<uint8_t> read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) {
|
||||
std::vector<uint8_t> samples;
|
||||
samples.reserve(count);
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
samples.push_back(io::read_uint8(stream));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
std::vector<int16_t> read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) {
|
||||
std::vector<int16_t> samples;
|
||||
samples.reserve(count);
|
||||
for (std::size_t i = 0; i < count; i++) {
|
||||
samples.push_back(io::read_int16(stream));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
struct OverloadVisitor : Ts... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
OverloadVisitor(Ts...) -> OverloadVisitor<Ts...>;
|
||||
|
||||
} // namespace
|
||||
|
||||
Wav::Wav() = default;
|
||||
|
||||
Wav::Wav(tcb::span<std::byte> data) {
|
||||
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<FmtTag> read_fmt;
|
||||
std::variant<std::vector<uint8_t>, std::vector<int16_t>> 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 <typename T>
|
||||
std::size_t read_samples(std::size_t channels,
|
||||
std::size_t offset,
|
||||
const std::vector<T>& samples,
|
||||
tcb::span<audio::Sample<1>> buffer) noexcept {
|
||||
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<float>(channels);
|
||||
}
|
||||
|
||||
return samples_to_read;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::size_t Wav::get_samples(std::size_t offset, tcb::span<audio::Sample<1>> buffer) const noexcept {
|
||||
auto samples_visitor = OverloadVisitor {
|
||||
[&](const std::vector<uint8_t>& samples) { return read_samples<uint8_t>(channels(), offset, samples, buffer); },
|
||||
[&](const std::vector<int16_t>& samples) {
|
||||
return read_samples<int16_t>(channels(), offset, samples, buffer);
|
||||
}};
|
||||
|
||||
return std::visit(samples_visitor, interleaved_samples_);
|
||||
}
|
||||
|
||||
std::size_t Wav::interleaved_length() const noexcept {
|
||||
auto samples_visitor = OverloadVisitor {[](const std::vector<uint8_t>& samples) { return samples.size(); },
|
||||
[](const std::vector<int16_t>& samples) { return samples.size(); }};
|
||||
return std::visit(samples_visitor, interleaved_samples_);
|
||||
}
|
||||
51
src/audio/wav.hpp
Normal file
51
src/audio/wav.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../io/streams.hpp"
|
||||
#include "sample.hpp"
|
||||
|
||||
namespace srb2::audio {
|
||||
|
||||
class Wav final {
|
||||
std::variant<std::vector<uint8_t>, std::vector<int16_t>> interleaved_samples_;
|
||||
std::size_t channels_ = 1;
|
||||
std::size_t sample_rate_ = 44100;
|
||||
|
||||
public:
|
||||
Wav();
|
||||
|
||||
explicit Wav(tcb::span<std::byte> data);
|
||||
|
||||
std::size_t get_samples(std::size_t offset, tcb::span<Sample<1>> 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 <typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Wav load_wav(I& stream) {
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Wav {data};
|
||||
}
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_WAV_HPP__
|
||||
45
src/audio/wav_player.cpp
Normal file
45
src/audio/wav_player.cpp
Normal file
|
|
@ -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>(wav)), position_(0), looping_(false) {
|
||||
}
|
||||
|
||||
std::size_t WavPlayer::generate(tcb::span<audio::Sample<1>> 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;
|
||||
}
|
||||
49
src/audio/wav_player.hpp
Normal file
49
src/audio/wav_player.hpp
Normal file
|
|
@ -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 <cstddef>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#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<Sample<1>> 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<float>(wav_.sample_rate()); }
|
||||
void seek(float seconds) { position_ = seconds * wav_.sample_rate(); }
|
||||
};
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_WAV_PLAYER_HPP__
|
||||
167
src/audio/xmp.cpp
Normal file
167
src/audio/xmp.cpp
Normal file
|
|
@ -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 <limits>
|
||||
|
||||
#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 <size_t C>
|
||||
Xmp<C>::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(std::vector<std::byte> data)
|
||||
: data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
_init();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(tcb::span<std::byte> data)
|
||||
: data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) {
|
||||
_init();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::Xmp(Xmp<C>&& rhs) noexcept : Xmp<C>() {
|
||||
std::swap(data_, rhs.data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
std::swap(module_loaded_, rhs.module_loaded_);
|
||||
std::swap(looping_, rhs.looping_);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>& Xmp<C>::operator=(Xmp<C>&& rhs) noexcept {
|
||||
std::swap(data_, rhs.data_);
|
||||
std::swap(instance_, rhs.instance_);
|
||||
std::swap(module_loaded_, rhs.module_loaded_);
|
||||
std::swap(looping_, rhs.looping_);
|
||||
|
||||
return *this;
|
||||
};
|
||||
|
||||
template <size_t C>
|
||||
Xmp<C>::~Xmp() {
|
||||
if (instance_) {
|
||||
xmp_free_context(instance_);
|
||||
instance_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
std::size_t Xmp<C>::play_buffer(tcb::span<std::array<int16_t, C>> buffer) {
|
||||
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 <size_t C>
|
||||
void Xmp<C>::reset() {
|
||||
SRB2_ASSERT(instance_ != nullptr);
|
||||
SRB2_ASSERT(module_loaded_ == true);
|
||||
|
||||
xmp_restart_module(instance_);
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float Xmp<C>::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<float>(info.total_time) / 1000.f;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void Xmp<C>::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 <size_t C>
|
||||
void Xmp<C>::_init() {
|
||||
if (instance_)
|
||||
return;
|
||||
|
||||
if (data_.size() >= std::numeric_limits<long>::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>;
|
||||
78
src/audio/xmp.hpp
Normal file
78
src/audio/xmp.hpp
Normal file
|
|
@ -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 <array>
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
#include <xmp.h>
|
||||
|
||||
#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 <size_t C>
|
||||
class Xmp final {
|
||||
std::vector<std::byte> data_;
|
||||
xmp_context instance_;
|
||||
bool module_loaded_;
|
||||
bool looping_;
|
||||
|
||||
public:
|
||||
Xmp();
|
||||
|
||||
explicit Xmp(std::vector<std::byte> data);
|
||||
explicit Xmp(tcb::span<std::byte> data);
|
||||
|
||||
Xmp(const Xmp<C>&) = delete;
|
||||
Xmp(Xmp<C>&& rhs) noexcept;
|
||||
|
||||
Xmp& operator=(const Xmp&) = delete;
|
||||
Xmp& operator=(Xmp&& rhs) noexcept;
|
||||
|
||||
std::size_t play_buffer(tcb::span<std::array<int16_t, C>> 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 <size_t C, typename I, typename std::enable_if_t<srb2::io::IsInputStreamV<I>, int> = 0>
|
||||
inline Xmp<C> load_xmp(I& stream) {
|
||||
std::vector<std::byte> data = srb2::io::read_to_vec(stream);
|
||||
return Xmp<C> {std::move(data)};
|
||||
}
|
||||
|
||||
} // namespace srb2::audio
|
||||
|
||||
#endif // __SRB2_AUDIO_XMP_HPP__
|
||||
57
src/audio/xmp_player.cpp
Normal file
57
src/audio/xmp_player.cpp
Normal file
|
|
@ -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 <cmath>
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::audio;
|
||||
|
||||
template <size_t C>
|
||||
XmpPlayer<C>::XmpPlayer(Xmp<C>&& xmp) : xmp_(std::move(xmp)), buf_() {
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
XmpPlayer<C>::XmpPlayer(XmpPlayer&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
XmpPlayer<C>& XmpPlayer<C>::operator=(XmpPlayer<C>&& rhs) noexcept = default;
|
||||
|
||||
template <size_t C>
|
||||
XmpPlayer<C>::~XmpPlayer() = default;
|
||||
|
||||
template <size_t C>
|
||||
std::size_t XmpPlayer<C>::generate(tcb::span<Sample<C>> buffer) {
|
||||
buf_.resize(buffer.size());
|
||||
std::size_t read = xmp_.play_buffer(tcb::make_span(buf_));
|
||||
buf_.resize(read);
|
||||
std::size_t ret = std::min(buffer.size(), buf_.size());
|
||||
|
||||
for (std::size_t i = 0; i < ret; i++) {
|
||||
for (std::size_t j = 0; j < C; j++) {
|
||||
buffer[i].amplitudes[j] = buf_[i][j] / 32768.f;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
float XmpPlayer<C>::duration_seconds() const {
|
||||
return xmp_.duration_seconds();
|
||||
}
|
||||
|
||||
template <size_t C>
|
||||
void XmpPlayer<C>::seek(float position_seconds) {
|
||||
xmp_.seek(static_cast<int>(std::round(position_seconds * 1000.f)));
|
||||
}
|
||||
|
||||
template class srb2::audio::XmpPlayer<1>;
|
||||
template class srb2::audio::XmpPlayer<2>;
|
||||
48
src/audio/xmp_player.hpp
Normal file
48
src/audio/xmp_player.hpp
Normal file
|
|
@ -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 <size_t C>
|
||||
class XmpPlayer final : public Source<C> {
|
||||
Xmp<C> xmp_;
|
||||
std::vector<std::array<int16_t, C>> buf_;
|
||||
|
||||
public:
|
||||
XmpPlayer(Xmp<C>&& xmp);
|
||||
|
||||
XmpPlayer(const XmpPlayer<C>&) = delete;
|
||||
XmpPlayer(XmpPlayer<C>&& rhs) noexcept;
|
||||
|
||||
XmpPlayer<C>& operator=(const XmpPlayer<C>&) = delete;
|
||||
XmpPlayer<C>& operator=(XmpPlayer<C>&& rhs) noexcept;
|
||||
|
||||
~XmpPlayer();
|
||||
|
||||
virtual std::size_t generate(tcb::span<Sample<C>> 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__
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
46
src/d_main.c
46
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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
259
src/d_netcmd.c
259
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
113
src/doomstat.h
113
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}};
|
||||
|
|
|
|||
223
src/g_demo.c
223
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<<DF_ATTACKSHIFT));
|
||||
|
||||
if (multiplayer && !netgame)
|
||||
demoflags |= DF_NONETMP;
|
||||
demoflags = DF_GHOST;
|
||||
|
||||
if (multiplayer)
|
||||
{
|
||||
demoflags |= DF_MULTIPLAYER;
|
||||
if (!netgame)
|
||||
demoflags |= DF_NONETMP;
|
||||
}
|
||||
else
|
||||
{
|
||||
demoflags |= modeattacking;
|
||||
}
|
||||
|
||||
if (encoremode)
|
||||
demoflags |= DF_ENCORE;
|
||||
|
|
@ -2379,7 +2380,9 @@ void G_BeginRecording(void)
|
|||
M_Memcpy(demobuf.p, mapmd5, 16); demobuf.p += 16;
|
||||
|
||||
WRITEUINT8(demobuf.p, demoflags);
|
||||
WRITEUINT8(demobuf.p, gametype & 0xFF);
|
||||
|
||||
WRITESTRINGN(demobuf.p, gametypes[gametype]->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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
563
src/g_game.c
563
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
|
||||
{
|
||||
|
|
|
|||
15
src/g_game.h
15
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);
|
||||
|
|
|
|||
132
src/hu_stuff.c
132
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++;
|
||||
|
||||
|
|
|
|||
104
src/info.c
104
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
|
||||
|
|
|
|||
26
src/info.h
26
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,
|
||||
|
|
|
|||
|
|
@ -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<srb2::io::SpanStream>;
|
||||
|
|
|
|||
|
|
@ -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<StreamOffset>(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<StreamOffset>(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;
|
||||
|
|
|
|||
201
src/k_battle.c
201
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++)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
39
src/k_boss.c
39
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;
|
||||
}
|
||||
|
|
|
|||
17
src/k_boss.h
17
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
284
src/k_hud.c
284
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.
|
||||
|
|
|
|||
317
src/k_kart.c
317
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;
|
||||
}
|
||||
}
|
||||
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
13
src/k_menu.h
13
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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[] =
|
||||
{
|
||||
|
|
|
|||
268
src/k_menudraw.c
268
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))
|
||||
|
|
|
|||
552
src/k_menufunc.c
552
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
370
src/k_roulette.c
370
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
21
src/m_cond.c
21
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
duel-bomb.c
|
||||
broly.c
|
||||
ufo.c
|
||||
monitor.c
|
||||
item-spot.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;
|
||||
}
|
||||
|
|
|
|||
35
src/objects/item-spot.c
Normal file
35
src/objects/item-spot.c
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
705
src/objects/monitor.c
Normal file
705
src/objects/monitor.c
Normal file
|
|
@ -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);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue