diff --git a/.gitignore b/.gitignore index 3eca1c57a..6b2702a76 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ Win32_LIB_ASM_Release /make /bin /build +/CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 10c65a167..f26eff021 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ option( "Link dependencies using CMake's find_package and do not use internal builds" ${SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT} ) +option(SRB2_CONFIG_ENABLE_TESTS "Build the test suite" ON) # This option isn't recommended for distribution builds and probably won't work (yet). cmake_dependent_option( SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES @@ -81,6 +82,25 @@ option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF) option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF) set(SRB2_CONFIG_ASSET_DIRECTORY "" CACHE PATH "Path to directory that contains all asset files for the installer. If set, assets will be part of installation and cpack.") +if(SRB2_CONFIG_ENABLE_TESTS) + # https://github.com/catchorg/Catch2 + CPMAddPackage( + NAME Catch2 + VERSION 3.1.1 + GITHUB_REPOSITORY catchorg/Catch2 + OPTIONS + "CATCH_INSTALL_DOCS OFF" + ) + list(APPEND CMAKE_MODULE_PATH "${Catch2_SOURCE_DIR}/extras") + include(CTest) + include(Catch) + add_executable(srb2tests) + # To add tests, use target_sources to add individual test files to the target in subdirs. + target_link_libraries(srb2tests PRIVATE Catch2::Catch2 Catch2::Catch2WithMain) + target_compile_features(srb2tests PRIVATE c_std_11 cxx_std_17) + catch_discover_tests(srb2tests) +endif() + # Enable CCache # (Set USE_CCACHE=ON to use, CCACHE_OPTIONS for options) if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL Windows) @@ -125,13 +145,6 @@ if ((${SRB2_USE_CCACHE}) AND (${CMAKE_C_COMPILER} MATCHES "clang")) message(WARNING "Using clang and CCache: You may want to set environment variable CCACHE_CPP2=yes to prevent include errors during compile.") endif() -# Add sources from Sourcefile -function(target_sourcefile type) - file(STRINGS Sourcefile list - REGEX "[-0-9A-Za-z_]+\.${type}") - target_sources(SRB2SDL2 PRIVATE ${list}) -endfunction() - # bitness check set(SRB2_SYSTEM_BITS 0) if(CMAKE_SIZEOF_VOID_P EQUAL 8) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b681d042..61e5ff86f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,15 +1,143 @@ -add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32) +add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 + comptime.c + md5.c + config.h.in + string.c + d_main.c + d_clisrv.c + d_net.c + d_netfil.c + d_netcmd.c + dehacked.c + deh_soc.c + deh_lua.c + deh_tables.c + z_zone.c + f_finale.c + f_wipe.c + g_demo.c + g_game.c + g_input.c + g_splitscreen.c + am_map.c + command.c + console.c + font.c + hu_stuff.c + i_time.c + y_inter.c + st_stuff.c + m_aatree.c + m_anigif.c + m_argv.c + m_bbox.c + m_cheat.c + m_cond.c + m_easing.c + m_fixed.c + m_misc.c + m_perfstats.c + m_random.c + m_queue.c + info.c + p_ceilng.c + p_enemy.c + p_floor.c + p_inter.c + p_lights.c + p_map.c + p_maputl.c + p_mobj.c + p_polyobj.c + p_saveg.c + p_setup.c + p_sight.c + p_spec.c + p_telept.c + p_tick.c + p_user.c + p_slopes.c + tables.c + r_bsp.c + r_data.c + r_draw.c + r_fps.c + r_main.c + r_plane.c + r_segs.c + r_skins.c + r_sky.c + r_splats.c + r_things.c + r_bbox.c + r_textures.c + r_patch.c + r_patchrotation.c + r_picformats.c + r_portal.c + screen.c + taglist.c + v_video.c + s_sound.c + sounds.c + w_wad.c + filesrch.c + mserv.c + http-mserv.c + i_tcp.c + lzf.c + vid_copy.s + lua_script.c + lua_baselib.c + lua_mathlib.c + lua_hooklib.c + lua_consolelib.c + lua_infolib.c + lua_mobjlib.c + lua_playerlib.c + lua_skinlib.c + lua_thinkerlib.c + lua_maplib.c + lua_taglib.c + lua_polyobjlib.c + lua_blockmaplib.c + lua_hudlib.c + lua_hudlib_drawlist.c + k_kart.c + k_respawn.c + k_collide.c + k_color.c + k_race.c + k_battle.c + k_pwrlv.c + k_waypoint.c + k_pathfind.c + k_bheap.c + k_bot.c + k_botitem.c + k_botsearch.c + k_grandprix.c + k_boss.c + k_hud.c + k_menudef.c + k_menufunc.c + k_menudraw.c + k_terrain.c + k_brightmap.c + k_terrain.c + k_director.c + k_follower.c + k_profiles.c + k_specialstage.c + k_roulette.c +) if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") # On MinGW with internal libraries, link the standard library statically target_link_options(SRB2SDL2 PRIVATE "-static") endif() -set_property(TARGET SRB2SDL2 PROPERTY C_STANDARD 11) - -# Core sources -target_sourcefile(c) -target_sources(SRB2SDL2 PRIVATE comptime.c md5.c config.h.in) +target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17) set(SRB2_ASM_SOURCES vid_copy.s) @@ -370,6 +498,7 @@ endif() add_subdirectory(sdl) add_subdirectory(objects) +add_subdirectory(tests) # strip debug symbols into separate file when using gcc. # to be consistent with Makefile, don't generate for OS X. diff --git a/src/Sourcefile b/src/Sourcefile deleted file mode 100644 index f2aa652b1..000000000 --- a/src/Sourcefile +++ /dev/null @@ -1,127 +0,0 @@ -string.c -d_main.c -d_clisrv.c -d_net.c -d_netfil.c -d_netcmd.c -dehacked.c -deh_soc.c -deh_lua.c -deh_tables.c -z_zone.c -f_finale.c -f_wipe.c -g_demo.c -g_game.c -g_input.c -g_splitscreen.c -am_map.c -command.c -console.c -font.c -hu_stuff.c -i_time.c -y_inter.c -st_stuff.c -m_aatree.c -m_anigif.c -m_argv.c -m_bbox.c -m_cheat.c -m_cond.c -m_easing.c -m_fixed.c -m_misc.c -m_perfstats.c -m_random.c -m_queue.c -info.c -p_ceilng.c -p_enemy.c -p_floor.c -p_inter.c -p_lights.c -p_map.c -p_maputl.c -p_mobj.c -p_polyobj.c -p_saveg.c -p_setup.c -p_sight.c -p_spec.c -p_telept.c -p_tick.c -p_user.c -p_slopes.c -tables.c -r_bsp.c -r_data.c -r_draw.c -r_fps.c -r_main.c -r_plane.c -r_segs.c -r_skins.c -r_sky.c -r_splats.c -r_things.c -r_bbox.c -r_textures.c -r_patch.c -r_patchrotation.c -r_picformats.c -r_portal.c -screen.c -taglist.c -v_video.c -s_sound.c -sounds.c -w_wad.c -filesrch.c -mserv.c -http-mserv.c -i_tcp.c -lzf.c -vid_copy.s -lua_script.c -lua_baselib.c -lua_mathlib.c -lua_hooklib.c -lua_consolelib.c -lua_infolib.c -lua_mobjlib.c -lua_playerlib.c -lua_skinlib.c -lua_thinkerlib.c -lua_maplib.c -lua_taglib.c -lua_polyobjlib.c -lua_blockmaplib.c -lua_hudlib.c -lua_hudlib_drawlist.c -k_kart.c -k_respawn.c -k_collide.c -k_color.c -k_race.c -k_battle.c -k_pwrlv.c -k_waypoint.c -k_pathfind.c -k_bheap.c -k_bot.c -k_botitem.c -k_botsearch.c -k_grandprix.c -k_boss.c -k_hud.c -k_menudef.c -k_menufunc.c -k_menudraw.c -k_terrain.c -k_brightmap.c -k_terrain.c -k_director.c -k_follower.c -k_profiles.c -k_specialstage.c diff --git a/src/blua/CMakeLists.txt b/src/blua/CMakeLists.txt index 4e9c67d2f..afe03fdc5 100644 --- a/src/blua/CMakeLists.txt +++ b/src/blua/CMakeLists.txt @@ -1 +1,27 @@ -target_sourcefile(c) +target_sources(SRB2SDL2 PRIVATE + lapi.c + lbaselib.c + ldo.c + lfunc.c + linit.c + liolib.c + llex.c + lmem.c + lobject.c + lstate.c + lstrlib.c + ltablib.c + lundump.c + lzio.c + lauxlib.c + lcode.c + ldebug.c + ldump.c + lgc.c + lopcodes.c + lparser.c + lstring.c + ltable.c + ltm.c + lvm.c +) diff --git a/src/blua/Sourcefile b/src/blua/Sourcefile deleted file mode 100644 index f99c89c8d..000000000 --- a/src/blua/Sourcefile +++ /dev/null @@ -1,25 +0,0 @@ -lapi.c -lbaselib.c -ldo.c -lfunc.c -linit.c -liolib.c -llex.c -lmem.c -lobject.c -lstate.c -lstrlib.c -ltablib.c -lundump.c -lzio.c -lauxlib.c -lcode.c -ldebug.c -ldump.c -lgc.c -lopcodes.c -lparser.c -lstring.c -ltable.c -ltm.c -lvm.c diff --git a/src/cxxutil.hpp b/src/cxxutil.hpp new file mode 100644 index 000000000..befe9c20b --- /dev/null +++ b/src/cxxutil.hpp @@ -0,0 +1,37 @@ +#ifndef __SRB2_CXXUTIL_HPP__ +#define __SRB2_CXXUTIL_HPP__ + +#include +#include + +namespace srb2 { + +template +class Finally { +public: + explicit Finally(const F& f) noexcept : f_(f) {} + explicit Finally(F&& f) noexcept : f_(f) {} + + Finally(Finally&& from) noexcept : f_(std::move(from.f_)), call_(std::exchange(from.call_, false)) {} + + Finally(const Finally& from) = delete; + void operator=(const Finally& from) = delete; + void operator=(Finally&& from) = delete; + + ~Finally() noexcept { + f_(); + } + +private: + F f_; + bool call_ = true; +}; + +template +Finally> finally(F&& f) noexcept { + return Finally {std::forward(f)}; +} + +} + +#endif // __SRB2_CXXUTIL_HPP__ diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 3494ad8c3..43b56282e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -360,37 +360,38 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_banana = CVAR_INIT ("banana", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_eggmanmonitor = CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_landmine = CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_shrink = CVAR_INIT ("shrink", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_lightningshield = CVAR_INIT ("lightningshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_flameshield = CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_superring = CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_droptarget = CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_gardentop = CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_gachabom = CVAR_INIT ("gachabom", "On", CV_NETVAR, CV_OnOff, NULL); - -consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL); -consvar_t cv_triplegachabom = CVAR_INIT ("triplegachabom", "On", CV_NETVAR, CV_OnOff, NULL); +consvar_t cv_items[NUMKARTRESULTS-1] = { + CVAR_INIT ("sneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("rocketsneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("invincibility", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("banana", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("orbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("jawz", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("mine", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("landmine", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("ballhog", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("grow", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("shrink", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("lightningshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("bubbleshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("flameshield", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("hyudoro", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("pogospring", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("superring", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("kitchensink", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("droptarget", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("gardentop", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("gachabom", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("dualsneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("triplesneaker", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("triplebanana", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("quadorbinaut", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("dualjawz", "On", CV_NETVAR, CV_OnOff, NULL), + CVAR_INIT ("triplegachabom", "On", CV_NETVAR, CV_OnOff, NULL) +}; consvar_t cv_kartspeed = CVAR_INIT ("gamespeed", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartspeed_cons_t, KartSpeed_OnChange); static CV_PossibleValue_t kartbumpers_cons_t[] = {{1, "MIN"}, {12, "MAX"}, {0, NULL}}; @@ -5661,7 +5662,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) K_StripItems(player); // Cancel roulette if rolling - player->itemroulette = 0; + player->itemRoulette.active = false; player->itemtype = item; player->itemamount = amt; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 57385dc5c..e0b969ffd 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -16,6 +16,7 @@ #define __D_NETCMD__ #include "command.h" +#include "d_player.h" // console vars extern consvar_t cv_playername[MAXSPLITSCREENPLAYERS]; @@ -72,39 +73,7 @@ extern consvar_t cv_pause; extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_respawntime; // SRB2kart items -extern consvar_t - cv_sneaker, - cv_rocketsneaker, - cv_invincibility, - cv_banana, - cv_eggmanmonitor, - cv_orbinaut, - cv_jawz, - cv_mine, - cv_landmine, - cv_ballhog, - cv_selfpropelledbomb, - cv_grow, - cv_shrink, - cv_lightningshield, - cv_bubbleshield, - cv_flameshield, - cv_hyudoro, - cv_pogospring, - cv_superring, - cv_kitchensink, - cv_droptarget, - cv_gardentop, - cv_gachabom; - -extern consvar_t - cv_dualsneaker, - cv_triplesneaker, - cv_triplebanana, - cv_tripleorbinaut, - cv_quadorbinaut, - cv_dualjawz, - cv_triplegachabom; +extern consvar_t cv_items[NUMKARTRESULTS-1]; extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; diff --git a/src/d_player.h b/src/d_player.h index 59a7a73b8..a63f81c97 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -227,6 +227,7 @@ typedef enum // Item box khud_itemblink, // Item flashing after roulette, serves as a mashing indicator khud_itemblinkmode, // Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items) + khud_rouletteoffset,// Roulette stop height // Rings khud_ringframe, // Ring spin frame @@ -331,6 +332,41 @@ struct skybox_t { mobj_t * centerpoint; }; +// player_t struct for item roulette variables + +// Doing this the right way is causing problems. +// so FINE, it's a static length now. +#define ITEM_LIST_SIZE (NUMKARTRESULTS << 3) + +struct itemroulette_t +{ + boolean active; + +#ifdef ITEM_LIST_SIZE + size_t itemListLen; + SINT8 itemList[ITEM_LIST_SIZE]; +#else + size_t itemListCap; + size_t itemListLen; + SINT8 *itemList; +#endif + + UINT8 useOdds; + UINT8 playing, exiting; + UINT32 dist, baseDist; + UINT32 firstDist, secondDist; + UINT32 secondToFirst; + + size_t index; + UINT8 sound; + + tic_t speed; + tic_t tics; + tic_t elapsed; + + boolean eggman; +}; + // ======================================================================== // PLAYER STRUCTURE // ======================================================================== @@ -479,8 +515,7 @@ struct player_t UINT8 tripwirePass; // see tripwirepass_t UINT16 tripwireLeniency; // When reaching a state that lets you go thru tripwire, you get an extra second leniency after it ends to still go through it. - UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") - UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) + itemroulette_t itemRoulette; // Item roulette data // Item held stuff SINT8 itemtype; // KITEM_ constant for item number diff --git a/src/deh_tables.c b/src/deh_tables.c index 19aa12528..d5cdbb34d 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5629,6 +5629,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_PAPERITEMSPOT", "MT_BEAMPOINT", + + "MT_BROLY", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/doomdef.h b/src/doomdef.h index f54916599..669b90c71 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -126,10 +126,10 @@ extern char logfilename[1024]; // VERSIONSTRING_RC is for the resource-definition script used by windows builds #else #ifdef BETAVERSION -#define VERSIONSTRING "v"SRB2VERSION" "BETAVERSION +#define VERSIONSTRING "v" SRB2VERSION " " BETAVERSION #define VERSIONSTRING_RC SRB2VERSION " " BETAVERSION "\0" #else -#define VERSIONSTRING "v"SRB2VERSION +#define VERSIONSTRING "v" SRB2VERSION #define VERSIONSTRING_RC SRB2VERSION "\0" #endif // Hey! If you change this, add 1 to the MODVERSION below! @@ -614,12 +614,14 @@ UINT32 quickncasehash (const char *p, size_t n) return x; } +#ifndef __cplusplus #ifndef min // Double-Check with WATTCP-32's cdefs.h #define min(x, y) (((x) < (y)) ? (x) : (y)) #endif #ifndef max // Double-Check with WATTCP-32's cdefs.h #define max(x, y) (((x) > (y)) ? (x) : (y)) #endif +#endif // Max gamepad/joysticks that can be detected/used. #define MAX_JOYSTICKS 4 diff --git a/src/doomtype.h b/src/doomtype.h index 95f060008..b1269eaff 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -17,6 +17,10 @@ #ifndef __DOOMTYPE__ #define __DOOMTYPE__ +#ifdef __cplusplus +extern "C" { +#endif + #ifdef _WIN32 //#define WIN32_LEAN_AND_MEAN #define RPC_NO_WINDOWS_H @@ -88,7 +92,9 @@ typedef long ssize_t; #endif #define strncasecmp strnicmp #define strcasecmp stricmp +#ifndef __cplusplus #define inline __inline +#endif #elif defined (__WATCOMC__) #include #include @@ -107,24 +113,6 @@ typedef long ssize_t; char *strcasestr(const char *in, const char *what); #define stristr strcasestr -#if defined (macintosh) //|| defined (__APPLE__) //skip all boolean/Boolean crap - #define true 1 - #define false 0 - #define min(x,y) (((x)<(y)) ? (x) : (y)) - #define max(x,y) (((x)>(y)) ? (x) : (y)) - -#ifdef macintosh - #define stricmp strcmp - #define strnicmp strncmp -#endif - - #define boolean INT32 - - #ifndef O_BINARY - #define O_BINARY 0 - #endif -#endif //macintosh - #if defined (PC_DOS) || defined (_WIN32) || defined (__HAIKU__) #define HAVE_DOSSTR_FUNCS #endif @@ -151,22 +139,24 @@ size_t strlcpy(char *dst, const char *src, size_t siz); /* Boolean type definition */ -// \note __BYTEBOOL__ used to be set above if "macintosh" was defined, -// if macintosh's version of boolean type isn't needed anymore, then isn't this macro pointless now? -#ifndef __BYTEBOOL__ - #define __BYTEBOOL__ +// Note: C++ bool and C99/C11 _Bool are NOT compatible. +// Historically, boolean was win32 BOOL on Windows. For equivalence, it's now +// int32_t. "true" and "false" are only declared for C code; in C++, conversion +// between "bool" and "int32_t" takes over. +#ifndef _WIN32 +typedef int32_t boolean; +#else +#define boolean BOOL +#endif - //faB: clean that up !! - #if defined( _MSC_VER) && (_MSC_VER >= 1800) // MSVC 2013 and forward - #include "stdbool.h" - #elif defined (_WIN32) - #define false FALSE // use windows types - #define true TRUE - #define boolean BOOL - #else - typedef enum {false, true} boolean; - #endif -#endif // __BYTEBOOL__ +#ifndef __cplusplus +#ifndef _WIN32 +enum {false = 0, true = 1}; +#else +#define false FALSE +#define true TRUE +#endif +#endif /* 7.18.2.1 Limits of exact-width integer types */ @@ -408,4 +398,8 @@ typedef UINT64 precise_t; #include "typedef.h" +#ifdef __cplusplus +} // extern "C" +#endif + #endif //__DOOMTYPE__ diff --git a/src/g_game.c b/src/g_game.c index 5cde2d270..ab4b6bc9c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2260,11 +2260,10 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) SINT8 xtralife; // SRB2kart + itemroulette_t itemRoulette; respawnvars_t respawn; INT32 itemtype; INT32 itemamount; - INT32 itemroulette; - INT32 roulettetype; INT32 growshrinktimer; INT32 bumper; boolean songcredit = false; @@ -2323,10 +2322,13 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE)); // SRB2kart + memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); + memcpy(&respawn, &players[player].respawn, sizeof (respawn)); + if (betweenmaps || leveltime < introtime) { - itemroulette = 0; - roulettetype = 0; + itemRoulette.active = false; + itemtype = 0; itemamount = 0; growshrinktimer = 0; @@ -2348,9 +2350,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } else { - itemroulette = (players[player].itemroulette > 0 ? 1 : 0); - roulettetype = players[player].roulettetype; - if (players[player].pflags & PF_ITEMOUT) { itemtype = 0; @@ -2406,8 +2405,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) P_SetTarget(&players[player].follower, NULL); } - memcpy(&respawn, &players[player].respawn, sizeof (respawn)); - p = &players[player]; memset(p, 0, sizeof (*p)); @@ -2453,8 +2450,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->xtralife = xtralife; // SRB2kart - p->itemroulette = itemroulette; - p->roulettetype = roulettetype; p->itemtype = itemtype; p->itemamount = itemamount; p->growshrinktimer = growshrinktimer; @@ -2470,6 +2465,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rubberband = FRACUNIT; p->botvars.controller = UINT16_MAX; + memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette)); memcpy(&p->respawn, &respawn, sizeof (p->respawn)); if (follower) @@ -2481,7 +2477,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) //p->follower = NULL; // respawn a new one with you, it looks better. // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. - p->playerstate = PST_LIVE; p->panim = PA_STILL; // standing animation diff --git a/src/hardware/CMakeLists.txt b/src/hardware/CMakeLists.txt index 4e9c67d2f..a0a0f280c 100644 --- a/src/hardware/CMakeLists.txt +++ b/src/hardware/CMakeLists.txt @@ -1 +1,15 @@ -target_sourcefile(c) +target_sources(SRB2SDL2 PRIVATE + hw_bsp.c + hw_draw.c + hw_light.c + hw_main.c + hw_clip.c + hw_md2.c + hw_cache.c + hw_md2load.c + hw_md3load.c + hw_model.c + u_list.c + hw_batching.c + r_opengl/r_opengl.c +) diff --git a/src/hardware/Sourcefile b/src/hardware/Sourcefile deleted file mode 100644 index 1c05de76c..000000000 --- a/src/hardware/Sourcefile +++ /dev/null @@ -1,13 +0,0 @@ -hw_bsp.c -hw_draw.c -hw_light.c -hw_main.c -hw_clip.c -hw_md2.c -hw_cache.c -hw_md2load.c -hw_md3load.c -hw_model.c -u_list.c -hw_batching.c -r_opengl/r_opengl.c diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index d8eac4a44..a336bd53c 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -249,7 +249,7 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p if ((cx + fwidth) > clip->right) { - const float n = (clip->right - clip->left); + const float n = (clip->right - cx); s_max = (s_min + ((n / fwidth) * s_max)); fwidth = n; @@ -257,7 +257,7 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p if ((cy + fheight) > clip->bottom) { - const float n = (clip->bottom - clip->top); + const float n = (clip->bottom - cy); t_max = (t_min + ((n / fheight) * t_max)); fheight = n; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e30a725e9..0f861f642 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -938,8 +938,7 @@ static void HU_TickSongCredits(void) if (cursongcredit.anim > 0) { - char *str = va("\x1F"" %s", cursongcredit.def->source); - INT32 len = V_ThinStringWidth(str, V_ALLOWLOWERCASE|V_6WIDTHSPACE); + INT32 len = V_ThinStringWidth(cursongcredit.text, V_ALLOWLOWERCASE|V_6WIDTHSPACE); fixed_t destx = (len+7) * FRACUNIT; if (cursongcredit.trans > 0) @@ -2045,29 +2044,28 @@ static void HU_DrawDemoInfo(void) // void HU_DrawSongCredits(void) { - char *str; fixed_t x; fixed_t y = (r_splitscreen ? (BASEVIDHEIGHT/2)-4 : 32) * FRACUNIT; INT32 bgt; - if (!cursongcredit.def) // No def + if (!cursongcredit.def || cursongcredit.trans >= NUMTRANSMAPS) // No def { return; } - str = va("\x1F"" %s", cursongcredit.def->source); bgt = (NUMTRANSMAPS/2) + (cursongcredit.trans / 2); x = R_InterpolateFixed(cursongcredit.old_x, cursongcredit.x); if (bgt < NUMTRANSMAPS) { - V_DrawFixedPatch(x, y - (2 * FRACUNIT), FRACUNIT, V_SNAPTOLEFT|(bgt< 1 || player->itemroulette > 0) + if (stealth > 1 || player->itemRoulette.active == true) { player->botvars.itemconfirm += player->botvars.difficulty * 4; throwdir = -1; @@ -1393,27 +1394,15 @@ static void K_BotItemRings(player_t *player, ticcmd_t *cmd) --------------------------------------------------*/ static void K_BotItemRouletteMash(player_t *player, ticcmd_t *cmd) { - boolean mash = false; - if (K_ItemButtonWasDown(player) == true) { return; } - if (player->rings < 0 && cv_superring.value) - { - // Uh oh, we need a loan! - // It'll be better in the long run for bots to lose an item set for 10 free rings. - mash = true; - } + // TODO: Would be nice to implement smarter behavior + // for selecting items. - // TODO: Mash based on how far behind you are, when items are - // almost garantueed to be in your favor. - - if (mash == true) - { - cmd->buttons |= BT_ATTACK; - } + cmd->buttons |= BT_ATTACK; } /*-------------------------------------------------- @@ -1441,7 +1430,7 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt) return; } - if (player->itemroulette) + if (player->itemRoulette.active == true) { // Mashing behaviors K_BotItemRouletteMash(player, cmd); diff --git a/src/k_collide.c b/src/k_collide.c index 8b4a7bb90..8ac2f68bf 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -12,6 +12,7 @@ #include "doomdef.h" // Sink snipe print #include "g_game.h" // Sink snipe print #include "k_objects.h" +#include "k_roulette.h" angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) { @@ -158,10 +159,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) } else { - K_DropItems(t2->player); //K_StripItems(t2->player); - //K_StripOther(t2->player); - t2->player->itemroulette = 1; - t2->player->roulettetype = 2; + K_StartEggmanRoulette(t2->player); } if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD) @@ -319,7 +317,7 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) if (!spin) { - K_SpawnBrolyKi(actor, minehitlag); + Obj_SpawnBrolyKi(actor, minehitlag); return minehitlag; } @@ -345,7 +343,7 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2) // Bomb punting if ((t1->state >= &states[S_SSMINE1] && t1->state <= &states[S_SSMINE4]) - || (t1->state >= &states[S_SSMINE_DEPLOY8] && t1->state <= &states[S_SSMINE_DEPLOY13])) + || (t1->state >= &states[S_SSMINE_DEPLOY8] && t1->state <= &states[S_SSMINE_EXPLODE2])) { P_KillMobj(t1, t2, t2, DMG_NORMAL); } diff --git a/src/k_color.c b/src/k_color.c index ac1a2e6c8..d73a938e7 100644 --- a/src/k_color.c +++ b/src/k_color.c @@ -24,11 +24,18 @@ --------------------------------------------------*/ UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b) { - UINT32 redweight = 1063 * r; - UINT32 greenweight = 3576 * g; - UINT32 blueweight = 361 * b; - UINT32 brightness = (redweight + greenweight + blueweight) / 5000; - return min(brightness, UINT8_MAX); + double redWeight = ((r * 1.0) / UINT8_MAX); + double greenWeight = ((g * 1.0) / UINT8_MAX); + double blueWeight = ((b * 1.0) / UINT8_MAX); + double brightness = 0.5; + + redWeight = pow(redWeight, 2.2) * 0.2126; + greenWeight = pow(greenWeight, 2.2) * 0.7152; + blueWeight = pow(greenWeight, 2.2) * 0.0722; + + brightness = pow(redWeight + greenWeight + blueWeight, 1.0 / 2.2); + + return (UINT8)(brightness * UINT8_MAX); } /*-------------------------------------------------- diff --git a/src/k_hud.c b/src/k_hud.c index 238da54ef..bed21baef 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -36,6 +36,7 @@ #include "r_things.h" #include "r_fps.h" #include "m_random.h" +#include "k_roulette.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -1069,64 +1070,83 @@ static void K_drawKartItem(void) // Why write V_DrawScaledPatch calls over and over when they're all the same? // Set to 'no item' just in case. const UINT8 offset = ((r_splitscreen > 1) ? 1 : 0); - patch_t *localpatch = kp_nodraw; + patch_t *localpatch[3] = { kp_nodraw, kp_nodraw, kp_nodraw }; patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... const INT32 numberdisplaymin = ((!offset && stplyr->itemtype == KITEM_ORBINAUT) ? 5 : 2); INT32 itembar = 0; INT32 maxl = 0; // itembar's normal highest value - const INT32 barlength = (r_splitscreen > 1 ? 12 : 26); - UINT16 localcolor = SKINCOLOR_NONE; - SINT8 colormode = TC_RAINBOW; - UINT8 *colmap = NULL; + const INT32 barlength = (offset ? 12 : 26); + UINT16 localcolor[3] = { stplyr->skincolor }; + SINT8 colormode[3] = { TC_RAINBOW }; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff - if (stplyr->itemroulette) + fixed_t rouletteOffset = 0; + fixed_t rouletteSpace = ROULETTE_SPACING; + vector2_t rouletteCrop = {7, 7}; + INT32 i; + + if (stplyr->itemRoulette.itemListLen > 0) { - const INT32 item = K_GetRollingRouletteItem(stplyr); - - if (stplyr->skincolor) - localcolor = stplyr->skincolor; - - switch (item) + // Init with item roulette stuff. + for (i = 0; i < 3; i++) { - case KITEM_INVINCIBILITY: - localpatch = localinv; - break; + const SINT8 indexOfs = i-1; + const size_t index = (stplyr->itemRoulette.index + indexOfs) % stplyr->itemRoulette.itemListLen; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[3 + offset]; - break; + const SINT8 result = stplyr->itemRoulette.itemList[index]; + const SINT8 item = K_ItemResultToType(result); + const UINT8 amt = K_ItemResultToAmount(result); - default: - localpatch = K_GetCachedItemPatch(item, offset); - break; + switch (item) + { + case KITEM_INVINCIBILITY: + localpatch[i] = localinv; + break; + + case KITEM_ORBINAUT: + localpatch[i] = kp_orbinaut[(offset ? 4 : min(amt-1, 3))]; + break; + + default: + localpatch[i] = K_GetCachedItemPatch(item, offset); + break; + } } } + + if (stplyr->itemRoulette.active == true) + { + rouletteOffset = K_GetRouletteOffset(&stplyr->itemRoulette, rendertimefrac); + } else { // I'm doing this a little weird and drawing mostly in reverse order // The only actual reason is to make sneakers line up this way in the code below // This shouldn't have any actual baring over how it functions // Hyudoro is first, because we're drawing it on top of the player's current item + + localcolor[1] = SKINCOLOR_NONE; + rouletteOffset = stplyr->karthud[khud_rouletteoffset]; + if (stplyr->stealingtimer < 0) { if (leveltime & 2) - localpatch = kp_hyudoro[offset]; + localpatch[1] = kp_hyudoro[offset]; else - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { - localpatch = kp_hyudoro[offset]; + localpatch[1] = kp_hyudoro[offset]; } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) - localpatch = kp_eggman[offset]; + localpatch[1] = kp_eggman[offset]; else - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->ballhogcharge > 0) { @@ -1134,9 +1154,9 @@ static void K_drawKartItem(void) maxl = (((stplyr->itemamount-1) * BALLHOGINCREMENT) + 1); if (leveltime & 1) - localpatch = kp_ballhog[offset]; + localpatch[1] = kp_ballhog[offset]; else - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->rocketsneakertimer > 1) { @@ -1144,31 +1164,31 @@ static void K_drawKartItem(void) maxl = (itemtime*3) - barlength; if (leveltime & 1) - localpatch = kp_rocketsneaker[offset]; + localpatch[1] = kp_rocketsneaker[offset]; else - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch = kp_sadface[offset]; + localpatch[1] = kp_sadface[offset]; else - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } else { if (stplyr->itemamount <= 0) return; - switch(stplyr->itemtype) + switch (stplyr->itemtype) { case KITEM_INVINCIBILITY: - localpatch = localinv; + localpatch[1] = localinv; localbg = kp_itembg[offset+1]; break; case KITEM_ORBINAUT: - localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; + localpatch[1] = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; break; case KITEM_SPB: @@ -1179,44 +1199,45 @@ static void K_drawKartItem(void) /*FALLTHRU*/ default: - localpatch = K_GetCachedItemPatch(stplyr->itemtype, offset); + localpatch[1] = K_GetCachedItemPatch(stplyr->itemtype, offset); - if (localpatch == NULL) - localpatch = kp_nodraw; // diagnose underflows + if (localpatch[1] == NULL) + localpatch[1] = kp_nodraw; // diagnose underflows break; } if ((stplyr->pflags & PF_ITEMOUT) && !(leveltime & 1)) - localpatch = kp_nodraw; + localpatch[1] = kp_nodraw; } if (stplyr->karthud[khud_itemblink] && (leveltime & 1)) { - colormode = TC_BLINK; + colormode[1] = TC_BLINK; switch (stplyr->karthud[khud_itemblinkmode]) { case 2: - localcolor = K_RainbowColor(leveltime); + localcolor[1] = K_RainbowColor(leveltime); break; case 1: - localcolor = SKINCOLOR_RED; + localcolor[1] = SKINCOLOR_RED; break; default: - localcolor = SKINCOLOR_WHITE; + localcolor[1] = SKINCOLOR_WHITE; break; } } + else + { + // Hide the other items. + // Effectively lets the other roulette items + // show flicker away after you select. + localpatch[0] = localpatch[2] = kp_nodraw; + } } // pain and suffering defined below - if (r_splitscreen < 2) // don't change shit for THIS splitscreen. - { - fx = ITEM_X; - fy = ITEM_Y; - fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; - } - else // now we're having a fun game. + if (offset) { if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... { @@ -1231,35 +1252,91 @@ static void K_drawKartItem(void) fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; flipamount = true; } - } - if (localcolor != SKINCOLOR_NONE) - colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); + rouletteSpace = ROULETTE_SPACING_SPLITSCREEN; + rouletteOffset = FixedMul(rouletteOffset, FixedDiv(ROULETTE_SPACING_SPLITSCREEN, ROULETTE_SPACING)); + rouletteCrop.x = 16; + rouletteCrop.y = 15; + } + else + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; + } V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); - //V_SetClipRect((fx + 10) << FRACBITS, (fy + 10) << FRACBITS, 30 << FRACBITS, 30 << FRACBITS, V_HUDTRANS|V_SLIDEIN|fflags); + // Need to draw these in a particular order, for sorting. + V_SetClipRect( + (fx + rouletteCrop.x) << FRACBITS, (fy + rouletteCrop.y) << FRACBITS, + rouletteSpace, rouletteSpace, + V_SLIDEIN|fflags + ); - // Then, the numbers: - if (stplyr->itemamount >= numberdisplaymin && !stplyr->itemroulette) + V_DrawFixedPatch( + fx<itemRoulette.active == true) { - V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. - V_DrawFixedPatch(fx<itemamount)); - else - V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", stplyr->itemamount)); - else - { - V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); - V_DrawKartString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|fflags, va("%d", stplyr->itemamount)); - } + // Draw the item underneath the box. + V_DrawFixedPatch( + fx<itemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + { + // Then, the numbers: + V_DrawScaledPatch( + fx + (flipamount ? 48 : 0), fy, + V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), + kp_itemmulsticker[offset] + ); // flip this graphic for p2 and p4 in split and shift it. + + V_DrawFixedPatch( + fx<itemamount)); + else + V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SLIDEIN|fflags, va("x%d", stplyr->itemamount)); + } + else + { + V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); + V_DrawKartString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|fflags, va("%d", stplyr->itemamount)); + } + } + else + { + V_DrawFixedPatch( + fx<> 1); + const fixed_t space = 24 * scale; + const fixed_t pad = 9 * scale; + + fixed_t x = -pad; + fixed_t y = -pad; + size_t i; if (stplyr != &players[displayplayers[0]]) // only for p1 - return; - - if (K_ForcedSPB(stplyr) == true) { - V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[KITEM_SPB]); - V_DrawThinString(x+11, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, "EX"); return; } - // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - pingame++; - if (players[i].bumpers > bestbumper) - bestbumper = players[i].bumpers; - } + K_FillItemRouletteData(stplyr, &rouletteData); - // lovely double loop...... - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < rouletteData.itemListLen; i++) { - if (playeringame[i] && !players[i].spectator - && players[i].position == 1) + const kartitems_t item = rouletteData.itemList[i]; + UINT8 amount = 1; + + if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) { - // This player is first! Yay! - pdis = stplyr->distancetofinish - players[i].distancetofinish; - break; + x += space; + y = -pad; } - } - pdis = K_ScaleItemDistance(pdis, pingame); - - if (stplyr->bot && stplyr->botvars.rival) - { - // Rival has better odds :) - pdis = (15 * pdis) / 14; - } - - useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - INT32 itemodds = K_KartGetItemOdds( - useodds, i, - stplyr->distancetofinish, - 0, - stplyr->bot, (stplyr->bot && stplyr->botvars.rival) - ); - INT32 amount = 1; - - if (itemodds <= 0) - continue; - - V_DrawScaledPatch(x, y, V_SNAPTOTOP, items[i]); - V_DrawThinString(x+11, y+31, V_SNAPTOTOP, va("%d", itemodds)); + V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, patches[item], NULL); // Display amount for multi-items - amount = K_ItemResultToAmount(i); + amount = K_ItemResultToAmount(item); if (amount > 1) { - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("x%d", amount)); + V_DrawStringScaled( + x + (18 * scale), + y + (23 * scale), + scale, FRACUNIT, FRACUNIT, + V_ALLOWLOWERCASE|V_SNAPTOTOP, + NULL, HU_FONT, + va("x%d", amount) + ); } - x += 32; - if (x >= 297) - { - x = -9; - y += 32; - } + y += space; } - V_DrawString(0, 0, V_SNAPTOTOP, va("USEODDS %d", useodds)); + V_DrawString((x >> FRACBITS) + 20, 2, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); + V_DrawString((x >> FRACBITS) + 20, 10, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + + V_DrawString((x >> FRACBITS) + 20, 22, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); + V_DrawString((x >> FRACBITS) + 20, 30, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); + + V_DrawString((x >> FRACBITS) + 20, 42, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); + V_DrawString((x >> FRACBITS) + 20, 50, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); + V_DrawString((x >> FRACBITS) + 20, 58, V_ALLOWLOWERCASE|V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + +#ifndef ITEM_LIST_SIZE + Z_Free(rouletteData.itemList); +#endif } static void K_DrawWaypointDebugger(void) diff --git a/src/k_kart.c b/src/k_kart.c index 7799b9f93..7c2efe279 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -42,6 +42,7 @@ #include "k_objects.h" #include "k_grandprix.h" #include "k_specialstage.h" +#include "k_roulette.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -295,35 +296,12 @@ angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, void K_RegisterKartStuff(void) { - CV_RegisterVar(&cv_sneaker); - CV_RegisterVar(&cv_rocketsneaker); - CV_RegisterVar(&cv_invincibility); - CV_RegisterVar(&cv_banana); - CV_RegisterVar(&cv_eggmanmonitor); - CV_RegisterVar(&cv_orbinaut); - CV_RegisterVar(&cv_jawz); - CV_RegisterVar(&cv_mine); - CV_RegisterVar(&cv_landmine); - CV_RegisterVar(&cv_ballhog); - CV_RegisterVar(&cv_selfpropelledbomb); - CV_RegisterVar(&cv_grow); - CV_RegisterVar(&cv_shrink); - CV_RegisterVar(&cv_lightningshield); - CV_RegisterVar(&cv_bubbleshield); - CV_RegisterVar(&cv_flameshield); - CV_RegisterVar(&cv_hyudoro); - CV_RegisterVar(&cv_pogospring); - CV_RegisterVar(&cv_superring); - CV_RegisterVar(&cv_kitchensink); - CV_RegisterVar(&cv_droptarget); - CV_RegisterVar(&cv_gardentop); + INT32 i; - CV_RegisterVar(&cv_dualsneaker); - CV_RegisterVar(&cv_triplesneaker); - CV_RegisterVar(&cv_triplebanana); - CV_RegisterVar(&cv_tripleorbinaut); - CV_RegisterVar(&cv_quadorbinaut); - CV_RegisterVar(&cv_dualjawz); + for (i = 0; i < NUMKARTRESULTS-1; i++) + { + CV_RegisterVar(&cv_items[i]); + } CV_RegisterVar(&cv_kartspeed); CV_RegisterVar(&cv_kartbumpers); @@ -397,158 +375,6 @@ fixed_t K_GetKartGameSpeedScalar(SINT8 value) return ((13 + (3*value)) << FRACBITS) / 16; } -//{ SRB2kart Roulette Code - Position Based - -consvar_t *KartItemCVars[NUMKARTRESULTS-1] = -{ - &cv_sneaker, - &cv_rocketsneaker, - &cv_invincibility, - &cv_banana, - &cv_eggmanmonitor, - &cv_orbinaut, - &cv_jawz, - &cv_mine, - &cv_landmine, - &cv_ballhog, - &cv_selfpropelledbomb, - &cv_grow, - &cv_shrink, - &cv_lightningshield, - &cv_bubbleshield, - &cv_flameshield, - &cv_hyudoro, - &cv_pogospring, - &cv_superring, - &cv_kitchensink, - &cv_droptarget, - &cv_gardentop, - &cv_gachabom, - &cv_dualsneaker, - &cv_triplesneaker, - &cv_triplebanana, - &cv_tripleorbinaut, - &cv_quadorbinaut, - &cv_dualjawz, - &cv_triplegachabom -}; - -#define NUMKARTODDS 80 - -// Less ugly 2D arrays -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = -{ - //B C D E F G H I - { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker - { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker - { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility - { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana - { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut - { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - { 0, 3, 3, 2, 0, 0, 0, 0 }, // Mine - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine - { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb - { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow - { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink - { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield - { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target - { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom - { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 - { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 - { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 2, 1, 0, 0, 0 }, // Jawz x2 - { 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3 -}; - -static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = -{ - //K L - { 2, 1 }, // Sneaker - { 0, 0 }, // Rocket Sneaker - { 4, 1 }, // Invincibility - { 0, 0 }, // Banana - { 1, 0 }, // Eggman Monitor - { 8, 0 }, // Orbinaut - { 8, 1 }, // Jawz - { 6, 1 }, // Mine - { 2, 0 }, // Land Mine - { 2, 1 }, // Ballhog - { 0, 0 }, // Self-Propelled Bomb - { 2, 1 }, // Grow - { 0, 0 }, // Shrink - { 4, 0 }, // Lightning Shield - { 1, 0 }, // Bubble Shield - { 1, 0 }, // Flame Shield - { 2, 0 }, // Hyudoro - { 3, 0 }, // Pogo Spring - { 0, 0 }, // Super Ring - { 0, 0 }, // Kitchen Sink - { 2, 0 }, // Drop Target - { 4, 0 }, // Garden Top - { 0, 0 }, // Gachabom - { 0, 0 }, // Sneaker x2 - { 0, 1 }, // Sneaker x3 - { 0, 0 }, // Banana x3 - { 2, 0 }, // Orbinaut x3 - { 1, 1 }, // Orbinaut x4 - { 5, 1 }, // Jawz x2 - { 0, 0 } // Gachabom x3 -}; - -// TODO: add back when this gets used -#if 0 -static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = -{ - //M N O P - { 1, 1, 0, 0 }, // Sneaker - { 0, 0, 0, 0 }, // Rocket Sneaker - { 0, 0, 0, 0 }, // Invincibility - { 0, 0, 0, 0 }, // Banana - { 0, 0, 0, 0 }, // Eggman Monitor - { 1, 1, 0, 0 }, // Orbinaut - { 1, 1, 0, 0 }, // Jawz - { 0, 0, 0, 0 }, // Mine - { 0, 0, 0, 0 }, // Land Mine - { 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 1 }, // Self-Propelled Bomb - { 0, 0, 0, 0 }, // Grow - { 0, 0, 0, 0 }, // Shrink - { 0, 0, 0, 0 }, // Lightning Shield - { 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0 }, // Flame Shield - { 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0 }, // Pogo Spring - { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Kitchen Sink - { 0, 0, 0, 0 }, // Drop Target - { 0, 0, 0, 0 }, // Garden Top - { 0, 0, 0, 0 }, // Gachabom - { 0, 1, 1, 0 }, // Sneaker x2 - { 0, 0, 1, 1 }, // Sneaker x3 - { 0, 0, 0, 0 }, // Banana x3 - { 0, 1, 1, 0 }, // Orbinaut x3 - { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 }, // Jawz x2 - { 0, 0, 0, 0 } // Gachabom x3 -}; -#endif - -#define DISTVAR (2048) // Magic number distance for use with item roulette tiers -#define SPBSTARTDIST (6*DISTVAR) // Distance when SPB can start appearing -#define SPBFORCEDIST (12*DISTVAR) // Distance when SPB is forced onto the next person who rolls an item -#define ENDDIST (12*DISTVAR) // Distance when the game stops giving you bananas - // Array of states to pick the starting point of the animation, based on the actual time left for invincibility. static INT32 K_SparkleTrailStartStates[KART_NUMINVSPARKLESANIM][2] = { {S_KARTINVULN12, S_KARTINVULNB12}, @@ -681,813 +507,6 @@ void K_RunItemCooldowns(void) } } - -/** \brief Item Roulette for Kart - - \param player player - \param getitem what item we're looking for - - \return void -*/ -static void K_KartGetItemResult(player_t *player, SINT8 getitem) -{ - if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) - { - K_SetItemCooldown(getitem, 20*TICRATE); - } - - player->botvars.itemdelay = TICRATE; - player->botvars.itemconfirm = 0; - - player->itemtype = K_ItemResultToType(getitem); - player->itemamount = K_ItemResultToAmount(getitem); -} - -fixed_t K_ItemOddsScale(UINT8 playerCount) -{ - const UINT8 basePlayer = 8; // The player count we design most of the game around. - fixed_t playerScaling = 0; - - if (playerCount < 2) - { - // Cap to 1v1 scaling - playerCount = 2; - } - - // Then, it multiplies it further if the player count isn't equal to basePlayer. - // This is done to make low player count races more interesting and high player count rates more fair. - if (playerCount < basePlayer) - { - // Less than basePlayer: increase odds significantly. - // 2P: x2.5 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 4); - } - else if (playerCount > basePlayer) - { - // More than basePlayer: reduce odds slightly. - // 16P: x0.75 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 32); - } - - return playerScaling; -} - -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) -{ - if (mapobjectscale != FRACUNIT) - { - // Bring back to normal scale. - distance = FixedDiv(distance, mapobjectscale); - } - - if (franticitems == true) - { - // Frantic items pretends everyone's farther apart, for crazier items. - distance = (15 * distance) / 14; - } - - // Items get crazier with the fewer players that you have. - distance = FixedMul( - distance, - FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) - ); - - return distance; -} - -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ - -INT32 K_KartGetItemOdds( - UINT8 pos, SINT8 item, - UINT32 ourDist, - fixed_t mashed, - boolean bot, boolean rival) -{ - INT32 newodds; - INT32 i; - - UINT8 pingame = 0, pexiting = 0; - - player_t *first = NULL; - player_t *second = NULL; - - UINT32 firstDist = UINT32_MAX; - UINT32 secondDist = UINT32_MAX; - UINT32 secondToFirst = 0; - boolean isFirst = false; - - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean notNearEnd = false; - - INT32 shieldtype = KSHIELD_NONE; - - I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists - - if (!KartItemCVars[item-1]->value && !modeattacking) - { - return 0; - } - - if (K_GetItemCooldown(item) > 0) - { - // Cooldown is still running, don't give another. - return 0; - } - - /* - if (bot) - { - // TODO: Item use on bots should all be passed-in functions. - // Instead of manually inserting these, it should return 0 - // for any items without an item use function supplied - - switch (item) - { - case KITEM_SNEAKER: - break; - default: - return 0; - } - } - */ - (void)bot; - - if (gametype == GT_BATTLE) - { - I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newodds = K_KartItemOddsBattle[item-1][pos]; - } - else - { - I_Assert(pos < 8); // Ditto - newodds = K_KartItemOddsRace[item-1][pos]; - } - - // Base multiplication to ALL item odds to simulate fractional precision - newodds *= 4; - - shieldtype = K_GetShieldFromItem(item); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (!(gametyperules & GTR_BUMPERS) || players[i].bumpers) - pingame++; - - if (players[i].exiting) - pexiting++; - - switch (shieldtype) - { - case KSHIELD_NONE: - /* Marble Garden Top is not REALLY - a Sonic 3 shield */ - case KSHIELD_TOP: - break; - - default: - if (shieldtype == K_GetShieldFromItem(players[i].itemtype)) - { - // Don't allow more than one of each shield type at a time - return 0; - } - } - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - - if (first != NULL) // calculate 2nd's distance from 1st, for SPB - { - firstDist = first->distancetofinish; - isFirst = (ourDist <= firstDist); - } - - if (second != NULL) - { - secondDist = second->distancetofinish; - } - - if (first != NULL && second != NULL) - { - secondToFirst = secondDist - firstDist; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); // Reversed scaling, so 16P is like 1v1, and 1v1 is like 16P - } - - switch (item) - { - case KITEM_BANANA: - case KITEM_EGGMAN: - case KITEM_SUPERRING: - notNearEnd = true; - break; - - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_LANDMINE: - case KITEM_DROPTARGET: - case KITEM_BALLHOG: - case KITEM_HYUDORO: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - powerItem = true; - break; - - case KRITEM_TRIPLEBANANA: - powerItem = true; - notNearEnd = true; - break; - - case KITEM_INVINCIBILITY: - case KITEM_MINE: - case KITEM_GROW: - case KITEM_BUBBLESHIELD: - case KITEM_FLAMESHIELD: - cooldownOnStart = true; - powerItem = true; - break; - - case KITEM_SPB: - cooldownOnStart = true; - notNearEnd = true; - - if (firstDist < ENDDIST*2 // No SPB when 1st is almost done - || isFirst == true) // No SPB for 1st ever - { - newodds = 0; - } - else - { - const UINT32 dist = max(0, ((signed)secondToFirst) - SPBSTARTDIST); - const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const UINT8 maxOdds = 20; - fixed_t multiplier = (dist * FRACUNIT) / distRange; - - if (multiplier < 0) - { - multiplier = 0; - } - - if (multiplier > FRACUNIT) - { - multiplier = FRACUNIT; - } - - newodds = FixedMul(maxOdds * 4, multiplier); - } - break; - - case KITEM_SHRINK: - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; - - if (pingame-1 <= pexiting) - newodds = 0; - break; - - case KITEM_LIGHTNINGSHIELD: - cooldownOnStart = true; - powerItem = true; - - if (spbplace != -1) - newodds = 0; - break; - - default: - break; - } - - if (newodds == 0) - { - // Nothing else we want to do with odds matters at this point :p - 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) && (ourDist < 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. - fixed_t fracOdds = newodds * FRACUNIT; - - if (franticitems == true) - { - // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. - fracOdds *= 2; - } - - if (rival == true) - { - // The Rival bot gets frantic-like items, also :p - fracOdds *= 2; - } - - fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame)); - - if (mashed > 0) - { - // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. - fracOdds = FixedDiv(fracOdds, FRACUNIT + mashed); - } - - newodds = fracOdds / FRACUNIT; - } - - return newodds; -} - -//{ SRB2kart Roulette Code - Distance Based, yes waypoints - -UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper) -{ - UINT8 i; - UINT8 useodds = 0; - UINT8 disttable[14]; - UINT8 distlen = 0; - boolean oddsvalid[8]; - - // Unused now, oops :V - (void)bestbumper; - - for (i = 0; i < 8; i++) - { - UINT8 j; - boolean available = false; - - if (gametype == GT_BATTLE && i > 1) - { - oddsvalid[i] = false; - break; - } - - for (j = 1; j < NUMKARTRESULTS; j++) - { - if (K_KartGetItemOdds( - i, j, - player->distancetofinish, - mashed, - player->bot, (player->bot && player->botvars.rival) - ) > 0) - { - available = true; - break; - } - } - - oddsvalid[i] = available; - } - -#define SETUPDISTTABLE(odds, num) \ - if (oddsvalid[odds]) \ - for (i = num; i; --i) \ - disttable[distlen++] = odds; - - if (gametype == GT_BATTLE) // Battle Mode - { - if (player->roulettetype == 1 && oddsvalid[1] == true) - { - // 1 is the extreme odds of player-controlled "Karma" items - useodds = 1; - } - else - { - useodds = 0; - - if (oddsvalid[0] == false && oddsvalid[1] == true) - { - // try to use karma odds as a fallback - useodds = 1; - } - } - } - else - { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,2); - SETUPDISTTABLE(4,2); - SETUPDISTTABLE(5,3); - SETUPDISTTABLE(6,3); - SETUPDISTTABLE(7,1); - - if (pdis == 0) - useodds = disttable[0]; - else if (pdis > DISTVAR * ((12 * distlen) / 14)) - useodds = disttable[distlen-1]; - else - { - for (i = 1; i < 13; i++) - { - if (pdis <= DISTVAR * ((i * distlen) / 14)) - { - useodds = disttable[((i * distlen) / 14)]; - break; - } - } - } - } - -#undef SETUPDISTTABLE - - return useodds; -} - -INT32 K_GetRollingRouletteItem(player_t *player) -{ - static UINT8 translation[NUMKARTITEMS-1]; - static UINT16 roulette_size; - - static INT16 odds_cached = -1; - - // Race odds have more columns than Battle - const UINT8 EMPTYODDS[sizeof K_KartItemOddsRace[0]] = {0}; - - if (odds_cached != gametype) - { - UINT8 *odds_row; - size_t odds_row_size; - - UINT8 i; - - roulette_size = 0; - - if (gametype == GT_BATTLE) - { - odds_row = K_KartItemOddsBattle[0]; - odds_row_size = sizeof K_KartItemOddsBattle[0]; - } - else - { - odds_row = K_KartItemOddsRace[0]; - odds_row_size = sizeof K_KartItemOddsRace[0]; - } - - for (i = 1; i < NUMKARTITEMS; ++i) - { - if (memcmp(odds_row, EMPTYODDS, odds_row_size)) - { - translation[roulette_size] = i; - roulette_size++; - } - - odds_row += odds_row_size; - } - - roulette_size *= 3; - odds_cached = gametype; - } - - return translation[(player->itemroulette % roulette_size) / 3]; -} - -boolean K_ForcedSPB(player_t *player) -{ - player_t *first = NULL; - player_t *second = NULL; - UINT32 secondToFirst = UINT32_MAX; - UINT8 pingame = 0; - UINT8 i; - - if (!cv_selfpropelledbomb.value) - { - return false; - } - - if (!(gametyperules & GTR_CIRCUIT)) - { - return false; - } - - if (player->position <= 1) - { - return false; - } - - if (spbplace != -1) - { - return false; - } - - if (itemCooldowns[KITEM_SPB - 1] > 0) - { - return false; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - { - continue; - } - - if (players[i].exiting) - { - return false; - } - - pingame++; - - if (players[i].position == 1) - { - first = &players[i]; - } - - if (players[i].position == 2) - { - second = &players[i]; - } - } - -#if 0 - if (pingame <= 2) - { - return false; - } -#endif - - if (first != NULL && second != NULL) - { - secondToFirst = second->distancetofinish - first->distancetofinish; - secondToFirst = K_ScaleItemDistance(secondToFirst, 16 - pingame); - } - - return (secondToFirst >= SPBFORCEDIST); -} - -static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) -{ - INT32 i; - UINT8 pingame = 0; - UINT8 roulettestop; - UINT32 pdis = 0; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - UINT8 bestbumper = 0; - fixed_t mashed = 0; - - // This makes the roulette cycle through items - if this is 0, you shouldn't be here. - if (!player->itemroulette) - return; - player->itemroulette++; - - // Gotta check how many players are active at this moment. - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - pingame++; - - if (players[i].bumpers > bestbumper) - bestbumper = players[i].bumpers; - } - - // This makes the roulette produce the random noises. - if ((player->itemroulette % 3) == 1 && P_IsDisplayPlayer(player) && !demo.freecam) - { -#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->itemroulette / 3) % 8)) - for (i = 0; i <= r_splitscreen; i++) - { - if (player == &players[displayplayers[i]] && players[displayplayers[i]].itemroulette) - PLAYROULETTESND; - } -#undef PLAYROULETTESND - } - - roulettestop = TICRATE + (3*(pingame - player->position)); - - // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. - // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. - // Finally, if you get past this check, now you can actually start calculating what item you get. - if ((cmd->buttons & BT_ATTACK) && (player->itemroulette >= roulettestop) - && !(player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS))) - { - // Mashing reduces your chances for the good items - mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; - } - else if (!(player->itemroulette >= (TICRATE*3))) - return; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator - && players[i].position == 1) - { - // This player is first! Yay! - - if (player->distancetofinish <= players[i].distancetofinish) - { - // Guess you're in first / tied for first? - pdis = 0; - } - else - { - // Subtract 1st's distance from your distance, to get your distance from 1st! - pdis = player->distancetofinish - players[i].distancetofinish; - } - break; - } - } - - pdis = K_ScaleItemDistance(pdis, pingame); - - if (player->bot && player->botvars.rival) - { - // Rival has better odds :) - pdis = (15 * pdis) / 14; - } - - // SPECIAL CASE No. 1: - // Fake Eggman items - if (player->roulettetype == 2) - { - player->eggmanexplode = 4*TICRATE; - //player->karthud[khud_itemblink] = TICRATE; - //player->karthud[khud_itemblinkmode] = 1; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, sfx_itrole); - return; - } - - // SPECIAL CASE No. 2: - // Give a debug item instead if specified - if (cv_kartdebugitem.value != 0 && !modeattacking) - { - K_KartGetItemResult(player, cv_kartdebugitem.value); - player->itemamount = cv_kartdebugamount.value; - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 2; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, sfx_dbgsal); - return; - } - - // SPECIAL CASE No. 3: - // Record Attack / alone mashing behavior - if (modeattacking || pingame == 1) - { - if (gametype == GT_RACE) - { - if (mashed && (modeattacking || cv_superring.value)) // ANY mashed value? You get rings. - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblinkmode] = 1; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else - { - if (modeattacking || cv_sneaker.value) // Waited patiently? You get a sneaker! - K_KartGetItemResult(player, KITEM_SNEAKER); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - else if (gametype == GT_BATTLE) - { - if (mashed && (modeattacking || bossinfo.boss || cv_gachabom.value)) // ANY mashed value? You get a Gachabom. - { - K_KartGetItemResult(player, KITEM_GACHABOM); - player->karthud[khud_itemblinkmode] = 1; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else if (bossinfo.boss) - { - K_KartGetItemResult(player, KITEM_ORBINAUT); // FIXME - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - else - { - if (modeattacking || cv_triplegachabom.value) // Waited patiently? You get Gachabom x3! - K_KartGetItemResult(player, KRITEM_TRIPLEGACHABOM); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - - player->karthud[khud_itemblink] = TICRATE; - player->itemroulette = 0; - player->roulettetype = 0; - return; - } - - // SPECIAL CASE No. 4: - // Being in ring debt occasionally forces Super Ring on you if you mashed - if (!(gametyperules & GTR_SPHERES) && mashed && player->rings < 0 && cv_superring.value) - { - INT32 debtamount = min(20, abs(player->rings)); - if (P_RandomChance(PR_ITEM_ROULETTE, (debtamount*FRACUNIT)/20)) - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 1; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - return; - } - } - - // SPECIAL CASE No. 5: - // Force SPB if 2nd is way too far behind - if (K_ForcedSPB(player) == true) - { - K_KartGetItemResult(player, KITEM_SPB); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 2; - player->itemroulette = 0; - player->roulettetype = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolk); - return; - } - - // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. - // Initializes existing spawnchance values - for (i = 0; i < NUMKARTRESULTS; i++) - spawnchance[i] = 0; - - // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - player->distancetofinish, - mashed, - player->bot, (player->bot && player->botvars.rival)) - ); - } - - // Award the player whatever power is rolled - if (totalspawnchance > 0) - { - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - K_KartGetItemResult(player, i); - } - else - { - player->itemtype = KITEM_SAD; - player->itemamount = 1; - } - - if (P_IsDisplayPlayer(player) && !demo.freecam) - S_StartSound(NULL, ((player->roulettetype == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); - - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = ((player->roulettetype == 1) ? 2 : (mashed ? 1 : 0)); - - player->itemroulette = 0; // Since we're done, clear the roulette number - player->roulettetype = 0; // This too -} - //} //{ SRB2kart p_user.c Stuff @@ -5223,39 +4242,6 @@ void K_SpawnMineExplosion(mobj_t *source, UINT8 color, tic_t delay) } } -void K_SpawnBrolyKi(mobj_t *source, tic_t duration) -{ - mobj_t *x; - - if (duration == 0) - { - return; - } - - x = P_SpawnMobjFromMobj(source, 0, 0, 0, MT_THOK); - - // Shrink into center of source object. - x->z = (source->z + source->height / 2); - x->height = 0; - - P_SetMobjState(x, S_BROLY1); - x->colorized = true; - x->color = source->color; - x->hitlag = 0; // do not copy source hitlag - - P_SetScale(x, 64 * mapobjectscale); - x->scalespeed = x->scale / duration; - - // The last tic doesn't actually get rendered so in order - // to show scale = destscale, add one buffer tic. - x->tics = (duration + 1); - x->destscale = 1; // 0 also doesn't work - - K_ReduceVFX(x, NULL); - - S_StartSound(x, sfx_cdfm74); -} - #undef MINEQUAKEDIST fixed_t K_ItemScaleForPlayer(player_t *player) @@ -7096,6 +6082,7 @@ 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; @@ -7105,13 +6092,12 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; + K_FillItemRouletteData(NULL, &rouletteData); + for (i = 1; i < NUMKARTRESULTS; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - UINT32_MAX, - 0, - false, false) + spawnchance[i] = ( + totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) ); } @@ -7984,6 +6970,20 @@ void K_KartPlayerHUDUpdate(player_t *player) player->karthud[khud_itemblink] = 0; } + if (player->karthud[khud_rouletteoffset] != 0) + { + if (abs(player->karthud[khud_rouletteoffset]) < (FRACUNIT >> 1)) + { + // Snap to 0, since the change is getting very small. + player->karthud[khud_rouletteoffset] = 0; + } + else + { + // Lerp to 0. + player->karthud[khud_rouletteoffset] = FixedMul(player->karthud[khud_rouletteoffset], FRACUNIT*3/4); + } + } + if (!(gametyperules & GTR_SPHERES)) { if (player->mo && player->mo->hitlag <= 0) @@ -10119,10 +9119,9 @@ void K_StripItems(player_t *player) player->itemamount = 0; player->pflags &= ~(PF_ITEMOUT|PF_EGGMANOUT); - if (!player->itemroulette || player->roulettetype != 2) + if (player->itemRoulette.eggman == false) { - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; } player->hyudorotimer = 0; @@ -10138,8 +9137,7 @@ void K_StripItems(player_t *player) void K_StripOther(player_t *player) { - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; player->invincibilitytimer = 0; if (player->growshrinktimer) @@ -10159,7 +9157,7 @@ void K_StripOther(player_t *player) static INT32 K_FlameShieldMax(player_t *player) { UINT32 disttofinish = 0; - UINT32 distv = DISTVAR; + UINT32 distv = 2048; UINT8 numplayers = 0; UINT8 i; @@ -10831,7 +9829,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->itemtype == KITEM_NONE && NO_HYUDORO && !(HOLDING_ITEM || player->itemamount - || player->itemroulette + || player->itemRoulette.active == true || player->rocketsneakertimer || player->eggmanexplode)) player->pflags |= PF_USERINGS; @@ -11542,8 +10540,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (spbplace == -1 || player->position != spbplace) player->pflags &= ~PF_RINGLOCK; // reset ring lock - if (player->itemtype == KITEM_SPB - || player->itemtype == KITEM_SHRINK) + if (K_ItemSingularity(player->itemtype) == true) { K_SetItemCooldown(player->itemtype, 20*TICRATE); } diff --git a/src/k_kart.h b/src/k_kart.h index 87f3e1998..8831fe0a4 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -50,14 +50,6 @@ void K_ReduceVFX(mobj_t *mo, player_t *owner); boolean K_IsPlayerLosing(player_t *player); fixed_t K_GetKartGameSpeedScalar(SINT8 value); -extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; - -UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper); -fixed_t K_ItemOddsScale(UINT8 numPlayers); -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, fixed_t mashed, boolean bot, boolean rival); -INT32 K_GetRollingRouletteItem(player_t *player); -boolean K_ForcedSPB(player_t *player); INT32 K_GetShieldFromItem(INT32 item); SINT8 K_ItemResultToType(SINT8 getitem); UINT8 K_ItemResultToAmount(SINT8 getitem); @@ -107,7 +99,6 @@ void K_DestroyBumpers(player_t *player, UINT8 amount); void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount); void K_MineFlashScreen(mobj_t *source); void K_SpawnMineExplosion(mobj_t *source, UINT8 color, tic_t delay); -void K_SpawnBrolyKi(mobj_t *source, tic_t duration); void K_RunFinishLineBeam(void); UINT16 K_DriftSparkColor(player_t *player, INT32 charge); void K_SpawnBoostTrail(player_t *player); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8bfec4716..71f8b4242 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3455,7 +3455,7 @@ void M_DrawItemToggles(void) continue; } - cv = KartItemCVars[currentMenu->menuitems[thisitem].mvar1-1]; + cv = &cv_items[currentMenu->menuitems[thisitem].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); drawnum = K_ItemResultToAmount(currentMenu->menuitems[thisitem].mvar1); @@ -3502,7 +3502,7 @@ void M_DrawItemToggles(void) } else { - cv = KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1]; + cv = &cv_items[currentMenu->menuitems[itemOn].mvar1-1]; translucent = (cv->value ? 0 : V_TRANSLUCENT); drawnum = K_ItemResultToAmount(currentMenu->menuitems[itemOn].mvar1); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index ab43217ff..384905f71 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -5632,12 +5632,12 @@ void M_HandleItemToggles(INT32 choice) else if (currentMenu->menuitems[itemOn].mvar1 == 0) { - INT32 v = cv_sneaker.value; + INT32 v = cv_items[0].value; S_StartSound(NULL, sfx_s1b4); for (i = 0; i < NUMKARTRESULTS-1; i++) { - if (KartItemCVars[i]->value == v) - CV_AddValue(KartItemCVars[i], 1); + if (cv_items[i].value == v) + CV_AddValue(&cv_items[i], 1); } } else @@ -5650,7 +5650,7 @@ void M_HandleItemToggles(INT32 choice) { S_StartSound(NULL, sfx_s1ba); } - CV_AddValue(KartItemCVars[currentMenu->menuitems[itemOn].mvar1-1], 1); + CV_AddValue(&cv_items[currentMenu->menuitems[itemOn].mvar1-1], 1); } } diff --git a/src/k_objects.h b/src/k_objects.h index 96e0fa2b5..4cc4ed1c3 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -54,4 +54,8 @@ void Obj_DuelBombReverse(mobj_t *bomb); void Obj_DuelBombTouch(mobj_t *bomb, mobj_t *toucher); void Obj_DuelBombInit(mobj_t *bomb); +/* Broly Ki */ +mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); +void Obj_BrolyKiThink(mobj_t *ki); + #endif/*k_objects_H*/ diff --git a/src/k_roulette.c b/src/k_roulette.c new file mode 100644 index 000000000..40cd68af4 --- /dev/null +++ b/src/k_roulette.c @@ -0,0 +1,1332 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Kart Krew +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_roulette.c +/// \brief Item roulette code. + +#include "k_roulette.h" + +#include "d_player.h" +#include "doomdef.h" +#include "hu_stuff.h" +#include "g_game.h" +#include "m_random.h" +#include "p_local.h" +#include "p_slopes.h" +#include "p_setup.h" +#include "r_draw.h" +#include "r_local.h" +#include "r_things.h" +#include "s_sound.h" +#include "st_stuff.h" +#include "v_video.h" +#include "z_zone.h" +#include "m_misc.h" +#include "m_cond.h" +#include "f_finale.h" +#include "lua_hud.h" // For Lua hud checks +#include "lua_hook.h" // For MobjDamage and ShouldDamage +#include "m_cheat.h" // objectplacing +#include "p_spec.h" + +#include "k_kart.h" +#include "k_battle.h" +#include "k_boss.h" +#include "k_pwrlv.h" +#include "k_color.h" +#include "k_respawn.h" +#include "k_waypoint.h" +#include "k_bot.h" +#include "k_hud.h" +#include "k_terrain.h" +#include "k_director.h" +#include "k_collide.h" +#include "k_follower.h" +#include "k_objects.h" +#include "k_grandprix.h" +#include "k_specialstage.h" + +// Magic number distance for use with item roulette tiers +#define DISTVAR (2048) + +// Distance when SPB can start appearing +#define SPBSTARTDIST (8*DISTVAR) + +// Distance when SPB is forced onto the next person who rolls an item +#define SPBFORCEDIST (16*DISTVAR) + +// Distance when the game stops giving you bananas +#define ENDDIST (14*DISTVAR) + +// Consistent seed used for item reels +#define ITEM_REEL_SEED (0x22D5FAA8) + +#define FRANTIC_ITEM_SCALE (FRACUNIT*6/5) + +#define ROULETTE_SPEED_SLOWEST (20) +#define ROULETTE_SPEED_FASTEST (2) +#define ROULETTE_SPEED_DIST (150*DISTVAR) +#define ROULETTE_SPEED_TIMEATTACK (9) + +static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = +{ + { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker + { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker + { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility + { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana + { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor + { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut + { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz + { 0, 3, 3, 2, 0, 0, 0, 0 }, // Mine + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine + { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog + { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb + { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow + { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink + { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield + { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield + { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target + { 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top + { 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2 + { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 + { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 + { 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2 +}; + +static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = +{ + //K L + { 2, 1 }, // Sneaker + { 0, 0 }, // Rocket Sneaker + { 4, 1 }, // Invincibility + { 0, 0 }, // Banana + { 1, 0 }, // Eggman Monitor + { 8, 0 }, // Orbinaut + { 8, 1 }, // Jawz + { 6, 1 }, // Mine + { 2, 0 }, // Land Mine + { 2, 1 }, // Ballhog + { 0, 0 }, // Self-Propelled Bomb + { 2, 1 }, // Grow + { 0, 0 }, // Shrink + { 4, 0 }, // Lightning Shield + { 1, 0 }, // Bubble Shield + { 1, 0 }, // Flame Shield + { 2, 0 }, // Hyudoro + { 3, 0 }, // Pogo Spring + { 0, 0 }, // Super Ring + { 0, 0 }, // Kitchen Sink + { 2, 0 }, // Drop Target + { 4, 0 }, // Garden Top + { 0, 0 }, // Sneaker x2 + { 0, 1 }, // Sneaker x3 + { 0, 0 }, // Banana x3 + { 2, 0 }, // Orbinaut x3 + { 1, 1 }, // Orbinaut x4 + { 5, 1 } // Jawz x2 +}; + +static kartitems_t K_KartItemReelTimeAttack[] = +{ + KITEM_SNEAKER, + KITEM_SUPERRING, + KITEM_NONE +}; + +static kartitems_t K_KartItemReelBreakTheCapsules[] = +{ + KRITEM_TRIPLEORBINAUT, + KITEM_BANANA, + KITEM_NONE +}; + +static kartitems_t K_KartItemReelBoss[] = +{ + KITEM_ORBINAUT, + KITEM_BANANA, + KITEM_NONE +}; + +/*-------------------------------------------------- + boolean K_ItemEnabled(kartitems_t item) + + See header file for description. +--------------------------------------------------*/ +boolean K_ItemEnabled(kartitems_t item) +{ + if (item < 1 || item >= NUMKARTRESULTS) + { + // Not a real item. + return false; + } + + if (K_CanChangeRules(true) == false) + { + // Force all items to be enabled. + return true; + } + + // Allow the user preference. + return cv_items[item - 1].value; +} + +/*-------------------------------------------------- + boolean K_ItemSingularity(kartitems_t item) + + See header file for description. +--------------------------------------------------*/ +boolean K_ItemSingularity(kartitems_t item) +{ + switch (item) + { + case KITEM_SPB: + case KITEM_SHRINK: + { + return true; + } + default: + { + return false; + } + } +} + +/*-------------------------------------------------- + static fixed_t K_ItemOddsScale(UINT8 playerCount) + + A multiplier for odds and distances to scale + them with the player count. + + Input Arguments:- + playerCount - Number of players in the game. + + Return:- + Fixed point number, to multiply odds or + distances by. +--------------------------------------------------*/ +static fixed_t K_ItemOddsScale(UINT8 playerCount) +{ + const UINT8 basePlayer = 8; // The player count we design most of the game around. + fixed_t playerScaling = 0; + + if (playerCount < 2) + { + // Cap to 1v1 scaling + playerCount = 2; + } + + // Then, it multiplies it further if the player count isn't equal to basePlayer. + // This is done to make low player count races more interesting and high player count rates more fair. + if (playerCount < basePlayer) + { + // Less than basePlayer: increase odds significantly. + // 2P: x2.5 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 4); + } + else if (playerCount > basePlayer) + { + // More than basePlayer: reduce odds slightly. + // 16P: x0.75 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 32); + } + + return playerScaling; +} + +/*-------------------------------------------------- + static UINT32 K_UndoMapScaling(UINT32 distance) + + Takes a raw map distance and adjusts it to + be in x1 scale. + + Input Arguments:- + distance - Original distance. + + Return:- + Distance unscaled by mapobjectscale. +--------------------------------------------------*/ +static UINT32 K_UndoMapScaling(UINT32 distance) +{ + if (mapobjectscale != FRACUNIT) + { + // Bring back to normal scale. + return FixedDiv(distance, mapobjectscale); + } + + return distance; +} + +/*-------------------------------------------------- + static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) + + Adjust item distance for lobby-size scaling + as well as Frantic Items. + + Input Arguments:- + distance - Original distance. + numPlayers - Number of players in the game. + + Return:- + New distance after scaling. +--------------------------------------------------*/ +static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) +{ + if (franticitems == true) + { + // Frantic items pretends everyone's farther apart, for crazier items. + distance = FixedMul(distance, FRANTIC_ITEM_SCALE); + } + + // Items get crazier with the fewer players that you have. + distance = FixedMul( + distance, + FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) + ); + + return distance; +} + +/*-------------------------------------------------- + static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) + + Gets a player's distance used for the item + roulette, including all scaling factors. + + Input Arguments:- + player - The player to get the distance of. + numPlayers - Number of players in the game. + + Return:- + The player's finalized item distance. +--------------------------------------------------*/ +static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) +{ + UINT32 pdis = 0; + + if (player == NULL) + { + return 0; + } + +#if 0 + if (specialStage.active == true) + { + UINT32 ufoDis = K_GetSpecialUFODistance(); + + if (player->distancetofinish <= ufoDis) + { + // You're ahead of the UFO. + pdis = 0; + } + else + { + // Subtract the UFO's distance from your distance! + pdis = player->distancetofinish - ufoDis; + } + } + else +#endif + { + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator + && players[i].position == 1) + { + // This player is first! Yay! + + if (player->distancetofinish <= players[i].distancetofinish) + { + // Guess you're in first / tied for first? + pdis = 0; + } + else + { + // Subtract 1st's distance from your distance, to get your distance from 1st! + pdis = player->distancetofinish - players[i].distancetofinish; + } + break; + } + } + } + + pdis = K_UndoMapScaling(pdis); + pdis = K_ScaleItemDistance(pdis, numPlayers); + + if (player->bot && player->botvars.rival) + { + // Rival has better odds :) + pdis = FixedMul(pdis, FRANTIC_ITEM_SCALE); + } + + return pdis; +} + +/*-------------------------------------------------- + INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) + + See header file for description. +--------------------------------------------------*/ +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; + + 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); + + if (player != NULL) + { + bot = player->bot; + rival = (bot == true && player->botvars.rival == true); + position = player->position; + } + + if (K_ItemEnabled(item) == false) + { + return 0; + } + + if (K_GetItemCooldown(item) > 0) + { + // Cooldown is still running, don't give another. + return 0; + } + + /* + if (bot) + { + // TODO: Item use on bots should all be passed-in functions. + // Instead of manually inserting these, it should return 0 + // for any items without an item use function supplied + + switch (item) + { + case KITEM_SNEAKER: + break; + default: + return 0; + } + } + */ + (void)bot; + + shieldType = K_GetShieldFromItem(item); + switch (shieldType) + { + 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; + } + } + } + } + + if (gametype == GT_BATTLE) + { + I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table + newOdds = K_KartItemOddsBattle[item-1][pos]; + } + else + { + I_Assert(pos < 8); // Ditto + newOdds = K_KartItemOddsRace[item-1][pos]; + } + + newOdds <<= FRACBITS; + + switch (item) + { + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_SUPERRING: + { + notNearEnd = true; + break; + } + + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_BALLHOG: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + { + powerItem = true; + break; + } + + case KITEM_HYUDORO: + case KRITEM_TRIPLEBANANA: + { + powerItem = true; + notNearEnd = true; + break; + } + + case KITEM_INVINCIBILITY: + case KITEM_MINE: + case KITEM_GROW: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + { + cooldownOnStart = true; + powerItem = true; + break; + } + + case KITEM_SPB: + { + cooldownOnStart = true; + notNearEnd = true; + + if ((gametyperules & GTR_CIRCUIT) == 0) + { + // Needs to be a race. + return 0; + } + + 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); + } + break; + } + + case KITEM_SHRINK: + { + cooldownOnStart = true; + powerItem = true; + notNearEnd = true; + + if (roulette->playing - 1 <= roulette->exiting) + { + return 0; + } + break; + } + + case KITEM_LIGHTNINGSHIELD: + { + cooldownOnStart = true; + powerItem = true; + + if (spbplace != -1) + { + return 0; + } + break; + } + + default: + { + break; + } + } + + if (newOdds == 0) + { + // Nothing else we want to do with odds matters at this point :p + 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)); + return newOdds; +} + +/*-------------------------------------------------- + static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) + + Gets which item bracket the player is in. + This can be adjusted depending on which + items being turned off. + + Input Arguments:- + player - The player the roulette is for. + roulette - The item roulette data. + + Return:- + The item bracket the player is in, as an + index to the array. +--------------------------------------------------*/ +static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) +{ + UINT8 i; + UINT8 useOdds = 0; + UINT8 distTable[14]; + UINT8 distLen = 0; + UINT8 totalSize = 0; + boolean oddsValid[8]; + + for (i = 0; i < 8; i++) + { + UINT8 j; + + if (gametype == GT_BATTLE && i > 1) + { + oddsValid[i] = false; + continue; + } + + for (j = 1; j < NUMKARTRESULTS; j++) + { + if (K_KartGetItemOdds(player, roulette, i, j) > 0) + { + break; + } + } + + oddsValid[i] = (j < NUMKARTRESULTS); + } + +#define SETUPDISTTABLE(odds, num) \ + totalSize += num; \ + if (oddsValid[odds]) \ + for (i = num; i; --i) \ + distTable[distLen++] = odds; + + if (gametype == GT_BATTLE) // Battle Mode + { + useOdds = 0; + } + else + { + SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,2); + SETUPDISTTABLE(4,2); + SETUPDISTTABLE(5,3); + SETUPDISTTABLE(6,3); + SETUPDISTTABLE(7,1); + + for (i = 0; i < totalSize; i++) + { + fixed_t pos = 0; + fixed_t dist = 0; + UINT8 index = 0; + + if (i == totalSize-1) + { + useOdds = distTable[distLen - 1]; + break; + } + + pos = ((i << FRACBITS) * distLen) / totalSize; + dist = FixedMul(DISTVAR << FRACBITS, pos) >> FRACBITS; + index = FixedInt(FixedRound(pos)); + + if (roulette->dist <= (unsigned)dist) + { + useOdds = distTable[index]; + break; + } + } + } + +#undef SETUPDISTTABLE + + return useOdds; +} + +/*-------------------------------------------------- + static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) + + Determines special conditions where we want + to forcefully give the player an SPB. + + Input Arguments:- + player - The player the roulette is for. + roulette - The item roulette data. + + Return:- + true if we want to give the player a forced SPB, + otherwise false. +--------------------------------------------------*/ +static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) +{ + if (K_ItemEnabled(KITEM_SPB) == false) + { + return false; + } + + if (!(gametyperules & GTR_CIRCUIT)) + { + return false; + } + + if (player == NULL) + { + return false; + } + + if (player->position <= 1) + { + return false; + } + + if (spbplace != -1) + { + return false; + } + + if (itemCooldowns[KITEM_SPB - 1] > 0) + { + return false; + } + +#if 0 + if (roulette->playing <= 2) + { + return false; + } +#endif + + return (roulette->secondToFirst >= SPBFORCEDIST); +} + +/*-------------------------------------------------- + static void K_InitRoulette(itemroulette_t *const roulette) + + Initializes the data for a new item roulette. + + Input Arguments:- + roulette - The item roulette data to initialize. + + Return:- + N/A +--------------------------------------------------*/ +static void K_InitRoulette(itemroulette_t *const roulette) +{ + size_t i; + +#ifndef ITEM_LIST_SIZE + if (roulette->itemList == NULL) + { + roulette->itemListCap = 8; + roulette->itemList = Z_Calloc( + sizeof(SINT8) * roulette->itemListCap, + PU_STATIC, + &roulette->itemList + ); + + if (roulette->itemList == NULL) + { + I_Error("Not enough memory for item roulette list\n"); + } + } +#endif + + roulette->itemListLen = 0; + roulette->index = 0; + + roulette->useOdds = UINT8_MAX; + roulette->baseDist = roulette->dist = 0; + roulette->playing = roulette->exiting = 0; + roulette->firstDist = roulette->secondDist = UINT32_MAX; + roulette->secondToFirst = 0; + + roulette->elapsed = 0; + roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK; // Some default speed + + roulette->active = true; + roulette->eggman = false; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + roulette->playing++; + + if (players[i].exiting) + { + roulette->exiting++; + } + + if (players[i].position == 1) + { + roulette->firstDist = K_UndoMapScaling(players[i].distancetofinish); + } + + if (players[i].position == 2) + { + roulette->secondDist = K_UndoMapScaling(players[i].distancetofinish); + } + } + + // Calculate 2nd's distance from 1st, for SPB + if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX) + { + roulette->secondToFirst = roulette->secondDist - roulette->firstDist; + roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling + } +} + +/*-------------------------------------------------- + static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) + + Pushes a new item to the end of the item + roulette's item list. + + Input Arguments:- + roulette - The item roulette data to modify. + item - The item to push to the list. + + Return:- + N/A +--------------------------------------------------*/ +static void K_PushToRouletteItemList(itemroulette_t *const roulette, kartitems_t item) +{ +#ifdef ITEM_LIST_SIZE + if (roulette->itemListLen >= ITEM_LIST_SIZE) + { + I_Error("Out of space for item reel! Go and make ITEM_LIST_SIZE bigger I guess?\n"); + return; + } +#else + I_Assert(roulette->itemList != NULL); + + if (roulette->itemListLen >= roulette->itemListCap) + { + roulette->itemListCap *= 2; + roulette->itemList = Z_Realloc( + roulette->itemList, + sizeof(SINT8) * roulette->itemListCap, + PU_STATIC, + &roulette->itemList + ); + + if (roulette->itemList == NULL) + { + I_Error("Not enough memory for item roulette list\n"); + } + } +#endif + + roulette->itemList[ roulette->itemListLen ] = item; + roulette->itemListLen++; +} + +/*-------------------------------------------------- + static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) + + Adds an item to a player's item reel. Unlike + pushing directly with K_PushToRouletteItemList, + this function handles special behaviors (like + padding with extra Super Rings). + + Input Arguments:- + player - The player to add to the item roulette. + This is valid to be NULL. + roulette - The player's item roulette data. + item - The item to push to the list. + + Return:- + N/A +--------------------------------------------------*/ +static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulette, kartitems_t item) +{ + K_PushToRouletteItemList(roulette, item); + + if (player == NULL) + { + return; + } + + // If we're in ring debt, pad out the reel with + // a BUNCH of Super Rings. + if (K_ItemEnabled(KITEM_SUPERRING) == true + && player->rings <= 0 + && (gametyperules & GTR_SPHERES) == 0) + { + K_PushToRouletteItemList(roulette, KITEM_SUPERRING); + } +} + +/*-------------------------------------------------- + static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) + + Determines the speed for the item roulette, + adjusted for progress in the race and front + running. + + Input Arguments:- + roulette - The item roulette data to modify. + + Return:- + N/A +--------------------------------------------------*/ +static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) +{ + fixed_t frontRun = 0; + fixed_t progress = 0; + fixed_t total = 0; + + if (modeattacking || roulette->playing <= 1) + { + // Time Attack rules; use a consistent speed. + roulette->tics = roulette->speed = ROULETTE_SPEED_TIMEATTACK; + return; + } + + if (roulette->baseDist > ENDDIST) + { + // Being farther in the course makes your roulette faster. + progress = min(FRACUNIT, FixedDiv(roulette->baseDist - ENDDIST, ROULETTE_SPEED_DIST)); + } + + if (roulette->baseDist > roulette->firstDist) + { + // Frontrunning makes your roulette faster. + frontRun = min(FRACUNIT, FixedDiv(roulette->baseDist - roulette->firstDist, ENDDIST)); + } + + // Combine our two factors together. + total = min(FRACUNIT, (frontRun / 2) + (progress / 2)); + + if (leveltime < starttime + 30*TICRATE) + { + // Don't impact as much at the start. + // This makes it so that everyone gets to enjoy the lowest speed at the start. + if (leveltime < starttime) + { + total = FRACUNIT; + } + else + { + const fixed_t lerp = FixedDiv(leveltime - starttime, 30*TICRATE); + total = FRACUNIT + FixedMul(lerp, total - FRACUNIT); + } + } + + roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); +} + +/*-------------------------------------------------- + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette) + + See header file for description. +--------------------------------------------------*/ +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette) +{ + UINT32 spawnChance[NUMKARTRESULTS] = {0}; + UINT32 totalSpawnChance = 0; + size_t rngRoll = 0; + + UINT8 numItems = 0; + kartitems_t singleItem = KITEM_SAD; + + size_t i; + + K_InitRoulette(roulette); + + if (player != NULL) + { + roulette->baseDist = K_UndoMapScaling(player->distancetofinish); + K_CalculateRouletteSpeed(roulette); + } + + // SPECIAL CASE No. 1: + // Give only the debug item if specified + if (cv_kartdebugitem.value != KITEM_NONE) + { + K_PushToRouletteItemList(roulette, cv_kartdebugitem.value); + return; + } + + // SPECIAL CASE No. 2: + // Use a special, pre-determined item reel for Time Attack / Free Play + if (bossinfo.boss == true) + { + for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelBoss[i]); + } + + return; + } + else if (modeattacking || roulette->playing <= 1) + { + switch (gametype) + { + 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; + } + } + + return; + } + + // SPECIAL CASE No. 3: + // Only give the SPB if conditions are right + if (K_ForcedSPB(player, roulette) == true) + { + K_AddItemToReel(player, roulette, KITEM_SPB); + return; + } + + // SPECIAL CASE No. 4: + // If only one item is enabled, always use it + for (i = 1; i < NUMKARTRESULTS; i++) + { + if (K_ItemEnabled(i) == true) + { + numItems++; + if (numItems > 1) + { + break; + } + + singleItem = i; + } + } + + if (numItems < 2) + { + // singleItem = KITEM_SAD by default, + // so it will be used when all items are turned off. + K_AddItemToReel(player, roulette, singleItem); + return; + } + + // Special cases are all handled, we can now + // actually calculate actual item reels. + roulette->dist = K_GetItemRouletteDistance(player, roulette->playing); + roulette->useOdds = K_FindUseodds(player, roulette); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + spawnChance[i] = ( + totalSpawnChance += K_KartGetItemOdds(player, roulette, roulette->useOdds, i) + ); + } + + if (totalSpawnChance == 0) + { + // This shouldn't happen, but if it does, early exit. + // Maybe can happen if you enable multiple items for + // another gametype, so we give the singleItem as a fallback. + K_AddItemToReel(player, roulette, singleItem); + return; + } + + // Create the same item reel given the same inputs. + P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + + while (totalSpawnChance > 0) + { + rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); + for (i = 1; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++) + { + continue; + } + + K_AddItemToReel(player, roulette, i); + + for (; i < NUMKARTRESULTS; i++) + { + // Be sure to fix the remaining items' odds too. + if (spawnChance[i] > 0) + { + spawnChance[i]--; + } + } + + totalSpawnChance--; + } +} + +/*-------------------------------------------------- + void K_StartItemRoulette(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_StartItemRoulette(player_t *const player) +{ + itemroulette_t *const roulette = &player->itemRoulette; + size_t i; + + K_FillItemRouletteData(player, roulette); + + // Make the bots select their item after a little while. + // One of the few instances of bot RNG, would be nice to remove it. + player->botvars.itemdelay = P_RandomRange(PR_UNDEFINED, TICRATE, TICRATE*3); + + // Prevent further duplicates of items that + // are intended to only have one out at a time. + for (i = 0; i < roulette->itemListLen; i++) + { + kartitems_t item = roulette->itemList[i]; + if (K_ItemSingularity(item) == true) + { + K_SetItemCooldown(item, TICRATE<<4); + } + } +} + +/*-------------------------------------------------- + void K_StartEggmanRoulette(player_t *const player) + + See header file for description. +--------------------------------------------------*/ +void K_StartEggmanRoulette(player_t *const player) +{ + itemroulette_t *const roulette = &player->itemRoulette; + K_StartItemRoulette(player); + roulette->eggman = true; +} + +/*-------------------------------------------------- + fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta) +{ + const fixed_t curTic = (roulette->tics << FRACBITS) - renderDelta; + const fixed_t midTic = roulette->speed * (FRACUNIT >> 1); + + return FixedMul(FixedDiv(midTic - curTic, ((roulette->speed + 1) << FRACBITS)), ROULETTE_SPACING); +} + +/*-------------------------------------------------- + static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) + + Initializes a player's item to what was + received from the roulette. + + Input Arguments:- + player - The player receiving the item. + getitem - The item to give to the player. + + Return:- + N/A +--------------------------------------------------*/ +static void K_KartGetItemResult(player_t *const player, kartitems_t getitem) +{ + if (K_ItemSingularity(getitem) == true) + { + K_SetItemCooldown(getitem, 20*TICRATE); + } + + player->botvars.itemdelay = TICRATE; + player->botvars.itemconfirm = 0; + + player->itemtype = K_ItemResultToType(getitem); + player->itemamount = K_ItemResultToAmount(getitem); +} + +/*-------------------------------------------------- + void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) + + See header file for description. +--------------------------------------------------*/ +void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) +{ + itemroulette_t *const roulette = &player->itemRoulette; + boolean confirmItem = false; + + // This makes the roulette cycle through items. + // If this isn't active, you shouldn't be here. + if (roulette->active == false) + { + return; + } + + if (roulette->itemListLen == 0 +#ifndef ITEM_LIST_SIZE + || roulette->itemList == NULL +#endif + ) + { + // Invalid roulette setup. + // Escape before we run into issues. + roulette->active = false; + return; + } + + if (roulette->elapsed > TICRATE>>1) // Prevent accidental immediate item confirm + { + if (roulette->elapsed > TICRATE<<4) + { + // Waited way too long, forcefully confirm the item. + confirmItem = true; + } + else + { + // We can stop our item when we choose. + confirmItem = !!(cmd->buttons & BT_ATTACK); + } + } + + // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. + // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. + // Finally, if you get past this check, now you can actually start calculating what item you get. + if (confirmItem == true && (player->pflags & (PF_ITEMOUT|PF_EGGMANOUT|PF_USERINGS)) == 0) + { + if (roulette->eggman == true) + { + // FATASS JUMPSCARE instead of your actual item + player->eggmanexplode = 4*TICRATE; + + //player->karthud[khud_itemblink] = TICRATE; + //player->karthud[khud_itemblinkmode] = 1; + //player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrole); + } + } + else + { + kartitems_t finalItem = roulette->itemList[ roulette->index ]; + + K_KartGetItemResult(player, finalItem); + + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = 0; + player->karthud[khud_rouletteoffset] = K_GetRouletteOffset(roulette, FRACUNIT); + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrolf); + } + } + + // We're done, disable the roulette + roulette->active = false; + return; + } + + roulette->elapsed++; + + if (roulette->tics == 0) + { + roulette->index = (roulette->index + 1) % roulette->itemListLen; + roulette->tics = roulette->speed; + + // This makes the roulette produce the random noises. + roulette->sound = (roulette->sound + 1) % 8; + + if (P_IsDisplayPlayer(player) && !demo.freecam) + { + S_StartSound(NULL, sfx_itrol1 + roulette->sound); + } + } + else + { + roulette->tics--; + } +} diff --git a/src/k_roulette.h b/src/k_roulette.h new file mode 100644 index 000000000..0cef89f7a --- /dev/null +++ b/src/k_roulette.h @@ -0,0 +1,169 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2022 by Kart Krew +// Copyright (C) 2022 by Sally "TehRealSalt" Cochenour +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_roulette.h +/// \brief Item roulette code. + +#ifndef __K_ROULETTE_H__ +#define __K_ROULETTE_H__ + +#include "doomtype.h" +#include "d_player.h" + +#define ROULETTE_SPACING (36 << FRACBITS) +#define ROULETTE_SPACING_SPLITSCREEN (16 << FRACBITS) + +/*-------------------------------------------------- + boolean K_ItemEnabled(kartitems_t item); + + Determines whenever or not an item should + be enabled. Accounts for situations where + rules should not be able to be changed. + + Input Arguments:- + item - The item to check. + + Return:- + true if the item is enabled, otherwise false. +--------------------------------------------------*/ + +boolean K_ItemEnabled(kartitems_t item); + + +/*-------------------------------------------------- + boolean K_ItemSingularity(kartitems_t item); + + Determines whenever or not this item should + be using special cases to prevent more than + one existing at a time. + + Input Arguments:- + item - The item to check. + + Return:- + true to use the special rules, otherwise false. +--------------------------------------------------*/ + +boolean K_ItemSingularity(kartitems_t item); + + +/*-------------------------------------------------- + INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); + + Gets the frequency an item should show up in + an item bracket, and adjusted for special + factors (such as Frantic Items). + + Input Arguments:- + player - The player we intend to give the item to later. + Can be NULL for generic use. + roulette - The roulette data that we intend to + insert this item into. + pos - The item bracket we are in. + item - The item to give. + + Return:- + The number of items we want to insert + into the roulette. +--------------------------------------------------*/ + +INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); + + +/*-------------------------------------------------- + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); + + Fills out the item roulette struct when it is + initially created. This function needs to be + HUD-safe for the item debugger, so the player + cannot be modified at this stage. + + Input Arguments:- + player - The player this roulette data is for. + Can be NULL for generic use. + roulette - The roulette data struct to fill out. + + Return:- + N/A +--------------------------------------------------*/ + +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette); + + +/*-------------------------------------------------- + void K_StartItemRoulette(player_t *const player); + + Starts the item roulette sequence for a player. + This stage can only be used by gameplay, thus + this handles gameplay modifications as well. + + Input Arguments:- + player - The player to start the item roulette for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_StartItemRoulette(player_t *const player); + + +/*-------------------------------------------------- + void K_StartEggmanRoulette(player_t *const player); + + Starts the Eggman Mark roulette sequence for + a player. Looks identical to a regular item + roulette, but gives you the Eggman explosion + countdown instead when confirming it. + + Input Arguments:- + player - The player to start the Eggman roulette for. + + Return:- + N/A +--------------------------------------------------*/ + +void K_StartEggmanRoulette(player_t *const player); + + +/*-------------------------------------------------- + fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta); + + Gets the Y offset, for use in the roulette HUD. + A separate function since it is used both by the + HUD itself, as well as when confirming an item. + + Input Arguments:- + roulette - The roulette we are drawing for. + renderDelta - Fractional tic delta, when used for HUD. + + Return:- + The Y offset when drawing the item. +--------------------------------------------------*/ + +fixed_t K_GetRouletteOffset(itemroulette_t *const roulette, fixed_t renderDelta); + + +/*-------------------------------------------------- + void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); + + Handles ticking a player's item roulette, + and player input for stopping it. + + Input Arguments:- + player - The player to run the item roulette for. + cmd - The player's controls. + + Return:- + N/A +--------------------------------------------------*/ + +void K_KartItemRoulette(player_t *const player, ticcmd_t *cmd); + + +#endif // __K_ROULETTE_H__ diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2a3b9e46a..b69449ba2 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -304,10 +304,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->tripwirePass); else if (fastcmp(field,"tripwireLeniency")) lua_pushinteger(L, plr->tripwireLeniency); + /* else if (fastcmp(field,"itemroulette")) lua_pushinteger(L, plr->itemroulette); - else if (fastcmp(field,"roulettetype")) - lua_pushinteger(L, plr->roulettetype); + */ else if (fastcmp(field,"itemtype")) lua_pushinteger(L, plr->itemtype); else if (fastcmp(field,"itemamount")) @@ -680,10 +680,10 @@ static int player_set(lua_State *L) plr->tripwirePass = luaL_checkinteger(L, 3); else if (fastcmp(field,"tripwireLeniency")) plr->tripwireLeniency = luaL_checkinteger(L, 3); + /* else if (fastcmp(field,"itemroulette")) plr->itemroulette = luaL_checkinteger(L, 3); - else if (fastcmp(field,"roulettetype")) - plr->roulettetype = luaL_checkinteger(L, 3); + */ else if (fastcmp(field,"itemtype")) plr->itemtype = luaL_checkinteger(L, 3); else if (fastcmp(field,"itemamount")) diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 4e9c67d2f..b3b5ff2c0 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -1 +1,12 @@ -target_sourcefile(c) +target_sources(SRB2SDL2 PRIVATE + hyudoro.c + gardentop.c + shrink.c + item-debris.c + spb.c + manta-ring.c + orbinaut.c + jawz.c + duel-bomb.c + broly.c +) diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile deleted file mode 100644 index b8cb63b1f..000000000 --- a/src/objects/Sourcefile +++ /dev/null @@ -1,9 +0,0 @@ -hyudoro.c -gardentop.c -shrink.c -item-debris.c -spb.c -manta-ring.c -orbinaut.c -jawz.c -duel-bomb.c diff --git a/src/objects/broly.c b/src/objects/broly.c new file mode 100644 index 000000000..d041c23b7 --- /dev/null +++ b/src/objects/broly.c @@ -0,0 +1,72 @@ +#include "../doomdef.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_easing.h" +#include "../p_local.h" +#include "../s_sound.h" + +// TODO: generic function +static void P_InstaScale(mobj_t *thing, fixed_t scale) +{ + P_SetScale(thing, scale); + thing->destscale = scale; +} + +/* An object may not be visible on the same tic: + 1) that it spawned + 2) that it cycles to the next state */ +#define BUFFER_TICS (2) + +#define broly_duration(o) ((o)->extravalue1) +#define broly_maxscale(o) ((o)->extravalue2) + +static inline fixed_t +get_unit_linear (const mobj_t *x) +{ + const tic_t t = (x->tics - BUFFER_TICS); + + return t * FRACUNIT / broly_duration(x); +} + +mobj_t * +Obj_SpawnBrolyKi +( mobj_t * source, + tic_t duration) +{ + mobj_t *x = P_SpawnMobjFromMobj( + source, 0, 0, 0, MT_BROLY); + + if (duration == 0) + { + return x; + } + + // Shrink into center of source object. + x->z = (source->z + source->height / 2); + + x->colorized = true; + x->color = source->color; + x->hitlag = 0; // do not copy source hitlag + + broly_maxscale(x) = 64 * mapobjectscale; + broly_duration(x) = duration; + + x->tics = (duration + BUFFER_TICS); + + K_ReduceVFX(x, NULL); + + S_StartSound(x, sfx_cdfm74); + + return x; +} + +void +Obj_BrolyKiThink (mobj_t *x) +{ + const fixed_t + t = get_unit_linear(x), + n = Easing_OutSine(t, 0, broly_maxscale(x)); + + P_InstaScale(x, n); +} diff --git a/src/p_enemy.c b/src/p_enemy.c index afe17b5bc..2e1ed8557 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -34,6 +34,7 @@ #include "k_respawn.h" #include "k_collide.h" #include "k_objects.h" +#include "k_roulette.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -13043,9 +13044,13 @@ void A_ItemPop(mobj_t *actor) Obj_SpawnItemDebrisEffects(actor, actor->target); if (locvar1 == 1) + { P_GivePlayerSpheres(actor->target->player, actor->extravalue1); + } else if (locvar1 == 0) - actor->target->player->itemroulette = 1; + { + K_StartItemRoulette(actor->target->player); + } // Here at mapload in battle? if ((gametyperules & GTR_BUMPERS) && (actor->flags2 & MF2_BOSSNOTRAP)) @@ -13186,7 +13191,7 @@ void A_LandMineExplode(mobj_t *actor) expl->momz = ((i+1)*actor->scale*5/2)*P_MobjFlip(expl); } - K_SpawnBrolyKi(actor, delay); + Obj_SpawnBrolyKi(actor, delay); } void A_BallhogExplode(mobj_t *actor) diff --git a/src/p_inter.c b/src/p_inter.c index 904905cb3..35dd88a02 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -38,6 +38,7 @@ #include "k_respawn.h" #include "p_spec.h" #include "k_objects.h" +#include "k_roulette.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -130,7 +131,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) return false; // Already have fake - if (player->roulettetype == 2 + if ((player->itemRoulette.active && player->itemRoulette.eggman) == true || player->eggmanexplode) return false; } @@ -143,7 +144,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) return false; // Item slot already taken up - if (player->itemroulette + if (player->itemRoulette.active == true || (weapon != 3 && player->itemamount) || (player->pflags & PF_ITEMOUT)) return false; @@ -411,8 +412,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) return; - player->itemroulette = 1; - player->roulettetype = 1; + K_StartItemRoulette(player); // Karma fireworks for (i = 0; i < 5; i++) @@ -1450,8 +1450,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget } player->karthud[khud_itemblink] = TICRATE; player->karthud[khud_itemblinkmode] = 0; - player->itemroulette = 0; - player->roulettetype = 0; + player->itemRoulette.active = false; if (P_IsDisplayPlayer(player)) S_StartSound(NULL, sfx_itrolf); } diff --git a/src/p_map.c b/src/p_map.c index a2916a94a..44ffcf81c 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -406,7 +406,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) K_TumbleInterrupt(object->player); P_ResetPlayer(object->player); - object->player->springstars = max(vertispeed, horizspeed) / FRACUNIT / 2; + object->player->springstars = max(abs(vertispeed), horizspeed) / FRACUNIT / 2; object->player->springcolor = starcolor; // Less friction when hitting springs diff --git a/src/p_mobj.c b/src/p_mobj.c index 137bc38fe..43aa79294 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6137,7 +6137,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) break; // see also K_drawKartItem in k_hud.c - case MT_PLAYERARROW: + case MT_PLAYERARROW: // FIXME: Delete this object, attach to name tags instead. if (mobj->target && mobj->target->health && mobj->target->player && !mobj->target->player->spectator && mobj->target->health && mobj->target->player->playerstate != PST_DEAD @@ -6191,7 +6191,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) } // Do this in an easy way - if (mobj->target->player->itemroulette) + if (mobj->target->player->itemRoulette.active) { mobj->tracer->color = mobj->target->player->skincolor; mobj->tracer->colorized = true; @@ -6207,11 +6207,11 @@ static void P_MobjSceneryThink(mobj_t *mobj) const INT32 numberdisplaymin = ((mobj->target->player->itemtype == KITEM_ORBINAUT) ? 5 : 2); // Set it to use the correct states for its condition - if (mobj->target->player->itemroulette) + if (mobj->target->player->itemRoulette.active) { P_SetMobjState(mobj, S_PLAYERARROW_BOX); mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = K_GetRollingRouletteItem(mobj->target->player) | FF_FULLBRIGHT; + mobj->tracer->frame = 1 | FF_FULLBRIGHT; mobj->tracer->renderflags &= ~RF_DONTDRAW; } else if (mobj->target->player->stealingtimer < 0) @@ -6517,6 +6517,9 @@ static void P_MobjSceneryThink(mobj_t *mobj) case MT_DRIFTELECTRICSPARK: mobj->renderflags ^= RF_DONTDRAW; break; + case MT_BROLY: + Obj_BrolyKiThink(mobj); + break; case MT_VWREF: case MT_VWREB: { diff --git a/src/p_saveg.c b/src/p_saveg.c index 73d4a7d1f..3ce18aa5f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -96,7 +96,7 @@ static void P_NetArchivePlayers(void) { INT32 i, j; UINT16 flags; -// size_t q; + size_t q; WRITEUINT32(save_p, ARCHIVEBLOCK_PLAYERS); @@ -310,9 +310,6 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].tripwirePass); WRITEUINT16(save_p, players[i].tripwireLeniency); - WRITEUINT16(save_p, players[i].itemroulette); - WRITEUINT8(save_p, players[i].roulettetype); - WRITESINT8(save_p, players[i].itemtype); WRITEUINT8(save_p, players[i].itemamount); WRITESINT8(save_p, players[i].throwdir); @@ -410,6 +407,50 @@ static void P_NetArchivePlayers(void) WRITEUINT32(save_p, players[i].botvars.itemconfirm); WRITESINT8(save_p, players[i].botvars.turnconfirm); WRITEUINT32(save_p, players[i].botvars.spindashconfirm); + + // itemroulette_t + WRITEUINT8(save_p, players[i].itemRoulette.active); + +#ifdef ITEM_LIST_SIZE + WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + + for (q = 0; q < ITEM_LIST_SIZE; q++) + { + if (q >= players[i].itemRoulette.itemListLen) + { + WRITESINT8(save_p, KITEM_NONE); + } + else + { + WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); + } + } +#else + if (players[i].itemRoulette.itemList == NULL) + { + WRITEUINT32(save_p, 0); + WRITEUINT32(save_p, 0); + } + else + { + WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); + WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + + for (q = 0; q < players[i].itemRoulette.itemListLen; q++) + { + WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); + } + } +#endif + + WRITEUINT8(save_p, players[i].itemRoulette.useOdds); + WRITEUINT32(save_p, players[i].itemRoulette.dist); + WRITEUINT32(save_p, players[i].itemRoulette.index); + WRITEUINT8(save_p, players[i].itemRoulette.sound); + WRITEUINT32(save_p, players[i].itemRoulette.speed); + WRITEUINT32(save_p, players[i].itemRoulette.tics); + WRITEUINT32(save_p, players[i].itemRoulette.elapsed); + WRITEUINT8(save_p, players[i].itemRoulette.eggman); } } @@ -417,6 +458,7 @@ static void P_NetUnArchivePlayers(void) { INT32 i, j; UINT16 flags; + size_t q; if (READUINT32(save_p) != ARCHIVEBLOCK_PLAYERS) I_Error("Bad $$$.sav at archive block Players"); @@ -612,9 +654,6 @@ static void P_NetUnArchivePlayers(void) players[i].tripwirePass = READUINT8(save_p); players[i].tripwireLeniency = READUINT16(save_p); - players[i].itemroulette = READUINT16(save_p); - players[i].roulettetype = READUINT8(save_p); - players[i].itemtype = READSINT8(save_p); players[i].itemamount = READUINT8(save_p); players[i].throwdir = READSINT8(save_p); @@ -713,6 +752,61 @@ static void P_NetUnArchivePlayers(void) players[i].botvars.turnconfirm = READSINT8(save_p); players[i].botvars.spindashconfirm = READUINT32(save_p); + // itemroulette_t + players[i].itemRoulette.active = (boolean)READUINT8(save_p); + +#ifdef ITEM_LIST_SIZE + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); + + for (q = 0; q < ITEM_LIST_SIZE; q++) + { + players[i].itemRoulette.itemList[q] = READSINT8(save_p); + } +#else + players[i].itemRoulette.itemListCap = (size_t)READUINT32(save_p); + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); + + if (players[i].itemRoulette.itemListCap > 0) + { + if (players[i].itemRoulette.itemList == NULL) + { + players[i].itemRoulette.itemList = Z_Calloc( + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_STATIC, + &players[i].itemRoulette.itemList + ); + } + else + { + players[i].itemRoulette.itemList = Z_Realloc( + players[i].itemRoulette.itemList, + sizeof(SINT8) * players[i].itemRoulette.itemListCap, + PU_STATIC, + &players[i].itemRoulette.itemList + ); + } + + if (players[i].itemRoulette.itemList == NULL) + { + I_Error("Not enough memory for item roulette list\n"); + } + + for (q = 0; q < players[i].itemRoulette.itemListLen; q++) + { + players[i].itemRoulette.itemList[q] = READSINT8(save_p); + } + } +#endif + + players[i].itemRoulette.useOdds = READUINT8(save_p); + players[i].itemRoulette.dist = READUINT32(save_p); + players[i].itemRoulette.index = (size_t)READUINT32(save_p); + players[i].itemRoulette.sound = READUINT8(save_p); + players[i].itemRoulette.speed = (tic_t)READUINT32(save_p); + players[i].itemRoulette.tics = (tic_t)READUINT32(save_p); + players[i].itemRoulette.elapsed = (tic_t)READUINT32(save_p); + players[i].itemRoulette.eggman = (boolean)READUINT8(save_p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } diff --git a/src/p_setup.c b/src/p_setup.c index 6a1c5c049..3be049311 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6837,6 +6837,7 @@ static void P_InitLevelSettings(void) memset(&quake,0,sizeof(struct quake)); // song credit init + Z_Free(cursongcredit.text); memset(&cursongcredit,0,sizeof(struct cursongcredit)); cursongcredit.trans = NUMTRANSMAPS; diff --git a/src/s_sound.c b/src/s_sound.c index fb21cc190..96211d653 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1476,16 +1476,32 @@ ReadMusicDefFields textline = value; - /* based ignored lumps */ - if (!stricmp(stoken, "usage")) { -#if 0 // Ignore for now - STRBUFCPY(def->usage, textline); -#endif - } else if (!stricmp(stoken, "source")) { - STRBUFCPY(def->source, textline); - } else if (!stricmp(stoken, "volume")) { + if (!stricmp(stoken, "title")) + { + Z_Free(def->title); + def->title = Z_StrDup(textline); + } + else if (!stricmp(stoken, "author")) + { + Z_Free(def->author); + def->author = Z_StrDup(textline); + } + else if (!stricmp(stoken, "source")) + { + Z_Free(def->source); + def->source = Z_StrDup(textline); + } + else if (!stricmp(stoken, "originalcomposers")) + { + Z_Free(def->composers); + def->composers = Z_StrDup(textline); + } + else if (!stricmp(stoken, "volume")) + { def->volume = atoi(textline); - } else { + } + else + { MusicDefError(CONS_WARNING, "Unknown field '%s'.", stoken, lumpnum, line); @@ -1608,14 +1624,53 @@ void S_ShowMusicCredit(void) { if (!stricmp(def->name, music_name)) { + char credittext[128] = ""; + char *work = NULL; + size_t len = 128, worklen; + + if (!def->title) + { + return; + } + + work = va("\x1F %s", def->title); + worklen = strlen(work); + if (worklen <= len) + { + strncat(credittext, work, len); + len -= worklen; + +#define MUSICCREDITAPPEND(field)\ + if (field)\ + {\ + work = va(" - %s", field);\ + worklen = strlen(work);\ + if (worklen <= len)\ + {\ + strncat(credittext, work, len);\ + len -= worklen;\ + }\ + } + + MUSICCREDITAPPEND(def->author); + MUSICCREDITAPPEND(def->source); + +#undef MUSICCREDITAPPEND + } + + if (credittext[0] == '\0') + return; + cursongcredit.def = def; + Z_Free(cursongcredit.text); + cursongcredit.text = Z_StrDup(credittext); cursongcredit.anim = 5*TICRATE; - cursongcredit.x = cursongcredit.old_x =0; + cursongcredit.x = cursongcredit.old_x = 0; cursongcredit.trans = NUMTRANSMAPS; return; } - else - def = def->next; + + def = def->next; } } diff --git a/src/s_sound.h b/src/s_sound.h index adbf92533..c9a86a2f5 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -173,8 +173,10 @@ boolean S_SpeedMusic(float speed); struct musicdef_t { char name[7]; - //char usage[256]; - char source[256]; + char *title; + char *author; + char *source; + char *composers; int volume; musicdef_t *next; }; @@ -182,6 +184,7 @@ struct musicdef_t extern struct cursongcredit { musicdef_t *def; + char *text; UINT16 anim; UINT8 trans; fixed_t x; diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index dd8d304a4..ee9c4cddc 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -1,12 +1,17 @@ # Declare SDL2 interface sources -target_sources(SRB2SDL2 PRIVATE mixer_sound.c) - -target_sourcefile(c) - -target_sources(SRB2SDL2 PRIVATE ogl_sdl.c) - -target_sources(SRB2SDL2 PRIVATE i_threads.c) +target_sources(SRB2SDL2 PRIVATE + mixer_sound.c + ogl_sdl.c + i_threads.c + i_net.c + i_system.c + i_main.c + i_video.c + dosstr.c + endtxt.c + hwsym_sdl.c +) if(${SRB2_USEASM}) set_source_files_properties(${SRB2_ASM_SOURCES} PROPERTIES LANGUAGE C) diff --git a/src/sdl/Sourcefile b/src/sdl/Sourcefile deleted file mode 100644 index 82d5ce073..000000000 --- a/src/sdl/Sourcefile +++ /dev/null @@ -1,7 +0,0 @@ -i_net.c -i_system.c -i_main.c -i_video.c -dosstr.c -endtxt.c -hwsym_sdl.c diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt new file mode 100644 index 000000000..28c4ce492 --- /dev/null +++ b/src/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(srb2tests PRIVATE + boolcompat.cpp +) diff --git a/src/tests/boolcompat.cpp b/src/tests/boolcompat.cpp new file mode 100644 index 000000000..fee40cd36 --- /dev/null +++ b/src/tests/boolcompat.cpp @@ -0,0 +1,8 @@ +#include + +#include "../doomtype.h" + +TEST_CASE("C++ bool is convertible to doomtype.h boolean") { + REQUIRE(static_cast(true) == 1); + REQUIRE(static_cast(false) == 0); +} diff --git a/src/typedef.h b/src/typedef.h index 5fb5ec146..0be1884bc 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -37,6 +37,7 @@ TYPEDEF (discordRequest_t); TYPEDEF (respawnvars_t); TYPEDEF (botvars_t); TYPEDEF (skybox_t); +TYPEDEF (itemroulette_t); TYPEDEF (player_t); // d_clisrv.h